commit 9ed6a80afabc6d2b48b9a3c51842029cc8fe7d93 Author: upstream-mirror Date: Wed Sep 16 01:43:26 2026 +0500 snapshot of https://github.com/flutter/samples.git (main, 2026-09-16; blobs > 131072B omitted) diff --git a/.gemini/settings.json b/.gemini/settings.json new file mode 100644 index 0000000..ab9a0fc --- /dev/null +++ b/.gemini/settings.json @@ -0,0 +1,13 @@ +{ + "context": { + "fileName": "/.prompts/llm.md" + }, + "mcpServers": { + "dart": { + "command": "dart", + "args": [ + "mcp-server" + ] + } + } +} \ No newline at end of file diff --git a/.gemini/settings.json.orig b/.gemini/settings.json.orig new file mode 100644 index 0000000..715d896 --- /dev/null +++ b/.gemini/settings.json.orig @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "dart": { + "command": "dart", + "args": [ + "mcp-server" + ] + } + }, + "contextFileName": "/.prompts/llm.md" +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dedd30c --- /dev/null +++ b/.gitattributes @@ -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 \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..ae93fca --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -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]. + + +[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 \ No newline at end of file diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..6e545fe --- /dev/null +++ b/.github/dependabot.yaml @@ -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" + diff --git a/.github/no-response.yml b/.github/no-response.yml new file mode 100644 index 0000000..c712897 --- /dev/null +++ b/.github/no-response.yml @@ -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! diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml new file mode 100644 index 0000000..fce97ad --- /dev/null +++ b/.github/workflows/build-android.yml @@ -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 \ No newline at end of file diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml new file mode 100644 index 0000000..a521c86 --- /dev/null +++ b/.github/workflows/build-ios.yml @@ -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 diff --git a/.github/workflows/gemini-cli.yml b/.github/workflows/gemini-cli.yml new file mode 100644 index 0000000..d3c19d9 --- /dev/null +++ b/.github/workflows/gemini-cli.yml @@ -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<> "${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<> "${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 ""` or `gh issue comment "${ISSUE_NUMBER}" --body ""` 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 --body ""` + - For Issues: `gh issue comment --edit --body ""` + 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: "` + - `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}/`. + - `git checkout -b issue/${ISSUE_NUMBER}/my-fix` + - `git add .` + - `git commit -m "feat: "` + - `git push origin issue/${ISSUE_NUMBER}/my-fix` + - After pushing, you can create a pull request: `gh pr create --title "Fixes #${ISSUE_NUMBER}: " --body "This PR addresses issue #${ISSUE_NUMBER}."` + - Summarize what was changed and why in a markdown file: `write_file("response.md", "")` + - 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.** diff --git a/.github/workflows/gemini-issue-automated-triage.yml b/.github/workflows/gemini-issue-automated-triage.yml new file mode 100644 index 0000000..9d39f0d --- /dev/null +++ b/.github/workflows/gemini-issue-automated-triage.yml @@ -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.' + }) diff --git a/.github/workflows/gemini-issue-scheduled-triage.yml b/.github/workflows/gemini-issue-scheduled-triage.yml new file mode 100644 index 0000000..bd9c0da --- /dev/null +++ b/.github/workflows/gemini-issue-scheduled-triage.yml @@ -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) diff --git a/.github/workflows/gemini-pr-review.yml b/.github/workflows/gemini-pr-review.yml new file mode 100644 index 0000000..d6a8b71 --- /dev/null +++ b/.github/workflows/gemini-pr-review.yml @@ -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<> "${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<> "${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: + + + FILE: + DIFF: + + + -------------------- + + FILE: + DIFF: + + + -------------------- + + (and so on for all files changed) + + + 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: + + {{SEVERITY}} {{COMMENT_TEXT}} + + + Inline Comment Syntax: (Preferred): + + {{SEVERITY}} {{COMMENT_TEXT}} + ```suggestion + {{CODE_SUGGESTION}} + ``` + + + 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: + + 🟒 Use camelCase for function names + ```suggestion + myFooBarFunction + ``` + + + A critical severity example would be: + + πŸ”΄ 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.' + }) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..019f557 --- /dev/null +++ b/.github/workflows/main.yml @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7943f51 --- /dev/null +++ b/.gitignore @@ -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/ diff --git a/.prompts/code_freshness.md b/.prompts/code_freshness.md new file mode 100644 index 0000000..6b28e9a --- /dev/null +++ b/.prompts/code_freshness.md @@ -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. + --- + ``` \ No newline at end of file diff --git a/.prompts/llm.md b/.prompts/llm.md new file mode 100644 index 0000000..c31e109 --- /dev/null +++ b/.prompts/llm.md @@ -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**: ``, ``, `` +- **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 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: `[]` instead of `List()` +- **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 data) { + // Implementation +} + +// DO use explicit types for variables (avoid var/dynamic) +final List users = []; +final Map 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 for async functions that don't return values +Future saveUser(User user) async { + await repository.save(user); +} + +// DO prefer async/await over raw futures +Future> 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 names = []; +final Map scores = {}; +final Set 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 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()), + ); + }); + }); +}); +``` + +## 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 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. \ No newline at end of file diff --git a/.prompts/release.md b/.prompts/release.md new file mode 100644 index 0000000..9c46e5d --- /dev/null +++ b/.prompts/release.md @@ -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. \ No newline at end of file diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..1448b6b --- /dev/null +++ b/AUTHORS @@ -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 + +Google Inc. +Abhijeeth Padarthi +Filip Hracek diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3bb4049 --- /dev/null +++ b/CHANGELOG.md @@ -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)). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..4d99c2d --- /dev/null +++ b/CONTRIBUTING.md @@ -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]_ + +
+ +# 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. + +
+ +# 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. + +
+ +# 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.**
+ 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.**
+ 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.**
+ If it is, you should likely use a personal repository. + +5. **The sample isn’t tied to a library or package.**
+ 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.**
+ 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. + +
+ +# 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. +``` + +
+ +# 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. + +
+ +# 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 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..57e60e3 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/PATENTS b/PATENTS new file mode 100644 index 0000000..0df0589 --- /dev/null +++ b/PATENTS @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2e2f645 --- /dev/null +++ b/README.md @@ -0,0 +1,167 @@ +# Flutter samples + +[![Build Status](https://github.com/flutter/samples/workflows/Main%20Branch%20CI/badge.svg)](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 diff --git a/add_to_app/README.md b/add_to_app/README.md new file mode 100644 index 0000000..1109a13 --- /dev/null +++ b/add_to_app/README.md @@ -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 " 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 diff --git a/add_to_app/android_view/README.md b/add_to_app/android_view/README.md new file mode 100644 index 0000000..77abc79 --- /dev/null +++ b/add_to_app/android_view/README.md @@ -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). diff --git a/add_to_app/android_view/android_view/.gitignore b/add_to_app/android_view/android_view/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/add_to_app/android_view/android_view/.gitignore @@ -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 diff --git a/add_to_app/android_view/android_view/README.md b/add_to_app/android_view/android_view/README.md new file mode 100644 index 0000000..3cf9002 --- /dev/null +++ b/add_to_app/android_view/android_view/README.md @@ -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). diff --git a/add_to_app/android_view/android_view/app/.gitignore b/add_to_app/android_view/android_view/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/add_to_app/android_view/android_view/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/add_to_app/android_view/android_view/app/build.gradle b/add_to_app/android_view/android_view/app/build.gradle new file mode 100644 index 0000000..0797ed5 --- /dev/null +++ b/add_to_app/android_view/android_view/app/build.gradle @@ -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' +} diff --git a/add_to_app/android_view/android_view/app/lint-baseline.xml b/add_to_app/android_view/android_view/app/lint-baseline.xml new file mode 100644 index 0000000..1809204 --- /dev/null +++ b/add_to_app/android_view/android_view/app/lint-baseline.xml @@ -0,0 +1,279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/add_to_app/android_view/android_view/app/proguard-rules.pro b/add_to_app/android_view/android_view/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/add_to_app/android_view/android_view/app/proguard-rules.pro @@ -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 \ No newline at end of file diff --git a/add_to_app/android_view/android_view/app/src/main/AndroidManifest.xml b/add_to_app/android_view/android_view/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..920589d --- /dev/null +++ b/add_to_app/android_view/android_view/app/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/add_to_app/android_view/android_view/app/src/main/java/dev/flutter/example/androidView/FlutterViewEngine.kt b/add_to_app/android_view/android_view/app/src/main/java/dev/flutter/example/androidView/FlutterViewEngine.kt new file mode 100644 index 0000000..e0c96a1 --- /dev/null +++ b/add_to_app/android_view/android_view/app/src/main/java/dev/flutter/example/androidView/FlutterViewEngine.kt @@ -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{ + 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, + 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!!; + } +} diff --git a/add_to_app/android_view/android_view/app/src/main/java/dev/flutter/example/androidView/ListAdapter.kt b/add_to_app/android_view/android_view/app/src/main/java/dev/flutter/example/androidView/ListAdapter.kt new file mode 100644 index 0000000..7a74ccf --- /dev/null +++ b/add_to_app/android_view/android_view/app/src/main/java/dev/flutter/example/androidView/ListAdapter.kt @@ -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() { + // 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(); + + 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) + } +} diff --git a/add_to_app/android_view/android_view/app/src/main/java/dev/flutter/example/androidView/MainActivity.kt b/add_to_app/android_view/android_view/app/src/main/java/dev/flutter/example/androidView/MainActivity.kt new file mode 100644 index 0000000..5367a60 --- /dev/null +++ b/add_to_app/android_view/android_view/app/src/main/java/dev/flutter/example/androidView/MainActivity.kt @@ -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( + 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, + 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() + } +} diff --git a/add_to_app/android_view/android_view/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/add_to_app/android_view/android_view/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/add_to_app/android_view/android_view/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/add_to_app/android_view/android_view/app/src/main/res/drawable/ic_dashboard_black_24dp.xml b/add_to_app/android_view/android_view/app/src/main/res/drawable/ic_dashboard_black_24dp.xml new file mode 100644 index 0000000..46fc8de --- /dev/null +++ b/add_to_app/android_view/android_view/app/src/main/res/drawable/ic_dashboard_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/add_to_app/android_view/android_view/app/src/main/res/drawable/ic_home_black_24dp.xml b/add_to_app/android_view/android_view/app/src/main/res/drawable/ic_home_black_24dp.xml new file mode 100644 index 0000000..f8bb0b5 --- /dev/null +++ b/add_to_app/android_view/android_view/app/src/main/res/drawable/ic_home_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/add_to_app/android_view/android_view/app/src/main/res/drawable/ic_launcher_background.xml b/add_to_app/android_view/android_view/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/add_to_app/android_view/android_view/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/add_to_app/android_view/android_view/app/src/main/res/drawable/ic_notifications_black_24dp.xml b/add_to_app/android_view/android_view/app/src/main/res/drawable/ic_notifications_black_24dp.xml new file mode 100644 index 0000000..78b75c3 --- /dev/null +++ b/add_to_app/android_view/android_view/app/src/main/res/drawable/ic_notifications_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/add_to_app/android_view/android_view/app/src/main/res/layout/activity_main.xml b/add_to_app/android_view/android_view/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..492a955 --- /dev/null +++ b/add_to_app/android_view/android_view/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/add_to_app/android_view/android_view/app/src/main/res/layout/android_card.xml b/add_to_app/android_view/android_view/app/src/main/res/layout/android_card.xml new file mode 100644 index 0000000..8007624 --- /dev/null +++ b/add_to_app/android_view/android_view/app/src/main/res/layout/android_card.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + diff --git a/add_to_app/android_view/android_view/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/add_to_app/android_view/android_view/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/add_to_app/android_view/android_view/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/add_to_app/android_view/android_view/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/add_to_app/android_view/android_view/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/add_to_app/android_view/android_view/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/add_to_app/android_view/android_view/app/src/main/res/mipmap-hdpi/ic_launcher.png b/add_to_app/android_view/android_view/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..a571e60 Binary files /dev/null and b/add_to_app/android_view/android_view/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/add_to_app/android_view/android_view/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/add_to_app/android_view/android_view/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..61da551 Binary files /dev/null and b/add_to_app/android_view/android_view/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/add_to_app/android_view/android_view/app/src/main/res/mipmap-mdpi/ic_launcher.png b/add_to_app/android_view/android_view/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c41dd28 Binary files /dev/null and b/add_to_app/android_view/android_view/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/add_to_app/android_view/android_view/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/add_to_app/android_view/android_view/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..db5080a Binary files /dev/null and b/add_to_app/android_view/android_view/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/add_to_app/android_view/android_view/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/add_to_app/android_view/android_view/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..6dba46d Binary files /dev/null and b/add_to_app/android_view/android_view/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/add_to_app/android_view/android_view/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/add_to_app/android_view/android_view/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..da31a87 Binary files /dev/null and b/add_to_app/android_view/android_view/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/add_to_app/android_view/android_view/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/add_to_app/android_view/android_view/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..15ac681 Binary files /dev/null and b/add_to_app/android_view/android_view/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/add_to_app/android_view/android_view/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/add_to_app/android_view/android_view/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..b216f2d Binary files /dev/null and b/add_to_app/android_view/android_view/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/add_to_app/android_view/android_view/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/add_to_app/android_view/android_view/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..f25a419 Binary files /dev/null and b/add_to_app/android_view/android_view/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/add_to_app/android_view/android_view/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/add_to_app/android_view/android_view/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..e96783c Binary files /dev/null and b/add_to_app/android_view/android_view/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/add_to_app/android_view/android_view/app/src/main/res/values-night/themes.xml b/add_to_app/android_view/android_view/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..82b8203 --- /dev/null +++ b/add_to_app/android_view/android_view/app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/add_to_app/android_view/android_view/app/src/main/res/values/colors.xml b/add_to_app/android_view/android_view/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/add_to_app/android_view/android_view/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/add_to_app/android_view/android_view/app/src/main/res/values/dimens.xml b/add_to_app/android_view/android_view/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..e00c2dd --- /dev/null +++ b/add_to_app/android_view/android_view/app/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + \ No newline at end of file diff --git a/add_to_app/android_view/android_view/app/src/main/res/values/strings.xml b/add_to_app/android_view/android_view/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..1e74146 --- /dev/null +++ b/add_to_app/android_view/android_view/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Flutter View Integration + \ No newline at end of file diff --git a/add_to_app/android_view/android_view/app/src/main/res/values/themes.xml b/add_to_app/android_view/android_view/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..2d692a1 --- /dev/null +++ b/add_to_app/android_view/android_view/app/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/add_to_app/android_view/android_view/build.gradle b/add_to_app/android_view/android_view/build.gradle new file mode 100644 index 0000000..f60b7c4 --- /dev/null +++ b/add_to_app/android_view/android_view/build.gradle @@ -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 +} diff --git a/add_to_app/android_view/android_view/gradle.properties b/add_to_app/android_view/android_view/gradle.properties new file mode 100644 index 0000000..c8ce6fb --- /dev/null +++ b/add_to_app/android_view/android_view/gradle.properties @@ -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 \ No newline at end of file diff --git a/add_to_app/android_view/android_view/gradle/wrapper/gradle-wrapper.properties b/add_to_app/android_view/android_view/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..d7952dc --- /dev/null +++ b/add_to_app/android_view/android_view/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/add_to_app/android_view/android_view/gradlew b/add_to_app/android_view/android_view/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/add_to_app/android_view/android_view/gradlew @@ -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" "$@" diff --git a/add_to_app/android_view/android_view/gradlew.bat b/add_to_app/android_view/android_view/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/add_to_app/android_view/android_view/gradlew.bat @@ -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 diff --git a/add_to_app/android_view/android_view/settings.gradle b/add_to_app/android_view/android_view/settings.gradle new file mode 100644 index 0000000..4f858a9 --- /dev/null +++ b/add_to_app/android_view/android_view/settings.gradle @@ -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' +)) diff --git a/add_to_app/android_view/content_sizing_android_view/.gitignore b/add_to_app/android_view/content_sizing_android_view/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/.gitignore @@ -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 diff --git a/add_to_app/android_view/content_sizing_android_view/README.md b/add_to_app/android_view/content_sizing_android_view/README.md new file mode 100644 index 0000000..3cf9002 --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/README.md @@ -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). diff --git a/add_to_app/android_view/content_sizing_android_view/app/.gitignore b/add_to_app/android_view/content_sizing_android_view/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/add_to_app/android_view/content_sizing_android_view/app/build.gradle b/add_to_app/android_view/content_sizing_android_view/app/build.gradle new file mode 100644 index 0000000..0797ed5 --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/build.gradle @@ -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' +} diff --git a/add_to_app/android_view/content_sizing_android_view/app/lint-baseline.xml b/add_to_app/android_view/content_sizing_android_view/app/lint-baseline.xml new file mode 100644 index 0000000..1809204 --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/lint-baseline.xml @@ -0,0 +1,279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/add_to_app/android_view/content_sizing_android_view/app/proguard-rules.pro b/add_to_app/android_view/content_sizing_android_view/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/proguard-rules.pro @@ -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 \ No newline at end of file diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/AndroidManifest.xml b/add_to_app/android_view/content_sizing_android_view/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..16116cb --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/java/dev/flutter/example/androidView/FlutterViewEngine.kt b/add_to_app/android_view/content_sizing_android_view/app/src/main/java/dev/flutter/example/androidView/FlutterViewEngine.kt new file mode 100644 index 0000000..e0c96a1 --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/src/main/java/dev/flutter/example/androidView/FlutterViewEngine.kt @@ -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{ + 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, + 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!!; + } +} diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/java/dev/flutter/example/androidView/ListAdapter.kt b/add_to_app/android_view/content_sizing_android_view/app/src/main/java/dev/flutter/example/androidView/ListAdapter.kt new file mode 100644 index 0000000..3033298 --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/src/main/java/dev/flutter/example/androidView/ListAdapter.kt @@ -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() { + // 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(); + + 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) + } +} diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/java/dev/flutter/example/androidView/MainActivity.kt b/add_to_app/android_view/content_sizing_android_view/app/src/main/java/dev/flutter/example/androidView/MainActivity.kt new file mode 100644 index 0000000..0993288 --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/src/main/java/dev/flutter/example/androidView/MainActivity.kt @@ -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( + 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, + 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() + } +} diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/drawable/ic_dashboard_black_24dp.xml b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/drawable/ic_dashboard_black_24dp.xml new file mode 100644 index 0000000..46fc8de --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/drawable/ic_dashboard_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/drawable/ic_home_black_24dp.xml b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/drawable/ic_home_black_24dp.xml new file mode 100644 index 0000000..f8bb0b5 --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/drawable/ic_home_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/drawable/ic_launcher_background.xml b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/drawable/ic_notifications_black_24dp.xml b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/drawable/ic_notifications_black_24dp.xml new file mode 100644 index 0000000..78b75c3 --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/drawable/ic_notifications_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/layout/activity_main.xml b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..492a955 --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/layout/android_card.xml b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/layout/android_card.xml new file mode 100644 index 0000000..3bf306b --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/layout/android_card.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-hdpi/ic_launcher.png b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..a571e60 Binary files /dev/null and b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..61da551 Binary files /dev/null and b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-mdpi/ic_launcher.png b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c41dd28 Binary files /dev/null and b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..db5080a Binary files /dev/null and b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..6dba46d Binary files /dev/null and b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..da31a87 Binary files /dev/null and b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..15ac681 Binary files /dev/null and b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..b216f2d Binary files /dev/null and b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..f25a419 Binary files /dev/null and b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..e96783c Binary files /dev/null and b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/values-night/themes.xml b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..82b8203 --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/values/colors.xml b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/values/dimens.xml b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..e00c2dd --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + \ No newline at end of file diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/values/strings.xml b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..97b2137 --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Flutter View Content Sizing Integration + \ No newline at end of file diff --git a/add_to_app/android_view/content_sizing_android_view/app/src/main/res/values/themes.xml b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..2d692a1 --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/app/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/add_to_app/android_view/content_sizing_android_view/build.gradle b/add_to_app/android_view/content_sizing_android_view/build.gradle new file mode 100644 index 0000000..f60b7c4 --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/build.gradle @@ -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 +} diff --git a/add_to_app/android_view/content_sizing_android_view/gradle.properties b/add_to_app/android_view/content_sizing_android_view/gradle.properties new file mode 100644 index 0000000..c8ce6fb --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/gradle.properties @@ -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 \ No newline at end of file diff --git a/add_to_app/android_view/content_sizing_android_view/gradle/wrapper/gradle-wrapper.properties b/add_to_app/android_view/content_sizing_android_view/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..d7952dc --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/add_to_app/android_view/content_sizing_android_view/gradlew b/add_to_app/android_view/content_sizing_android_view/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/gradlew @@ -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" "$@" diff --git a/add_to_app/android_view/content_sizing_android_view/gradlew.bat b/add_to_app/android_view/content_sizing_android_view/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/gradlew.bat @@ -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 diff --git a/add_to_app/android_view/content_sizing_android_view/settings.gradle b/add_to_app/android_view/content_sizing_android_view/settings.gradle new file mode 100644 index 0000000..62f8e65 --- /dev/null +++ b/add_to_app/android_view/content_sizing_android_view/settings.gradle @@ -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_content_sizing_android_view/.android/include_flutter.groovy' +)) diff --git a/add_to_app/android_view/flutter_module_using_plugin_android_view/.gitignore b/add_to_app/android_view/flutter_module_using_plugin_android_view/.gitignore new file mode 100644 index 0000000..86f4691 --- /dev/null +++ b/add_to_app/android_view/flutter_module_using_plugin_android_view/.gitignore @@ -0,0 +1,42 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +*.swp +profile + +DerivedData/ + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +build/ +.android/ +.ios/ +.flutter-plugins +.flutter-plugins-dependencies diff --git a/add_to_app/android_view/flutter_module_using_plugin_android_view/.metadata b/add_to_app/android_view/flutter_module_using_plugin_android_view/.metadata new file mode 100644 index 0000000..194fb3c --- /dev/null +++ b/add_to_app/android_view/flutter_module_using_plugin_android_view/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 532a8fed41a4f6595965f02f3edf9666ba5ebf44 + channel: master + +project_type: module diff --git a/add_to_app/android_view/flutter_module_using_plugin_android_view/README.md b/add_to_app/android_view/flutter_module_using_plugin_android_view/README.md new file mode 100644 index 0000000..01c484f --- /dev/null +++ b/add_to_app/android_view/flutter_module_using_plugin_android_view/README.md @@ -0,0 +1,14 @@ +# flutter_module_using_plugin + +An example Flutter module that uses a native plugin, intended for use in the +Flutter add-to-app samples. For more information on how to use it, see the +[README.md](../README.md) parent directory. + +## 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). diff --git a/add_to_app/android_view/flutter_module_using_plugin_android_view/analysis_options.yaml b/add_to_app/android_view/flutter_module_using_plugin_android_view/analysis_options.yaml new file mode 100644 index 0000000..e2badd7 --- /dev/null +++ b/add_to_app/android_view/flutter_module_using_plugin_android_view/analysis_options.yaml @@ -0,0 +1 @@ +include: ../../../analysis_options.yaml diff --git a/add_to_app/android_view/flutter_module_using_plugin_android_view/lib/cell.dart b/add_to_app/android_view/flutter_module_using_plugin_android_view/lib/cell.dart new file mode 100644 index 0000000..a799a5f --- /dev/null +++ b/add_to_app/android_view/flutter_module_using_plugin_android_view/lib/cell.dart @@ -0,0 +1,154 @@ +// 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. + +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:sensors_plus/sensors_plus.dart'; + +// This is on alternate entrypoint for this module to display Flutter UI in +// a (multi-)view integration scenario. +void main() { + runApp(const Cell()); +} + +class Cell extends StatefulWidget { + const Cell({super.key}); + + @override + State createState() => _CellState(); +} + +class _CellState extends State with WidgetsBindingObserver { + static const double gravity = 9.81; + static final AccelerometerEvent defaultPosition = AccelerometerEvent( + 0, + 0, + 0, + DateTime.now(), + ); + + int cellNumber = 0; + Random? _random; + AppLifecycleState? appLifecycleState; + + @override + void initState() { + const channel = MethodChannel('dev.flutter.example/cell'); + channel.setMethodCallHandler((call) async { + if (call.method == 'setCellNumber') { + setState(() { + cellNumber = call.arguments as int; + _random = Random(cellNumber); + }); + } + }); + // Keep track of what the current platform lifecycle state is. + WidgetsBinding.instance.addObserver(this); + super.initState(); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + setState(() { + appLifecycleState = state; + }); + } + + // Show a random bright color. + Color randomLightColor() { + _random ??= Random(cellNumber); + + return Color.fromARGB( + 255, + _random!.nextInt(50) + 205, + _random!.nextInt(50) + 205, + _random!.nextInt(50) + 205, + ); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + // The Flutter cells will be noticeably different (due to background color + // and the Flutter logo). The banner breaks immersion. + debugShowCheckedModeBanner: false, + home: Container( + color: Colors.white, + child: Builder( + builder: (context) { + return Card( + // Mimic the platform Material look. + margin: const EdgeInsets.symmetric( + horizontal: 36, + vertical: 24, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + elevation: 16, + color: randomLightColor(), + child: Stack( + children: [ + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + // Show a number provided by the platform based on + // the cell's index. + cellNumber.toString(), + style: Theme.of(context).textTheme.displaySmall, + ), + ], + ), + ), + Positioned( + left: 42, + top: 0, + bottom: 0, + child: Opacity( + opacity: 0.2, + child: StreamBuilder( + // Don't continuously rebuild for nothing when the + // cell isn't visible. + stream: appLifecycleState == AppLifecycleState.resumed + ? accelerometerEventStream() + : Stream.value(defaultPosition), + initialData: defaultPosition, + builder: (context, snapshot) { + return Transform( + // Figure out the phone's orientation relative + // to gravity's direction. Ignore the z vector. + transform: + Matrix4.rotationX( + snapshot.data!.y / gravity * pi / 2, + )..multiply( + Matrix4.rotationY( + snapshot.data!.x / gravity * pi / 2, + ), + ), + alignment: Alignment.center, + child: const FlutterLogo(size: 72), + ); + }, + ), + ), + ), + ], + ), + ); + }, + ), + ), + ); + } +} diff --git a/add_to_app/android_view/flutter_module_using_plugin_android_view/lib/main.dart b/add_to_app/android_view/flutter_module_using_plugin_android_view/lib/main.dart new file mode 100644 index 0000000..95d0874 --- /dev/null +++ b/add_to_app/android_view/flutter_module_using_plugin_android_view/lib/main.dart @@ -0,0 +1,180 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart' as launcher; + +import 'cell.dart'; + +/// The entrypoint for the flutter module. +void main() { + // This call ensures the Flutter binding has been set up before creating the + // MethodChannel-based model. + WidgetsFlutterBinding.ensureInitialized(); + + final model = CounterModel(); + + runApp(ChangeNotifierProvider.value(value: model, child: const MyApp())); +} + +/// This is on alternate entrypoint for this module to display Flutter UI in +/// a (multi-)view integration scenario. +// This is unfortunately in this file due to +// https://github.com/flutter/flutter/issues/72630. +@pragma("vm:entry-point") +void showCell() { + runApp(const Cell()); +} + +/// A simple model that uses a [MethodChannel] as the source of truth for the +/// state of a counter. +/// +/// Rather than storing app state data within the Flutter module itself (where +/// the native portions of the app can't access it), this module passes messages +/// back to the containing app whenever it needs to increment or retrieve the +/// value of the counter. +class CounterModel extends ChangeNotifier { + CounterModel() { + _channel.setMethodCallHandler(_handleMessage); + _channel.invokeMethod('requestCounter'); + } + + final _channel = const MethodChannel('dev.flutter.example/counter'); + + int _count = 0; + + int get count => _count; + + void increment() { + _channel.invokeMethod('incrementCounter'); + } + + Future _handleMessage(MethodCall call) async { + if (call.method == 'reportCounter') { + _count = call.arguments as int; + notifyListeners(); + } + } +} + +/// The "app" displayed by this module. +/// +/// It offers two routes, one suitable for displaying as a full screen and +/// another designed to be part of a larger UI. +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Module Title', + theme: ThemeData(colorSchemeSeed: Colors.blue), + routes: { + '/': (context) => const FullScreenView(), + '/mini': (context) => const Contents(), + }, + ); + } +} + +/// Wraps [Contents] in a Material [Scaffold] so it looks correct when displayed +/// full-screen. +class FullScreenView extends StatelessWidget { + const FullScreenView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Full-screen Flutter with plugin')), + body: const Contents(showExit: true), + ); + } +} + +/// The actual content displayed by the module. +/// +/// This widget displays info about the state of a counter and how much room (in +/// logical pixels) it's been given. It also offers buttons to increment the +/// counter, opening the Flutter documentation via the url_launcher plugin, and +/// (optionally) close the Flutter view. +class Contents extends StatelessWidget { + final bool showExit; + + const Contents({this.showExit = false, super.key}); + + @override + Widget build(BuildContext context) { + final mediaInfo = MediaQuery.of(context); + + return SizedBox.expand( + child: Stack( + children: [ + Positioned.fill( + child: DecoratedBox( + decoration: BoxDecoration( + color: Theme.of(context).scaffoldBackgroundColor, + ), + ), + ), + const Positioned.fill( + child: Opacity( + opacity: .25, + child: FittedBox(fit: BoxFit.cover, child: FlutterLogo()), + ), + ), + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Window is ${mediaInfo.size.width.toStringAsFixed(1)} x ' + '${mediaInfo.size.height.toStringAsFixed(1)}', + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox(height: 16), + Consumer( + builder: (context, model, child) { + return Text( + 'Taps: ${model.count}', + style: Theme.of(context).textTheme.headlineSmall, + ); + }, + ), + const SizedBox(height: 16), + Consumer( + builder: (context, model, child) { + return FilledButton( + onPressed: () => model.increment(), + child: const Text('Tap me!'), + ); + }, + ), + FilledButton( + onPressed: () async { + // Use the url_launcher plugin to open the Flutter docs in + // a browser. + final url = Uri.parse('https://flutter.dev/docs'); + if (await launcher.canLaunchUrl(url)) { + await launcher.launchUrl(url); + } + }, + child: const Text('Open Flutter Docs'), + ), + if (showExit) ...[ + const SizedBox(height: 16), + FilledButton( + onPressed: () => SystemNavigator.pop(), + child: const Text('Exit this screen'), + ), + ], + ], + ), + ), + ], + ), + ); + } +} diff --git a/add_to_app/android_view/flutter_module_using_plugin_android_view/pubspec.yaml b/add_to_app/android_view/flutter_module_using_plugin_android_view/pubspec.yaml new file mode 100644 index 0000000..4f6067a --- /dev/null +++ b/add_to_app/android_view/flutter_module_using_plugin_android_view/pubspec.yaml @@ -0,0 +1,34 @@ +name: flutter_module_using_plugin_android_view +description: An example Flutter module that uses a plugin. +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.8.1-0 + +dependencies: + flutter: + sdk: flutter + provider: ^6.1.5 + url_launcher: ^6.3.2 + sensors_plus: ^6.1.1 + +dev_dependencies: + analysis_defaults: + path: ../../../analysis_defaults + flutter_test: + sdk: flutter + +flutter: + uses-material-design: true + + # This section identifies your Flutter project as a module meant for + # embedding in a native host app. These identifiers should _not_ ordinarily + # be changed after generation - they are used to ensure that the tooling can + # maintain consistency when adding or modifying assets and plugins. + # They also do not have any bearing on your native host application's + # identifiers, which may be completely independent or the same as these. + module: + androidX: true + androidPackage: dev.flutter.example.flutter_module_using_plugin + iosBundleIdentifier: dev.flutter.example.flutterModuleUsingPlugin diff --git a/add_to_app/android_view/flutter_module_using_plugin_android_view/test/widget_test.dart b/add_to_app/android_view/flutter_module_using_plugin_android_view/test/widget_test.dart new file mode 100644 index 0000000..f6cdf39 --- /dev/null +++ b/add_to_app/android_view/flutter_module_using_plugin_android_view/test/widget_test.dart @@ -0,0 +1,50 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_module_using_plugin_android_view/main.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; + +class MockCounterModel extends ChangeNotifier implements CounterModel { + int _count = 0; + + @override + int get count => _count; + + @override + void increment() { + _count++; + notifyListeners(); + } +} + +void main() { + testWidgets('MiniView smoke test', (tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: ChangeNotifierProvider.value( + value: MockCounterModel(), + child: const Contents(), + ), + ), + ); + + // Verify that our counter starts at 0. + expect(find.text('Taps: 0'), findsOneWidget); + expect(find.text('Taps: 1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.text('Tap me!')); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('Taps: 0'), findsNothing); + expect(find.text('Taps: 1'), findsOneWidget); + }); +} diff --git a/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/.gitignore b/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/.gitignore new file mode 100644 index 0000000..86f4691 --- /dev/null +++ b/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/.gitignore @@ -0,0 +1,42 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +*.swp +profile + +DerivedData/ + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +build/ +.android/ +.ios/ +.flutter-plugins +.flutter-plugins-dependencies diff --git a/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/.metadata b/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/.metadata new file mode 100644 index 0000000..194fb3c --- /dev/null +++ b/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 532a8fed41a4f6595965f02f3edf9666ba5ebf44 + channel: master + +project_type: module diff --git a/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/README.md b/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/README.md new file mode 100644 index 0000000..01c484f --- /dev/null +++ b/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/README.md @@ -0,0 +1,14 @@ +# flutter_module_using_plugin + +An example Flutter module that uses a native plugin, intended for use in the +Flutter add-to-app samples. For more information on how to use it, see the +[README.md](../README.md) parent directory. + +## 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). diff --git a/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/analysis_options.yaml b/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/analysis_options.yaml new file mode 100644 index 0000000..e2badd7 --- /dev/null +++ b/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/analysis_options.yaml @@ -0,0 +1 @@ +include: ../../../analysis_options.yaml diff --git a/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/lib/main.dart b/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/lib/main.dart new file mode 100644 index 0000000..0cd24a0 --- /dev/null +++ b/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/lib/main.dart @@ -0,0 +1,56 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +void main() { + runApp(const ResizeApp()); +} + +class ResizeApp extends StatefulWidget { + const ResizeApp({super.key}); + + @override + State createState() => _ResizeAppState(); +} + +class _ResizeAppState extends State { + int _listSize = 1; + void _addToList() { + setState(() { + _listSize++; + }); + } + + @override + Widget build(BuildContext context) { + return Center( + heightFactor: 1, + child: Directionality( + textDirection: TextDirection.ltr, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (int i = 0; i < _listSize; i++) + Container( + color: HSVColor.fromAHSV(1, (10.0 * i), 1, 1).toColor(), + height: 50, + width: 200, + child: Center( + child: Text( + 'Flutter Widget $i', + style: const TextStyle(fontSize: 16, color: Colors.black), + ), + ), + ), + TextButton( + onPressed: _addToList, + child: Text('Listception!'), + ), + ], + ), + ), + ); + } +} diff --git a/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/pubspec.yaml b/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/pubspec.yaml new file mode 100644 index 0000000..4074a0a --- /dev/null +++ b/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/pubspec.yaml @@ -0,0 +1,34 @@ +name: flutter_module_using_plugin_content_sizing_android_view +description: An example Flutter module that uses a plugin. +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.8.1-0 + +dependencies: + flutter: + sdk: flutter + provider: ^6.1.5 + url_launcher: ^6.3.2 + sensors_plus: ^6.1.1 + +dev_dependencies: + analysis_defaults: + path: ../../../analysis_defaults + flutter_test: + sdk: flutter + +flutter: + uses-material-design: true + + # This section identifies your Flutter project as a module meant for + # embedding in a native host app. These identifiers should _not_ ordinarily + # be changed after generation - they are used to ensure that the tooling can + # maintain consistency when adding or modifying assets and plugins. + # They also do not have any bearing on your native host application's + # identifiers, which may be completely independent or the same as these. + module: + androidX: true + androidPackage: dev.flutter.example.flutter_module_using_plugin + iosBundleIdentifier: dev.flutter.example.flutterModuleUsingPlugin diff --git a/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/test/widget_test.dart b/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/test/widget_test.dart new file mode 100644 index 0000000..4c7ccce --- /dev/null +++ b/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/test/widget_test.dart @@ -0,0 +1,15 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class MockCounterModel extends ChangeNotifier {} + +void main() { + testWidgets('MiniView smoke test', (tester) async {}); +} diff --git a/add_to_app/books/README.md b/add_to_app/books/README.md new file mode 100644 index 0000000..1484b5a --- /dev/null +++ b/add_to_app/books/README.md @@ -0,0 +1,87 @@ +# 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. + +## Description + +These apps integrate the `flutter_books` module using the simpler build-together +project setup. They simulate a mock scenario where an existing book catalog +list app already exists. Flutter is used to implement an additional book details +page. + +* Similar to [`fullscreen`](../fullscreen). +* An existing books catalog app is already implemented in Kotlin and Swift. +* The platform-side app has existing middleware constraints that should also + be the middleware foundation for the additional Flutter screen. + * On Android, the Kotlin app already uses GSON and OkHttp for networking and + references the Google Books API as a data source. These same libraries also + underpin the data fetched and shown in the Flutter screen. + * On iOS, the Swift app does a similar fetch and parse of the Google Books API + using iOS's built-in libraries. +* The platform application interfaces with the Flutter book details page using + idiomatic platform API conventions rather than Flutter conventions. + * On Android, the Flutter activity receives the book to show via activity + intent and returns the edited book by setting the result intent on the + activity. No Flutter concepts are leaked into the consumer activity. + * On iOS, Storyboards are used to design the presentation of the books, just + as one might do in a full UIKit app. +* The [pigeon](https://pub.dev/packages/pigeon) plugin is used to generate + interop APIs and data classes. The same `Book` model class is used within the + Kotlin/Swift program, the Dart program and in the interop between Kotlin/Swift + and Dart. No manual platform channel plumbing needed for interop. + * The `api.dart/java/mm` files generated from the + `flutter_module_books/pigeon/schema.dart` file are checked into source + control. Therefore `pigeon` is only a dev dependency with no runtime + requirements. + * If the `schema.dart` is modified, the generated classes can be updated with + + ```bash + dart run pigeon --input pigeon/schema.dart \ + --dart_out lib/api.dart \ + --objc_header_out ../ios_books/IosBooks/api.h \ + --objc_source_out ../ios_books/IosBooks/api.m \ + --objc_prefix BK \ + --java_out ../android_books/app/src/main/java/dev/flutter/example/books/Api.java \ + --java_package "dev.flutter.example.books" + ``` + + in the `flutter_module_books` directory. + +Once you've understood the basics of add-to-app with `android_fullscreen` and +`ios_fullscreen`, this is a good sample to demonstrate how to integrate Flutter +in a slightly more realistic setting with existing business logic. + +## 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_books/ + flutter pub get + + # For Android builds: + open -a "Android Studio" ../android_books # macOS only + # Or open the ../android_books folder in Android Studio for other platforms. + + # For iOS builds: + cd ../ios_books + pod install + open IosBooks.xcworkspace +``` + +## Requirements + +* Flutter +* Android Studio +* Cocoapods (iOS) + +## Questions/issues + +See [add_to_app/README.md](../README.md) for further help. diff --git a/add_to_app/books/android_books/.gitignore b/add_to_app/books/android_books/.gitignore new file mode 100644 index 0000000..603b140 --- /dev/null +++ b/add_to_app/books/android_books/.gitignore @@ -0,0 +1,14 @@ +*.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 diff --git a/add_to_app/books/android_books/README.md b/add_to_app/books/android_books/README.md new file mode 100644 index 0000000..5e1dd13 --- /dev/null +++ b/add_to_app/books/android_books/README.md @@ -0,0 +1,14 @@ +# android_books + +An example Android application used in the Flutter add-to-app samples. For more +information on how to use it, see the [README](../README.md) file located in the +[/add_to_app](/add_to_app) directory of this repo. + +## 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). diff --git a/add_to_app/books/android_books/app/.gitignore b/add_to_app/books/android_books/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/add_to_app/books/android_books/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/add_to_app/books/android_books/app/build.gradle b/add_to_app/books/android_books/app/build.gradle new file mode 100644 index 0000000..a932ff6 --- /dev/null +++ b/add_to_app/books/android_books/app/build.gradle @@ -0,0 +1,53 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 31 + buildToolsVersion "29.0.3" + + defaultConfig { + applicationId "dev.flutter.example.books" + minSdkVersion 21 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + profile { + initWith debug + } + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + signingConfig debug.signingConfig + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation fileTree(dir: "libs", include: ["*.jar"]) + implementation "com.squareup.okhttp3:okhttp:4.7.2" + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'androidx.core:core-ktx:1.3.0' + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation "androidx.activity:activity-ktx:1.1.0" + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'com.google.android.material:material:1.1.0' + implementation 'com.google.code.gson:gson:2.8.6' + implementation project(path: ':flutter') + testImplementation 'junit:junit:4.13' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + +} \ No newline at end of file diff --git a/add_to_app/books/android_books/app/proguard-rules.pro b/add_to_app/books/android_books/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/add_to_app/books/android_books/app/proguard-rules.pro @@ -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 \ No newline at end of file diff --git a/add_to_app/books/android_books/app/src/androidTest/java/dev/flutter/example/books/ExampleInstrumentedTest.kt b/add_to_app/books/android_books/app/src/androidTest/java/dev/flutter/example/books/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..3648083 --- /dev/null +++ b/add_to_app/books/android_books/app/src/androidTest/java/dev/flutter/example/books/ExampleInstrumentedTest.kt @@ -0,0 +1,27 @@ +package dev.flutter.example.books + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class MainActivityTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("dev.flutter.example.books", appContext.packageName) + } + + // The app should be hermetic (with offline books JSON) before adding + // more tests. +} diff --git a/add_to_app/books/android_books/app/src/main/AndroidManifest.xml b/add_to_app/books/android_books/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b15f0cc --- /dev/null +++ b/add_to_app/books/android_books/app/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/add_to_app/books/android_books/app/src/main/java/dev/flutter/example/books/Api.java b/add_to_app/books/android_books/app/src/main/java/dev/flutter/example/books/Api.java new file mode 100644 index 0000000..7293e7c --- /dev/null +++ b/add_to_app/books/android_books/app/src/main/java/dev/flutter/example/books/Api.java @@ -0,0 +1,252 @@ +// Autogenerated from Pigeon (v1.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +package dev.flutter.example.books; + +import io.flutter.plugin.common.BasicMessageChannel; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MessageCodec; +import io.flutter.plugin.common.StandardMessageCodec; +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Generated class from Pigeon. */ +@SuppressWarnings( + {"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"}) +public class Api { + + /** Generated class from Pigeon that represents data sent in messages. */ + public static class Book { + private String title; + public String getTitle() { return title; } + public void setTitle(String setterArg) { this.title = setterArg; } + + private String subtitle; + public String getSubtitle() { return subtitle; } + public void setSubtitle(String setterArg) { this.subtitle = setterArg; } + + private String author; + public String getAuthor() { return author; } + public void setAuthor(String setterArg) { this.author = setterArg; } + + private String summary; + public String getSummary() { return summary; } + public void setSummary(String setterArg) { this.summary = setterArg; } + + private String publishDate; + public String getPublishDate() { return publishDate; } + public void setPublishDate(String setterArg) { + this.publishDate = setterArg; + } + + private Long pageCount; + public Long getPageCount() { return pageCount; } + public void setPageCount(Long setterArg) { this.pageCount = setterArg; } + + private Thumbnail thumbnail; + public Thumbnail getThumbnail() { return thumbnail; } + public void setThumbnail(Thumbnail setterArg) { + this.thumbnail = setterArg; + } + + Map toMap() { + Map toMapResult = new HashMap<>(); + toMapResult.put("title", title); + toMapResult.put("subtitle", subtitle); + toMapResult.put("author", author); + toMapResult.put("summary", summary); + toMapResult.put("publishDate", publishDate); + toMapResult.put("pageCount", pageCount); + toMapResult.put("thumbnail", + (thumbnail == null) ? null : thumbnail.toMap()); + return toMapResult; + } + static Book fromMap(Map map) { + Book fromMapResult = new Book(); + Object title = map.get("title"); + fromMapResult.title = (String)title; + Object subtitle = map.get("subtitle"); + fromMapResult.subtitle = (String)subtitle; + Object author = map.get("author"); + fromMapResult.author = (String)author; + Object summary = map.get("summary"); + fromMapResult.summary = (String)summary; + Object publishDate = map.get("publishDate"); + fromMapResult.publishDate = (String)publishDate; + Object pageCount = map.get("pageCount"); + fromMapResult.pageCount = + (pageCount == null) + ? null + : ((pageCount instanceof Integer) ? (Integer)pageCount + : (Long)pageCount); + Object thumbnail = map.get("thumbnail"); + fromMapResult.thumbnail = Thumbnail.fromMap((Map)thumbnail); + return fromMapResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static class Thumbnail { + private String url; + public String getUrl() { return url; } + public void setUrl(String setterArg) { this.url = setterArg; } + + Map toMap() { + Map toMapResult = new HashMap<>(); + toMapResult.put("url", url); + return toMapResult; + } + static Thumbnail fromMap(Map map) { + Thumbnail fromMapResult = new Thumbnail(); + Object url = map.get("url"); + fromMapResult.url = (String)url; + return fromMapResult; + } + } + private static class FlutterBookApiCodec extends StandardMessageCodec { + public static final FlutterBookApiCodec INSTANCE = + new FlutterBookApiCodec(); + private FlutterBookApiCodec() {} + @Override + protected Object readValueOfType(byte type, ByteBuffer buffer) { + switch (type) { + case (byte)128: + return Book.fromMap((Map)readValue(buffer)); + + default: + return super.readValueOfType(type, buffer); + } + } + @Override + protected void writeValue(ByteArrayOutputStream stream, Object value) { + if (value instanceof Book) { + stream.write(128); + writeValue(stream, ((Book)value).toMap()); + } else { + super.writeValue(stream, value); + } + } + } + + /** + * Generated class from Pigeon that represents Flutter messages that can be + * called from Java. + */ + public static class FlutterBookApi { + private final BinaryMessenger binaryMessenger; + public FlutterBookApi(BinaryMessenger argBinaryMessenger) { + this.binaryMessenger = argBinaryMessenger; + } + public interface Reply { + void reply(T reply); + } + static MessageCodec getCodec() { + return FlutterBookApiCodec.INSTANCE; + } + + public void displayBookDetails(Book bookArg, Reply callback) { + BasicMessageChannel channel = new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.FlutterBookApi.displayBookDetails", getCodec()); + channel.send(new ArrayList(Arrays.asList(bookArg)), + channelReply -> { callback.reply(null); }); + } + } + private static class HostBookApiCodec extends StandardMessageCodec { + public static final HostBookApiCodec INSTANCE = new HostBookApiCodec(); + private HostBookApiCodec() {} + @Override + protected Object readValueOfType(byte type, ByteBuffer buffer) { + switch (type) { + case (byte)128: + return Book.fromMap((Map)readValue(buffer)); + + default: + return super.readValueOfType(type, buffer); + } + } + @Override + protected void writeValue(ByteArrayOutputStream stream, Object value) { + if (value instanceof Book) { + stream.write(128); + writeValue(stream, ((Book)value).toMap()); + } else { + super.writeValue(stream, value); + } + } + } + + /** + * Generated interface from Pigeon that represents a handler of messages from + * Flutter. + */ + public interface HostBookApi { + void cancel(); + void finishEditingBook(Book book); + + /** The codec used by HostBookApi. */ + static MessageCodec getCodec() { return HostBookApiCodec.INSTANCE; } + + /** + * Sets up an instance of `HostBookApi` to handle messages through the + * `binaryMessenger`. + */ + static void setup(BinaryMessenger binaryMessenger, HostBookApi api) { + { + BasicMessageChannel channel = new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.HostBookApi.cancel", + getCodec()); + if (api != null) { + channel.setMessageHandler((message, reply) -> { + Map wrapped = new HashMap<>(); + try { + api.cancel(); + wrapped.put("result", null); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.HostBookApi.finishEditingBook", + getCodec()); + if (api != null) { + channel.setMessageHandler((message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList)message; + Book bookArg = (Book)args.get(0); + if (bookArg == null) { + throw new NullPointerException("bookArg unexpectedly null."); + } + api.finishEditingBook(bookArg); + wrapped.put("result", null); + } catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } + private static Map wrapError(Throwable exception) { + Map errorMap = new HashMap<>(); + errorMap.put("message", exception.toString()); + errorMap.put("code", exception.getClass().getSimpleName()); + errorMap.put("details", null); + return errorMap; + } +} diff --git a/add_to_app/books/android_books/app/src/main/java/dev/flutter/example/books/BookApplication.kt b/add_to_app/books/android_books/app/src/main/java/dev/flutter/example/books/BookApplication.kt new file mode 100644 index 0000000..11941b0 --- /dev/null +++ b/add_to_app/books/android_books/app/src/main/java/dev/flutter/example/books/BookApplication.kt @@ -0,0 +1,28 @@ +// Copyright 2020 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.books + +import android.app.Application +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.embedding.engine.FlutterEngineCache +import io.flutter.embedding.engine.dart.DartExecutor + +class BookApplication: Application() { + companion object { + const val ENGINE_ID = "book_engine" + } + + private lateinit var flutterEngine: FlutterEngine + + override fun onCreate() { + super.onCreate() + // This application reuses a single FlutterEngine instance throughout. + // Create the FlutterEngine on application start. + flutterEngine = FlutterEngine(this).apply{ + dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault()) + } + FlutterEngineCache.getInstance().put(ENGINE_ID, flutterEngine) + } +} diff --git a/add_to_app/books/android_books/app/src/main/java/dev/flutter/example/books/FlutterBookActivity.kt b/add_to_app/books/android_books/app/src/main/java/dev/flutter/example/books/FlutterBookActivity.kt new file mode 100644 index 0000000..2089c73 --- /dev/null +++ b/add_to_app/books/android_books/app/src/main/java/dev/flutter/example/books/FlutterBookActivity.kt @@ -0,0 +1,101 @@ +// Copyright 2020 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.books + +import android.app.Activity +import android.content.Context +import android.content.Intent +import dev.flutter.example.books.BookApplication.Companion.ENGINE_ID +import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import java.util.HashMap + +/** + * This {@link FlutterActivity} class repackages Kotlin-Dart interop using the Pigeon IPC mechanism. + * It repackages Flutter/Dart-side functionalities in standard Android API style, passing + * arguments in and out of the activity using 'startActivityForResult' intents and + * 'onActivityResult' intents. + */ +class FlutterBookActivity: FlutterActivity() { + companion object { + const val EXTRA_BOOK = "book" + + /** + * Static intent factory to start {@link FlutterBookActivity} with the singleton + * {@link FlutterEngine} the application started. + * + * The activity launched from this intent shows the details of the {@link Api.Book} + * supplied. + */ + fun withBook(context: Context, book: Api.Book): Intent { + // In a more realistic app, there should be some dependency injection mechanism to + // determine which engine to use. + return CachedEngineBookIntentBuilder(ENGINE_ID) + .build(context) + .putExtra( + // The Pigeon data class is useful not only between Kotlin/Java and Dart + // but also within Kotlin/Java where activities must communicate with + // each other via serializable data. The Pigeon data class is a + // serializable class by definition. + EXTRA_BOOK, + // TODO(gaaclarke): the Pigeon generated data class should just implement + // Serializable so we won't need 'toMap()' here + // https://github.com/flutter/flutter/issues/58909 + HashMap(book.toMap()) + ) + } + + /** + * A static helper method to parse a result intent from this activity into a {@link Book}. + * + * @param resultIntent an {@link Intent} that must be the data intent returned by this + * activity's {@code onActivityResult}. + */ + fun getBookFromResultIntent(resultIntent: Intent): Api.Book { + return Api.Book.fromMap((resultIntent.getSerializableExtra(FlutterBookActivity.EXTRA_BOOK) as HashMap)); + } + } + + // Intent builder class to build a FlutterBookActivity instance instead of the default FlutterActivity. + class CachedEngineBookIntentBuilder(engineId: String): CachedEngineIntentBuilder(FlutterBookActivity::class.java, engineId) { } + + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + // Called shortly after the activity is created, when the activity is bound to a + // FlutterEngine responsible for rendering the Flutter activity's content. + super.configureFlutterEngine(flutterEngine) + + // The book to give to Flutter is passed in from the MainActivity via this activity's + // source intent getter. The intent contains the book serialized as on extra. + val bookToShow = Api.Book.fromMap(intent.getSerializableExtra(EXTRA_BOOK) as HashMap) + + // Register the HostBookApiHandler callback class to get results from Flutter. + Api.HostBookApi.setup(flutterEngine.dartExecutor, HostBookApiHandler()) + + // Send in the book instance to Flutter. + Api.FlutterBookApi(flutterEngine.dartExecutor).displayBookDetails(bookToShow) { + // We don't care about the callback + } + } + + // This {@link Api.HostBookApi} subclass will be called by Pigeon when the corresponding + // APIs are invoked on the Dart side. + inner class HostBookApiHandler: Api.HostBookApi { + override fun cancel() { + // Flutter called cancel. Finish the activity with a cancel result. + setResult(Activity.RESULT_CANCELED) + finish() + } + + override fun finishEditingBook(book: Api.Book?) { + if (book == null) { + throw IllegalArgumentException("finishedEditingBook cannot be called with a null argument") + } + // Flutter returned an edited book instance. Return it to the MainActivity via the + // standard Android Activity set result mechanism. + setResult(Activity.RESULT_OK, Intent().putExtra(EXTRA_BOOK, HashMap(book.toMap()))) + finish() + } + } +} diff --git a/add_to_app/books/android_books/app/src/main/java/dev/flutter/example/books/MainActivity.kt b/add_to_app/books/android_books/app/src/main/java/dev/flutter/example/books/MainActivity.kt new file mode 100644 index 0000000..c81526b --- /dev/null +++ b/add_to_app/books/android_books/app/src/main/java/dev/flutter/example/books/MainActivity.kt @@ -0,0 +1,169 @@ +// Copyright 2020 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.books + +import android.app.Activity +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.view.View +import android.widget.LinearLayout +import android.widget.ProgressBar +import android.widget.TextView +import com.google.android.material.button.MaterialButton +import com.google.gson.GsonBuilder +import com.google.gson.JsonParser +import okhttp3.Call +import okhttp3.Callback +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import java.io.IOException +import java.lang.Exception +import java.lang.RuntimeException + +class MainActivity : AppCompatActivity() { + companion object { + const val BOOKS_QUERY = "https://www.googleapis.com/books/v1/volumes?q=greenwood+tulsa&maxResults=15" + } + + private lateinit var books: MutableList + private lateinit var list: LinearLayout + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + list = findViewById(R.id.list) + + // OkHttp is arbitrarily chosen here to represent an existing middleware constraint that's + // already present in an existing application's infrastructure. + val httpClient = OkHttpClient() + val bookRequest = Request.Builder() + // Retrieve data from Google Books API (arbitrarily chosen). This represents existing + // data sources that an existing application is already interfacing with. + .url(BOOKS_QUERY) + .build() + + httpClient.newCall(bookRequest).enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + throw e + } + + override fun onResponse(call: Call, response: Response) { + response.use { + if (!response.isSuccessful) throw IOException("Unexpected code $response") + books = parseGoogleBooksJsonToBooks(response.body!!.string()) + + val spinner = findViewById(R.id.spinner) + + runOnUiThread { + // Showed a spinner while network call is in progress. Remove it when + // response is received. + spinner.visibility = View.GONE + populateBookCards() + } + } + } + }) + } + + // Take a top level Google Books query's response JSON and create a list of Book Pigeon data + // classes that will be used both as a model here on the Kotlin side and also in the IPCs + // to Dart. + private fun parseGoogleBooksJsonToBooks(jsonBody: String): MutableList { + // Here we're arbitrarily using GSON to represent another existing middleware constraint + // that already exists in your existing application's infrastructure. + val jsonBooks = JsonParser.parseString(jsonBody).asJsonObject.getAsJsonArray("items") + val books = mutableListOf() + for (jsonBook in jsonBooks.map { it.asJsonObject }) { + try { + // Here we're using GSON to populate a Pigeon data class directly. The Pigeon data + // class can be used not just as part of your IPC API's signature but also as a + // normal data class in your existing application. + // + // We could either push the Pigeon data class usage higher into the existing GSON + // "middleware" or lower, closer to the IPC. + val book = Api.Book() + val volumeInfoJson = jsonBook.getAsJsonObject("volumeInfo") + book.title = volumeInfoJson.get("title").asString + book.subtitle = volumeInfoJson.get("subtitle")?.asString + // Sorry co-authors, we're trying to keep this simple. + book.author = volumeInfoJson.getAsJsonArray("authors")[0].asString + book.summary = volumeInfoJson.get("description").asString + book.publishDate = volumeInfoJson.get("publishedDate").asString + book.pageCount = volumeInfoJson.get("pageCount").asLong + val thumbnail = Api.Thumbnail() + thumbnail.url = volumeInfoJson.getAsJsonObject("imageLinks").get("thumbnail").asString + book.thumbnail = thumbnail + books.add(book) + } catch (e: Exception) { + println("Failed to parse book:") + println(GsonBuilder().setPrettyPrinting().create().toJson(jsonBook)) + println("Parsing error:") + println(e) + } + } + return books + } + + // Given a populated books list, create a Material Design card in a scroll view for each book. + private fun populateBookCards() { + for ((index, book) in books.withIndex()) { + val card = layoutInflater.inflate(R.layout.book_card, null) + updateCardWithBook(card, book) + card.findViewById(R.id.edit).setOnClickListener { + // When the edit button is clicked in a book's card, launch a Flutter activity + // showing the details of the book. + startActivityForResult( + // We're using our own 'FlutterActivity' subclass which wraps Pigeon API usages + // into an idiomatic Android activity interface with intent extras as input and + // with activity 'setResult' as output. + // + // This lets activity-level feature developers abstract their Flutter usage + // and present a standard Android API to their upstream application developers. + // + // No Flutter-specific concepts are leaked outside the Flutter activity itself + // into the consuming class. + FlutterBookActivity + // Re-read from the 'books' list rather than just capturing the iterated + // 'book' instance since we change it when Dart updates it in onActivityResult. + .withBook(this, books[index]), + // The index lets us know which book we're returning the result for when we + // return from the Flutter activity. + index) + } + list.addView(card) + } + } + + // Given a Material Design card and a book, update the card content to reflect the book model. + private fun updateCardWithBook(card: View, book: Api.Book) { + card.findViewById(R.id.title).text = book.title + card.findViewById(R.id.subtitle).text = book.subtitle + card.findViewById(R.id.author).text = resources.getString(R.string.author_prefix, book.author) + } + + // Callback when the Flutter activity started with 'startActivityForResult' above returns. + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + + // The Flutter activity may cancel the edit. If so, don't update anything. + if (resultCode == Activity.RESULT_OK) { + if (data == null) { + throw RuntimeException("The FlutterBookActivity returning RESULT_OK should always have a return data intent") + } + + // If the book was edited in Flutter, the Flutter activity finishes and returns an + // activity result in an intent (the 'data' argument). The intent has an extra which is + // the edited book in serialized form. + val returnedBook = FlutterBookActivity.getBookFromResultIntent(data) + // Update our book model list. + books[requestCode] = returnedBook + + // Refresh the UI here on the Kotlin side. + updateCardWithBook(list.getChildAt(requestCode), returnedBook) + } + } +} diff --git a/add_to_app/books/android_books/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/add_to_app/books/android_books/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/add_to_app/books/android_books/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/add_to_app/books/android_books/app/src/main/res/drawable/ic_launcher_background.xml b/add_to_app/books/android_books/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/add_to_app/books/android_books/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/add_to_app/books/android_books/app/src/main/res/layout/activity_main.xml b/add_to_app/books/android_books/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..061bdec --- /dev/null +++ b/add_to_app/books/android_books/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/add_to_app/books/android_books/app/src/main/res/layout/book_card.xml b/add_to_app/books/android_books/app/src/main/res/layout/book_card.xml new file mode 100644 index 0000000..9ac1785 --- /dev/null +++ b/add_to_app/books/android_books/app/src/main/res/layout/book_card.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/add_to_app/books/android_books/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/add_to_app/books/android_books/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/add_to_app/books/android_books/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/add_to_app/books/android_books/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/add_to_app/books/android_books/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/add_to_app/books/android_books/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/add_to_app/books/android_books/app/src/main/res/mipmap-hdpi/ic_launcher.png b/add_to_app/books/android_books/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..a571e60 Binary files /dev/null and b/add_to_app/books/android_books/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/add_to_app/books/android_books/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/add_to_app/books/android_books/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..61da551 Binary files /dev/null and b/add_to_app/books/android_books/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/add_to_app/books/android_books/app/src/main/res/mipmap-mdpi/ic_launcher.png b/add_to_app/books/android_books/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c41dd28 Binary files /dev/null and b/add_to_app/books/android_books/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/add_to_app/books/android_books/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/add_to_app/books/android_books/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..db5080a Binary files /dev/null and b/add_to_app/books/android_books/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/add_to_app/books/android_books/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/add_to_app/books/android_books/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..6dba46d Binary files /dev/null and b/add_to_app/books/android_books/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/add_to_app/books/android_books/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/add_to_app/books/android_books/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..da31a87 Binary files /dev/null and b/add_to_app/books/android_books/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/add_to_app/books/android_books/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/add_to_app/books/android_books/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..15ac681 Binary files /dev/null and b/add_to_app/books/android_books/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/add_to_app/books/android_books/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/add_to_app/books/android_books/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..b216f2d Binary files /dev/null and b/add_to_app/books/android_books/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/add_to_app/books/android_books/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/add_to_app/books/android_books/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..f25a419 Binary files /dev/null and b/add_to_app/books/android_books/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/add_to_app/books/android_books/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/add_to_app/books/android_books/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..e96783c Binary files /dev/null and b/add_to_app/books/android_books/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/add_to_app/books/android_books/app/src/main/res/values/colors.xml b/add_to_app/books/android_books/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..4faecfa --- /dev/null +++ b/add_to_app/books/android_books/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #6200EE + #3700B3 + #03DAC5 + \ No newline at end of file diff --git a/add_to_app/books/android_books/app/src/main/res/values/strings.xml b/add_to_app/books/android_books/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..aca1b9f --- /dev/null +++ b/add_to_app/books/android_books/app/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + Books + Edit + By: %1$s + \ No newline at end of file diff --git a/add_to_app/books/android_books/app/src/main/res/values/styles.xml b/add_to_app/books/android_books/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..97150c7 --- /dev/null +++ b/add_to_app/books/android_books/app/src/main/res/values/styles.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/add_to_app/books/android_books/build.gradle b/add_to_app/books/android_books/build.gradle new file mode 100644 index 0000000..50ff451 --- /dev/null +++ b/add_to_app/books/android_books/build.gradle @@ -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.6.0" + repositories { + google() + mavenCentral() + } + dependencies { + classpath "com.android.tools.build:gradle:7.4.2" + 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() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/add_to_app/books/android_books/gradle.properties b/add_to_app/books/android_books/gradle.properties new file mode 100644 index 0000000..4d15d01 --- /dev/null +++ b/add_to_app/books/android_books/gradle.properties @@ -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 +# 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 +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official \ No newline at end of file diff --git a/add_to_app/books/android_books/gradle/wrapper/gradle-wrapper.properties b/add_to_app/books/android_books/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..774fae8 --- /dev/null +++ b/add_to_app/books/android_books/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/add_to_app/books/android_books/gradlew b/add_to_app/books/android_books/gradlew new file mode 100755 index 0000000..1b6c787 --- /dev/null +++ b/add_to_app/books/android_books/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright Β© 2015-2021 the original authors. +# +# 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions Β«$varΒ», Β«${var}Β», Β«${var:-default}Β», Β«${var+SET}Β», +# Β«${var#prefix}Β», Β«${var%suffix}Β», and Β«$( cmd )Β»; +# * compound commands having a testable exit status, especially Β«caseΒ»; +# * various built-in commands including Β«commandΒ», Β«setΒ», and Β«ulimitΒ». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${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='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# 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 ;; #( + MSYS* | 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" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/add_to_app/books/android_books/gradlew.bat b/add_to_app/books/android_books/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/add_to_app/books/android_books/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@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 Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@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="-Xmx64m" "-Xms64m" + +@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 execute + +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 execute + +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 + +: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 %* + +: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 diff --git a/add_to_app/books/android_books/settings.gradle b/add_to_app/books/android_books/settings.gradle new file mode 100644 index 0000000..c334c18 --- /dev/null +++ b/add_to_app/books/android_books/settings.gradle @@ -0,0 +1,7 @@ +include ':app' +setBinding(new Binding([gradle: this])) +evaluate(new File( + settingsDir, + '../flutter_module_books/.android/include_flutter.groovy' +)) +rootProject.name = "android_books" diff --git a/add_to_app/books/flutter_module_books/.gitignore b/add_to_app/books/flutter_module_books/.gitignore new file mode 100644 index 0000000..ff612b3 --- /dev/null +++ b/add_to_app/books/flutter_module_books/.gitignore @@ -0,0 +1,48 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +*.swp +profile + +DerivedData/ + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +build/ +.android/ +.ios/ +.flutter-plugins +.flutter-plugins-dependencies + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json diff --git a/add_to_app/books/flutter_module_books/.metadata b/add_to_app/books/flutter_module_books/.metadata new file mode 100644 index 0000000..37f7232 --- /dev/null +++ b/add_to_app/books/flutter_module_books/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: c0091b289e9966b6da99647b2f18dcb93de1f207 + channel: master + +project_type: module diff --git a/add_to_app/books/flutter_module_books/README.md b/add_to_app/books/flutter_module_books/README.md new file mode 100644 index 0000000..7cc246c --- /dev/null +++ b/add_to_app/books/flutter_module_books/README.md @@ -0,0 +1,53 @@ +# Books add-to-app sample + +This application simulates a mock scenario in which an existing app with +business logic and middleware already exists. It demonstrates how to add Flutter +to an app that has established patterns for these domains. For more information +on how to use it, see the [README.md](../README.md) parent directory. + +This application also utilizes the [Pigeon](https://pub.dev/packages/pigeon) +plugin to avoid manual platform channel wiring. Pigeon autogenerates the +platform channel code in Dart/Java/Objective-C to allow interop using higher +order functions and data classes instead of string-encoded methods and +serialized primitives. + +The Pigeon autogenerated code is checked-in and ready to use. If the schema +in `pigeon/schema.dart` is updated, the generated classes can also be re- +generated using: + +```shell +dart run pigeon --input pigeon/schema.dart \ + --dart_out lib/api.dart \ + --objc_header_out ../ios_books/IosBooks/api.h \ + --objc_source_out ../ios_books/IosBooks/api.m \ + --objc_prefix BK \ + --java_out ../android_books/app/src/main/java/dev/flutter/example/books/Api.java \ + --java_package "dev.flutter.example.books" +``` + +## Demonstrated concepts + +* An existing books catalog app is already implemented in Kotlin and Swift. +* The platform-side app has existing middleware constraints that should also + be the middleware foundation for the additional Flutter screen. + * On Android, the Kotlin app already uses GSON and OkHttp for networking and + references the Google Books API as a data source. These same libraries + also underpin the data fetched and shown in the Flutter screen. +* The platform application interfaces with the Flutter book details page using + idiomatic platform API conventions rather than Flutter conventions. + * On Android, the Flutter activity receives the book to show via activity + intent and returns the edited book by setting the result intent on the + activity. No Flutter concepts are leaked into the consumer activity. +* The [pigeon](https://pub.dev/packages/pigeon) plugin is used to generate + interop APIs and data classes. The same `Book` model class is used within the + Kotlin/Swift program, the Dart program and in the interop between Kotlin/Swift + and Dart. + +## More info + +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). diff --git a/add_to_app/books/flutter_module_books/analysis_options.yaml b/add_to_app/books/flutter_module_books/analysis_options.yaml new file mode 100644 index 0000000..e2badd7 --- /dev/null +++ b/add_to_app/books/flutter_module_books/analysis_options.yaml @@ -0,0 +1 @@ +include: ../../../analysis_options.yaml diff --git a/add_to_app/books/flutter_module_books/lib/api.dart b/add_to_app/books/flutter_module_books/lib/api.dart new file mode 100644 index 0000000..6076a41 --- /dev/null +++ b/add_to_app/books/flutter_module_books/lib/api.dart @@ -0,0 +1,191 @@ +// Autogenerated from Pigeon (v1.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name +// @dart = 2.12 +import 'dart:async'; +import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List; + +import 'package:flutter/services.dart'; + +class Book { + String? title; + String? subtitle; + String? author; + String? summary; + String? publishDate; + int? pageCount; + Thumbnail? thumbnail; + + Object encode() { + final pigeonMap = {}; + pigeonMap['title'] = title; + pigeonMap['subtitle'] = subtitle; + pigeonMap['author'] = author; + pigeonMap['summary'] = summary; + pigeonMap['publishDate'] = publishDate; + pigeonMap['pageCount'] = pageCount; + pigeonMap['thumbnail'] = thumbnail == null ? null : thumbnail!.encode(); + return pigeonMap; + } + + static Book decode(Object message) { + final pigeonMap = message as Map; + return Book() + ..title = pigeonMap['title'] as String? + ..subtitle = pigeonMap['subtitle'] as String? + ..author = pigeonMap['author'] as String? + ..summary = pigeonMap['summary'] as String? + ..publishDate = pigeonMap['publishDate'] as String? + ..pageCount = pigeonMap['pageCount'] as int? + ..thumbnail = pigeonMap['thumbnail'] != null + ? Thumbnail.decode(pigeonMap['thumbnail']!) + : null; + } +} + +class Thumbnail { + String? url; + + Object encode() { + final pigeonMap = {}; + pigeonMap['url'] = url; + return pigeonMap; + } + + static Thumbnail decode(Object message) { + final pigeonMap = message as Map; + return Thumbnail()..url = pigeonMap['url'] as String?; + } +} + +class _FlutterBookApiCodec extends StandardMessageCodec { + const _FlutterBookApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is Book) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return Book.decode(readValue(buffer)!); + + default: + return super.readValueOfType(type, buffer); + } + } +} + +abstract class FlutterBookApi { + static const MessageCodec codec = _FlutterBookApiCodec(); + + void displayBookDetails(Book book); + static void setup(FlutterBookApi? api) { + { + const channel = BasicMessageChannel( + 'dev.flutter.pigeon.FlutterBookApi.displayBookDetails', codec); + if (api == null) { + channel.setMessageHandler(null); + } else { + // ignore: avoid_types_on_closure_parameters + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.FlutterBookApi.displayBookDetails was null.'); + final List args = (message as List?)!; + final Book? arg_book = args[0] as Book?; + assert(arg_book != null, + 'Argument for dev.flutter.pigeon.FlutterBookApi.displayBookDetails was null, expected non-null Book.'); + api.displayBookDetails(arg_book!); + return; + }); + } + } + } +} + +class _HostBookApiCodec extends StandardMessageCodec { + const _HostBookApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is Book) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return Book.decode(readValue(buffer)!); + + default: + return super.readValueOfType(type, buffer); + } + } +} + +class HostBookApi { + /// Constructor for [HostBookApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + HostBookApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = _HostBookApiCodec(); + + Future cancel() async { + final channel = BasicMessageChannel( + 'dev.flutter.pigeon.HostBookApi.cancel', codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = + await channel.send(null) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + details: null, + ); + } else if (replyMap['error'] != null) { + final error = (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } + } + + Future finishEditingBook(Book arg_book) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.HostBookApi.finishEditingBook', codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = + await channel.send([arg_book]) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + details: null, + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } + } +} diff --git a/add_to_app/books/flutter_module_books/lib/main.dart b/add_to_app/books/flutter_module_books/lib/main.dart new file mode 100644 index 0000000..99da87c --- /dev/null +++ b/add_to_app/books/flutter_module_books/lib/main.dart @@ -0,0 +1,239 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; +import 'package:flutter_module_books/api.dart'; + +void main() => runApp(const MyApp()); + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + theme: ThemeData(primaryColor: const Color(0xff6200ee)), + home: const BookDetail(), + ); + } +} + +typedef BookReceived = void Function(Book book); + +class FlutterBookApiHandler extends FlutterBookApi { + FlutterBookApiHandler(this.callback); + + final BookReceived callback; + + @override + void displayBookDetails(Book book) { + callback(book); + } +} + +class BookDetail extends StatefulWidget { + const BookDetail({super.key, this.hostApi, this.flutterApi, this.book}); + + // These are the outgoing and incoming APIs that are here for injection for + // tests. + final HostBookApi? hostApi; + final FlutterBookApi? flutterApi; + final Book? book; + + @override + State createState() => _BookDetailState(); +} + +class _BookDetailState extends State { + Book? book; + + late HostBookApi hostApi; + + FocusNode textFocusNode = FocusNode(); + TextEditingController titleTextController = TextEditingController(); + TextEditingController subtitleTextController = TextEditingController(); + TextEditingController authorTextController = TextEditingController(); + + @override + void initState() { + super.initState(); + book = widget.book; + + // This `HostBookApi` class instance lets us make outgoing calls to the + // platform. + hostApi = widget.hostApi ?? HostBookApi(); + + // Registering this `FlutterBookApiHandler` class lets us receive incoming + // calls from the platform. + FlutterBookApi.setup( + FlutterBookApiHandler( + // The `FlutterBookApi` just has one method. Just give a closure for that + // method to the handler class. + (book) { + setState(() { + // This book model is what we're going to return to Kotlin eventually. + // Keep it bound to the UI. + this.book = book; + titleTextController.text = book.title ?? ''; + titleTextController.addListener(() { + this.book!.title = titleTextController.text; + }); + // Subtitle could be null. + subtitleTextController.text = book.subtitle ?? ''; + subtitleTextController.addListener(() { + this.book!.subtitle = subtitleTextController.text; + }); + authorTextController.text = book.author ?? ''; + authorTextController.addListener(() { + this.book!.author = authorTextController.text; + }); + }); + }, + ), + ); + } + + // Not overriding didUpdateWidget because the Android program can't change + // the book given to Flutter on the Android side. + + void clear() { + book = null; + // Keep focus if going to the home screen but unfocus if leaving + // the activity. + textFocusNode.unfocus(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Book Details'), + leading: IconButton( + icon: const Icon(Icons.clear), + // Pressing clear cancels the edit and leaves the activity without + // modification. + onPressed: () { + hostApi.cancel(); + clear(); + }, + ), + actions: [ + IconButton( + icon: const Icon(Icons.check), + // Pressing save sends the updated book to the platform. + onPressed: book != null + ? () { + hostApi.finishEditingBook(book!); + clear(); + } + : null, + ), + ], + ), + body: book == null + // Draw a spinner until the platform gives us the book to show details + // for. + ? const Center(child: CircularProgressIndicator()) + : BookForm( + book: book!, + focusNode: textFocusNode, + authorTextController: authorTextController, + subtitleTextController: subtitleTextController, + titleTextController: titleTextController, + ), + ); + } +} + +class BookForm extends StatelessWidget { + const BookForm({ + super.key, + required this.book, + required this.focusNode, + required this.authorTextController, + required this.subtitleTextController, + required this.titleTextController, + }); + + final Book book; + final FocusNode focusNode; + final TextEditingController titleTextController; + final TextEditingController subtitleTextController; + final TextEditingController authorTextController; + + @override + Widget build(BuildContext context) { + return Focus( + focusNode: focusNode, + child: ListView( + padding: const EdgeInsets.all(24), + children: [ + TextField( + controller: titleTextController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + filled: true, + hintText: "Title", + labelText: "Title", + ), + ), + const SizedBox(height: 24), + TextField( + controller: subtitleTextController, + maxLines: 2, + decoration: const InputDecoration( + border: OutlineInputBorder(), + filled: true, + hintText: "Subtitle", + labelText: "Subtitle", + ), + ), + const SizedBox(height: 24), + TextField( + controller: authorTextController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + filled: true, + hintText: "Author", + labelText: "Author", + ), + ), + const SizedBox(height: 32), + const Divider(), + Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + '${book.pageCount} pages ~ published ${book.publishDate}', + ), + ), + ), + const Divider(), + const SizedBox(height: 32), + if (book.thumbnail?.url != null) ...[ + Center(child: Image.network(book.thumbnail!.url!)), + const SizedBox(height: 32), + ], + if (book.summary != null) ...[ + const Center( + child: Text( + 'BOOK DESCRIPTION', + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + decoration: TextDecoration.underline, + ), + ), + ), + const SizedBox(height: 12), + Text( + book.summary ?? '', + style: TextStyle(color: Colors.grey.shade600, height: 1.24), + ), + ], + ], + ), + ); + } +} diff --git a/add_to_app/books/flutter_module_books/pigeon/schema.dart b/add_to_app/books/flutter_module_books/pigeon/schema.dart new file mode 100644 index 0000000..95f20f2 --- /dev/null +++ b/add_to_app/books/flutter_module_books/pigeon/schema.dart @@ -0,0 +1,30 @@ +// Copyright 2020 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. + +import 'package:pigeon/pigeon.dart'; + +class Book { + String? title; + String? subtitle; + String? author; + String? summary; + String? publishDate; + int? pageCount; + Thumbnail? thumbnail; +} + +class Thumbnail { + String? url; +} + +@FlutterApi() +abstract class FlutterBookApi { + void displayBookDetails(Book book); +} + +@HostApi() +abstract class HostBookApi { + void cancel(); + void finishEditingBook(Book book); +} diff --git a/add_to_app/books/flutter_module_books/pubspec.yaml b/add_to_app/books/flutter_module_books/pubspec.yaml new file mode 100644 index 0000000..01d44f0 --- /dev/null +++ b/add_to_app/books/flutter_module_books/pubspec.yaml @@ -0,0 +1,34 @@ +name: flutter_module_books +description: A Flutter module using the Pigeon package to demonstrate + integrating Flutter in a realistic scenario where the existing platform app + already has business logic and middleware constraints. +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + analysis_defaults: + path: ../../../analysis_defaults + pigeon: ">=11.0.0 <27.0.0" + flutter_test: + sdk: flutter + +flutter: + uses-material-design: true + + # This section identifies your Flutter project as a module meant for + # embedding in a native host app. These identifiers should _not_ ordinarily + # be changed after generation - they are used to ensure that the tooling can + # maintain consistency when adding or modifying assets and plugins. + # They also do not have any bearing on your native host application's + # identifiers, which may be completely independent or the same as these. + module: + androidX: true + androidPackage: dev.flutter.example.flutter_module_books + iosBundleIdentifier: dev.flutter.example.flutterModuleBooks diff --git a/add_to_app/books/flutter_module_books/run_pigeon.sh b/add_to_app/books/flutter_module_books/run_pigeon.sh new file mode 100755 index 0000000..a8b584a --- /dev/null +++ b/add_to_app/books/flutter_module_books/run_pigeon.sh @@ -0,0 +1,8 @@ +#!/bin/sh +dart run pigeon --input pigeon/schema.dart \ + --dart_out lib/api.dart \ + --objc_header_out ../ios_books/IosBooks/api.h \ + --objc_source_out ../ios_books/IosBooks/api.m \ + --objc_prefix BK \ + --java_out ../android_books/app/src/main/java/dev/flutter/example/books/Api.java \ + --java_package "dev.flutter.example.books" diff --git a/add_to_app/books/flutter_module_books/test/widget_test.dart b/add_to_app/books/flutter_module_books/test/widget_test.dart new file mode 100644 index 0000000..6de9cb0 --- /dev/null +++ b/add_to_app/books/flutter_module_books/test/widget_test.dart @@ -0,0 +1,54 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; +import 'package:flutter_module_books/api.dart'; +import 'package:flutter_module_books/main.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Pressing clear calls the cancel API', (tester) async { + MockHostBookApi mockHostApi = MockHostBookApi(); + + await tester.pumpWidget( + MaterialApp(home: BookDetail(hostApi: mockHostApi)), + ); + + await tester.tap(find.byIcon(Icons.clear)); + + expect(mockHostApi.cancelCalls, 1); + }); + + testWidgets('Pressing done calls the finish editing API', ( + tester, + ) async { + MockHostBookApi mockHostApi = MockHostBookApi(); + + await tester.pumpWidget( + MaterialApp( + home: BookDetail(book: Book(), hostApi: mockHostApi), + ), + ); + + await tester.tap(find.byIcon(Icons.check)); + + expect(mockHostApi.booksFinished.length, 1); + }); +} + +// A super-simple mock for testing that calls are made to the API. +class MockHostBookApi implements HostBookApi { + int cancelCalls = 0; + final booksFinished = []; + + @override + Future cancel() async { + cancelCalls++; + } + + @override + Future finishEditingBook(Book arg) async { + booksFinished.add(arg); + } +} diff --git a/add_to_app/books/ios_books/.gitignore b/add_to_app/books/ios_books/.gitignore new file mode 100644 index 0000000..b9e23e4 --- /dev/null +++ b/add_to_app/books/ios_books/.gitignore @@ -0,0 +1,2 @@ +xcuserdata +Pods diff --git a/add_to_app/books/ios_books/IosBooks.xcodeproj/project.pbxproj b/add_to_app/books/ios_books/IosBooks.xcodeproj/project.pbxproj new file mode 100644 index 0000000..cda644b --- /dev/null +++ b/add_to_app/books/ios_books/IosBooks.xcodeproj/project.pbxproj @@ -0,0 +1,438 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 00365BF7846F71A1052D8F66 /* Pods_IosBooks.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEE09DC86DF77B0A62A53D22 /* Pods_IosBooks.framework */; }; + 0DDE4C3B25E0390700389930 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDE4C3A25E0390700389930 /* AppDelegate.swift */; }; + 0DDE4C3D25E0390700389930 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDE4C3C25E0390700389930 /* SceneDelegate.swift */; }; + 0DDE4C3F25E0390700389930 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDE4C3E25E0390700389930 /* ViewController.swift */; }; + 0DDE4C4225E0390700389930 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0DDE4C4025E0390700389930 /* Main.storyboard */; }; + 0DDE4C4425E0390700389930 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0DDE4C4325E0390700389930 /* Assets.xcassets */; }; + 0DDE4C4725E0390700389930 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0DDE4C4525E0390700389930 /* LaunchScreen.storyboard */; }; + 0DDE4C5725E041B700389930 /* BookCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDE4C5625E041B700389930 /* BookCell.swift */; }; + 0DDE4C5C25E0752A00389930 /* api.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DDE4C5B25E0752A00389930 /* api.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0DDE4C3725E0390700389930 /* IosBooks.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IosBooks.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 0DDE4C3A25E0390700389930 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 0DDE4C3C25E0390700389930 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 0DDE4C3E25E0390700389930 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 0DDE4C4125E0390700389930 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 0DDE4C4325E0390700389930 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 0DDE4C4625E0390700389930 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 0DDE4C4825E0390700389930 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 0DDE4C5625E041B700389930 /* BookCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookCell.swift; sourceTree = ""; }; + 0DDE4C5925E0752900389930 /* IosBooks-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "IosBooks-Bridging-Header.h"; sourceTree = ""; }; + 0DDE4C5A25E0752A00389930 /* api.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = api.h; sourceTree = ""; }; + 0DDE4C5B25E0752A00389930 /* api.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = api.m; sourceTree = ""; }; + 18154B6C0196BC72EA210903 /* Pods-IosBooks.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IosBooks.release.xcconfig"; path = "Target Support Files/Pods-IosBooks/Pods-IosBooks.release.xcconfig"; sourceTree = ""; }; + CC7F3EDAABB4C083AD5BFC47 /* Pods-IosBooks.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IosBooks.debug.xcconfig"; path = "Target Support Files/Pods-IosBooks/Pods-IosBooks.debug.xcconfig"; sourceTree = ""; }; + DEE09DC86DF77B0A62A53D22 /* Pods_IosBooks.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_IosBooks.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 0DDE4C3425E0390700389930 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 00365BF7846F71A1052D8F66 /* Pods_IosBooks.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0DDE4C2E25E0390700389930 = { + isa = PBXGroup; + children = ( + 0DDE4C3925E0390700389930 /* IosBooks */, + 0DDE4C3825E0390700389930 /* Products */, + 1CA615579663E7DAA193BE81 /* Pods */, + 4E1F60927412758AE8CBF737 /* Frameworks */, + ); + sourceTree = ""; + }; + 0DDE4C3825E0390700389930 /* Products */ = { + isa = PBXGroup; + children = ( + 0DDE4C3725E0390700389930 /* IosBooks.app */, + ); + name = Products; + sourceTree = ""; + }; + 0DDE4C3925E0390700389930 /* IosBooks */ = { + isa = PBXGroup; + children = ( + 0DDE4C5A25E0752A00389930 /* api.h */, + 0DDE4C5B25E0752A00389930 /* api.m */, + 0DDE4C3A25E0390700389930 /* AppDelegate.swift */, + 0DDE4C3C25E0390700389930 /* SceneDelegate.swift */, + 0DDE4C3E25E0390700389930 /* ViewController.swift */, + 0DDE4C4025E0390700389930 /* Main.storyboard */, + 0DDE4C4325E0390700389930 /* Assets.xcassets */, + 0DDE4C4525E0390700389930 /* LaunchScreen.storyboard */, + 0DDE4C4825E0390700389930 /* Info.plist */, + 0DDE4C5625E041B700389930 /* BookCell.swift */, + 0DDE4C5925E0752900389930 /* IosBooks-Bridging-Header.h */, + ); + path = IosBooks; + sourceTree = ""; + }; + 1CA615579663E7DAA193BE81 /* Pods */ = { + isa = PBXGroup; + children = ( + CC7F3EDAABB4C083AD5BFC47 /* Pods-IosBooks.debug.xcconfig */, + 18154B6C0196BC72EA210903 /* Pods-IosBooks.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 4E1F60927412758AE8CBF737 /* Frameworks */ = { + isa = PBXGroup; + children = ( + DEE09DC86DF77B0A62A53D22 /* Pods_IosBooks.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 0DDE4C3625E0390700389930 /* IosBooks */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0DDE4C4B25E0390700389930 /* Build configuration list for PBXNativeTarget "IosBooks" */; + buildPhases = ( + 10D6942EFE7900338BEA50F9 /* [CP] Check Pods Manifest.lock */, + 887B098B8DCF409FE0F1E164 /* [CP-User] Run Flutter Build flutter_module_books Script */, + 0DDE4C3325E0390700389930 /* Sources */, + 0DDE4C3425E0390700389930 /* Frameworks */, + 0DDE4C3525E0390700389930 /* Resources */, + CA4628050A0CDB5CBFD737AD /* [CP-User] Embed Flutter Build flutter_module_books Script */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = IosBooks; + productName = IosBooks; + productReference = 0DDE4C3725E0390700389930 /* IosBooks.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0DDE4C2F25E0390700389930 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1220; + LastUpgradeCheck = 1220; + TargetAttributes = { + 0DDE4C3625E0390700389930 = { + CreatedOnToolsVersion = 12.2; + LastSwiftMigration = 1220; + }; + }; + }; + buildConfigurationList = 0DDE4C3225E0390700389930 /* Build configuration list for PBXProject "IosBooks" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 0DDE4C2E25E0390700389930; + productRefGroup = 0DDE4C3825E0390700389930 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 0DDE4C3625E0390700389930 /* IosBooks */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 0DDE4C3525E0390700389930 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0DDE4C4725E0390700389930 /* LaunchScreen.storyboard in Resources */, + 0DDE4C4425E0390700389930 /* Assets.xcassets in Resources */, + 0DDE4C4225E0390700389930 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 10D6942EFE7900338BEA50F9 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-IosBooks-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 887B098B8DCF409FE0F1E164 /* [CP-User] Run Flutter Build flutter_module_books Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + name = "[CP-User] Run Flutter Build flutter_module_books Script"; + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -e\nset -u\nsource \"${SRCROOT}/../flutter_module_books/.ios/Flutter/flutter_export_environment.sh\"\nexport VERBOSE_SCRIPT_LOGGING=1 && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/xcode_backend.sh build"; + }; + CA4628050A0CDB5CBFD737AD /* [CP-User] Embed Flutter Build flutter_module_books Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + name = "[CP-User] Embed Flutter Build flutter_module_books Script"; + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -e\nset -u\nsource \"${SRCROOT}/../flutter_module_books/.ios/Flutter/flutter_export_environment.sh\"\nexport VERBOSE_SCRIPT_LOGGING=1 && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/xcode_backend.sh embed_and_thin"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 0DDE4C3325E0390700389930 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0DDE4C5725E041B700389930 /* BookCell.swift in Sources */, + 0DDE4C3F25E0390700389930 /* ViewController.swift in Sources */, + 0DDE4C5C25E0752A00389930 /* api.m in Sources */, + 0DDE4C3B25E0390700389930 /* AppDelegate.swift in Sources */, + 0DDE4C3D25E0390700389930 /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 0DDE4C4025E0390700389930 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 0DDE4C4125E0390700389930 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 0DDE4C4525E0390700389930 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 0DDE4C4625E0390700389930 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 0DDE4C4925E0390700389930 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.2; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 0DDE4C4A25E0390700389930 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.2; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 0DDE4C4C25E0390700389930 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CC7F3EDAABB4C083AD5BFC47 /* Pods-IosBooks.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = IosBooks/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.samples.IosBooks; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "IosBooks/IosBooks-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 0DDE4C4D25E0390700389930 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 18154B6C0196BC72EA210903 /* Pods-IosBooks.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = IosBooks/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.samples.IosBooks; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "IosBooks/IosBooks-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 0DDE4C3225E0390700389930 /* Build configuration list for PBXProject "IosBooks" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0DDE4C4925E0390700389930 /* Debug */, + 0DDE4C4A25E0390700389930 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0DDE4C4B25E0390700389930 /* Build configuration list for PBXNativeTarget "IosBooks" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0DDE4C4C25E0390700389930 /* Debug */, + 0DDE4C4D25E0390700389930 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0DDE4C2F25E0390700389930 /* Project object */; +} diff --git a/add_to_app/books/ios_books/IosBooks.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/add_to_app/books/ios_books/IosBooks.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/add_to_app/books/ios_books/IosBooks.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/add_to_app/books/ios_books/IosBooks.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/add_to_app/books/ios_books/IosBooks.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/add_to_app/books/ios_books/IosBooks.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/add_to_app/books/ios_books/IosBooks.xcodeproj/xcshareddata/xcschemes/IosBooks.xcscheme b/add_to_app/books/ios_books/IosBooks.xcodeproj/xcshareddata/xcschemes/IosBooks.xcscheme new file mode 100644 index 0000000..98b9ae6 --- /dev/null +++ b/add_to_app/books/ios_books/IosBooks.xcodeproj/xcshareddata/xcschemes/IosBooks.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/add_to_app/books/ios_books/IosBooks.xcworkspace/contents.xcworkspacedata b/add_to_app/books/ios_books/IosBooks.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..5fefaae --- /dev/null +++ b/add_to_app/books/ios_books/IosBooks.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/add_to_app/books/ios_books/IosBooks.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/add_to_app/books/ios_books/IosBooks.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/add_to_app/books/ios_books/IosBooks.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/add_to_app/books/ios_books/IosBooks/AppDelegate.swift b/add_to_app/books/ios_books/IosBooks/AppDelegate.swift new file mode 100644 index 0000000..4ca809b --- /dev/null +++ b/add_to_app/books/ios_books/IosBooks/AppDelegate.swift @@ -0,0 +1,37 @@ +// Copyright 2020 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. + +import Flutter +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + lazy var engine: FlutterEngine = { + let result = FlutterEngine.init(name: "Books") + // This could be `run` earlier in the app to avoid the overhead of doing it the first time the + // engine is needed. + result.run() + return result + }() + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return true + } + + func application( + _ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, + options: UIScene.ConnectionOptions + ) -> UISceneConfiguration { + return UISceneConfiguration( + name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application( + _ application: UIApplication, didDiscardSceneSessions sceneSessions: Set + ) { + } +} diff --git a/add_to_app/books/ios_books/IosBooks/Assets.xcassets/AccentColor.colorset/Contents.json b/add_to_app/books/ios_books/IosBooks/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/add_to_app/books/ios_books/IosBooks/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/add_to_app/books/ios_books/IosBooks/Assets.xcassets/AppIcon.appiconset/Contents.json b/add_to_app/books/ios_books/IosBooks/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..9221b9b --- /dev/null +++ b/add_to_app/books/ios_books/IosBooks/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/add_to_app/books/ios_books/IosBooks/Assets.xcassets/Contents.json b/add_to_app/books/ios_books/IosBooks/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/add_to_app/books/ios_books/IosBooks/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/add_to_app/books/ios_books/IosBooks/Base.lproj/LaunchScreen.storyboard b/add_to_app/books/ios_books/IosBooks/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/add_to_app/books/ios_books/IosBooks/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/add_to_app/books/ios_books/IosBooks/Base.lproj/Main.storyboard b/add_to_app/books/ios_books/IosBooks/Base.lproj/Main.storyboard new file mode 100644 index 0000000..3fbc31f --- /dev/null +++ b/add_to_app/books/ios_books/IosBooks/Base.lproj/Main.storyboard @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/add_to_app/books/ios_books/IosBooks/BookCell.swift b/add_to_app/books/ios_books/IosBooks/BookCell.swift new file mode 100644 index 0000000..648aa4b --- /dev/null +++ b/add_to_app/books/ios_books/IosBooks/BookCell.swift @@ -0,0 +1,8 @@ +import UIKit + +class BookCell: UITableViewCell { + @IBOutlet weak var title: UILabel! + @IBOutlet weak var subtitle: UILabel! + @IBOutlet weak var byLine: UILabel! + @IBOutlet weak var editButton: UIButton! +} diff --git a/add_to_app/books/ios_books/IosBooks/Info.plist b/add_to_app/books/ios_books/IosBooks/Info.plist new file mode 100644 index 0000000..5b531f7 --- /dev/null +++ b/add_to_app/books/ios_books/IosBooks/Info.plist @@ -0,0 +1,66 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/add_to_app/books/ios_books/IosBooks/IosBooks-Bridging-Header.h b/add_to_app/books/ios_books/IosBooks/IosBooks-Bridging-Header.h new file mode 100644 index 0000000..93ea84e --- /dev/null +++ b/add_to_app/books/ios_books/IosBooks/IosBooks-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import "api.h" diff --git a/add_to_app/books/ios_books/IosBooks/SceneDelegate.swift b/add_to_app/books/ios_books/IosBooks/SceneDelegate.swift new file mode 100644 index 0000000..1c86706 --- /dev/null +++ b/add_to_app/books/ios_books/IosBooks/SceneDelegate.swift @@ -0,0 +1,69 @@ +// Copyright 2020 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. + +import Flutter +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate, FlutterSceneLifeCycleProvider +{ + var sceneLifeCycleDelegate: FlutterPluginSceneLifeCycleDelegate = + FlutterPluginSceneLifeCycleDelegate() + + var window: UIWindow? + + func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + sceneLifeCycleDelegate.scene( + scene, + willConnectTo: session, + options: connectionOptions + ) + } + + func sceneDidDisconnect(_ scene: UIScene) { + sceneLifeCycleDelegate.sceneDidDisconnect(scene) + } + + func sceneWillEnterForeground(_ scene: UIScene) { + sceneLifeCycleDelegate.sceneWillEnterForeground(scene) + } + + func sceneDidBecomeActive(_ scene: UIScene) { + sceneLifeCycleDelegate.sceneDidBecomeActive(scene) + } + + func sceneWillResignActive(_ scene: UIScene) { + sceneLifeCycleDelegate.sceneWillResignActive(scene) + } + + func sceneDidEnterBackground(_ scene: UIScene) { + sceneLifeCycleDelegate.sceneDidEnterBackground(scene) + } + + func scene( + _ scene: UIScene, + openURLContexts URLContexts: Set + ) { + sceneLifeCycleDelegate.scene(scene, openURLContexts: URLContexts) + } + + func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { + sceneLifeCycleDelegate.scene(scene, continue: userActivity) + } + + func windowScene( + _ windowScene: UIWindowScene, + performActionFor shortcutItem: UIApplicationShortcutItem, + completionHandler: @escaping (Bool) -> Void + ) { + sceneLifeCycleDelegate.windowScene( + windowScene, + performActionFor: shortcutItem, + completionHandler: completionHandler + ) + } +} diff --git a/add_to_app/books/ios_books/IosBooks/ViewController.swift b/add_to_app/books/ios_books/IosBooks/ViewController.swift new file mode 100644 index 0000000..210ad76 --- /dev/null +++ b/add_to_app/books/ios_books/IosBooks/ViewController.swift @@ -0,0 +1,135 @@ +// Copyright 2020 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. + +import Flutter +import UIKit + +class ViewController: UITableViewController, BKHostBookApi { + private var books: [BKBook] = [] + private var api: BKFlutterBookApi! + private var editingIndex: Int = -1 + + override func viewDidLoad() { + super.viewDidLoad() + self.navigationItem.title = "Books" + let appDelegate = UIApplication.shared.delegate as! AppDelegate + BKHostBookApiSetup(appDelegate.engine.binaryMessenger, self) + api = BKFlutterBookApi.init(binaryMessenger: appDelegate.engine.binaryMessenger) + if let url = URL( + string: "https://www.googleapis.com/books/v1/volumes?q=greenwood+tulsa&maxResults=15") + { + self.loadBooks(url: url) { (newBooks) in + if let newBooks = newBooks { + self.books = newBooks + self.tableView.reloadData() + } + } + } + } + + /** + Loads a JSON file from the supplied URL, parses it and calls the callback with the array of + parsed books on the main thread. + */ + func loadBooks(url: URL, completion: @escaping ([BKBook]?) -> Void) { + URLSession.shared.dataTask(with: url) { data, response, error in + if let data = data { + do { + if let json = try JSONSerialization.jsonObject(with: data, options: []) + as? [String: Any] + { + var newBooks: [BKBook] = [] + for item in json["items"] as! [[String: Any]] { + let volumeInfo = item["volumeInfo"] as! [String: Any] + let title = volumeInfo["title"] as! String + let subtitle = volumeInfo["subtitle"] as! String? + let authors = (volumeInfo["authors"] as! [String]).joined(separator: " & ") + let pageCount = volumeInfo["pageCount"] as! Int32 + let publishedDate = volumeInfo["publishedDate"] as! String + let summary = volumeInfo["description"] as! String? + let imageLinks = volumeInfo["imageLinks"] as! [String: Any] + let thumbnail: BKThumbnail = BKThumbnail.init() + thumbnail.url = imageLinks["thumbnail"] as! String? + let book: BKBook = BKBook.init() + book.author = authors + book.title = title + book.subtitle = subtitle + book.title = title + book.pageCount = NSNumber.init(value: pageCount) + book.publishDate = publishedDate + book.summary = summary + book.thumbnail = thumbnail + newBooks.append(book) + } + DispatchQueue.main.async { + completion(newBooks) + } + } + } catch let error { + print("json decode error: \(error)") + completion(nil) + } + } + }.resume() + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.books.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) + -> UITableViewCell + { + let cell: BookCell = self.tableView.dequeueReusableCell(withIdentifier: "BookCell") as! BookCell + let bookInfo = books[indexPath.row] + cell.title.text = bookInfo.title + cell.subtitle.text = bookInfo.subtitle + if let author = bookInfo.author { + cell.byLine.text = "by: \(author)" + } + weak var weakSelf = self + let editAction = UIAction(title: "Edit") { (action) in + if let weakSelf = weakSelf { + weakSelf.editItem(index: indexPath.row) + } + } + cell.editButton.removeTarget(nil, action: nil, for: .allEvents) + cell.editButton.addAction(editAction, for: UIControl.Event.touchUpInside) + return cell + } + + /** + Presents the FlutterViewController that edits the book at the supplied index. + */ + func editItem(index: Int) { + let appDelegate = UIApplication.shared.delegate as! AppDelegate + let flutterViewController = FlutterViewController.init( + engine: appDelegate.engine, nibName: nil, bundle: nil) + self.editingIndex = index + api.displayBookDetailsBook(self.books[index]) { (error) in + if let error = error { + print(error) + } + } + self.present(flutterViewController, animated: true, completion: nil) + } + + /** + Called by Pigeon when the FlutterViewController is dismissed without accepting any edits. + */ + func cancelWithError(_ error: AutoreleasingUnsafeMutablePointer) { + self.editingIndex = -1 + self.dismiss(animated: true, completion: nil) + } + + /** + Called by Pigeon when edits to the book are accepted in the FlutterViewController. + */ + func finishEditingBook(_ input: BKBook, error: AutoreleasingUnsafeMutablePointer) { + self.books[editingIndex] = input + self.tableView.reloadData() + self.editingIndex = -1 + self.dismiss(animated: true, completion: nil) + } +} diff --git a/add_to_app/books/ios_books/IosBooks/api.h b/add_to_app/books/ios_books/IosBooks/api.h new file mode 100644 index 0000000..12e7ffc --- /dev/null +++ b/add_to_app/books/ios_books/IosBooks/api.h @@ -0,0 +1,49 @@ +// Autogenerated from Pigeon (v1.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon +#import +@protocol FlutterBinaryMessenger; +@protocol FlutterMessageCodec; +@class FlutterError; +@class FlutterStandardTypedData; + +NS_ASSUME_NONNULL_BEGIN + +@class BKBook; +@class BKThumbnail; + +@interface BKBook : NSObject +@property(nonatomic, copy, nullable) NSString *title; +@property(nonatomic, copy, nullable) NSString *subtitle; +@property(nonatomic, copy, nullable) NSString *author; +@property(nonatomic, copy, nullable) NSString *summary; +@property(nonatomic, copy, nullable) NSString *publishDate; +@property(nonatomic, strong, nullable) NSNumber *pageCount; +@property(nonatomic, strong, nullable) BKThumbnail *thumbnail; +@end + +@interface BKThumbnail : NSObject +@property(nonatomic, copy, nullable) NSString *url; +@end + +/// The codec used by BKFlutterBookApi. +NSObject *BKFlutterBookApiGetCodec(void); + +@interface BKFlutterBookApi : NSObject +- (instancetype)initWithBinaryMessenger: + (id)binaryMessenger; +- (void)displayBookDetailsBook:(BKBook *)book + completion:(void (^)(NSError *_Nullable))completion; +@end +/// The codec used by BKHostBookApi. +NSObject *BKHostBookApiGetCodec(void); + +@protocol BKHostBookApi +- (void)cancelWithError:(FlutterError *_Nullable *_Nonnull)error; +- (void)finishEditingBookBook:(BKBook *)book + error:(FlutterError *_Nullable *_Nonnull)error; +@end + +extern void BKHostBookApiSetup(id binaryMessenger, + NSObject *_Nullable api); + +NS_ASSUME_NONNULL_END diff --git a/add_to_app/books/ios_books/IosBooks/api.m b/add_to_app/books/ios_books/IosBooks/api.m new file mode 100644 index 0000000..db28730 --- /dev/null +++ b/add_to_app/books/ios_books/IosBooks/api.m @@ -0,0 +1,269 @@ +// Autogenerated from Pigeon (v1.0.1), do not edit directly. +// See also: https://pub.dev/packages/pigeon +#import "api.h" +#import + +#if !__has_feature(objc_arc) +#error File requires ARC to be enabled. +#endif + +static NSDictionary *wrapResult(id result, + FlutterError *error) { + NSDictionary *errorDict = (NSDictionary *)[NSNull null]; + if (error) { + errorDict = @{ + @"code" : (error.code ? error.code : [NSNull null]), + @"message" : (error.message ? error.message : [NSNull null]), + @"details" : (error.details ? error.details : [NSNull null]), + }; + } + return @{ + @"result" : (result ? result : [NSNull null]), + @"error" : errorDict, + }; +} + +@interface BKBook () ++ (BKBook *)fromMap:(NSDictionary *)dict; +- (NSDictionary *)toMap; +@end +@interface BKThumbnail () ++ (BKThumbnail *)fromMap:(NSDictionary *)dict; +- (NSDictionary *)toMap; +@end + +@implementation BKBook ++ (BKBook *)fromMap:(NSDictionary *)dict { + BKBook *result = [[BKBook alloc] init]; + result.title = dict[@"title"]; + if ((NSNull *)result.title == [NSNull null]) { + result.title = nil; + } + result.subtitle = dict[@"subtitle"]; + if ((NSNull *)result.subtitle == [NSNull null]) { + result.subtitle = nil; + } + result.author = dict[@"author"]; + if ((NSNull *)result.author == [NSNull null]) { + result.author = nil; + } + result.summary = dict[@"summary"]; + if ((NSNull *)result.summary == [NSNull null]) { + result.summary = nil; + } + result.publishDate = dict[@"publishDate"]; + if ((NSNull *)result.publishDate == [NSNull null]) { + result.publishDate = nil; + } + result.pageCount = dict[@"pageCount"]; + if ((NSNull *)result.pageCount == [NSNull null]) { + result.pageCount = nil; + } + result.thumbnail = [BKThumbnail fromMap:dict[@"thumbnail"]]; + if ((NSNull *)result.thumbnail == [NSNull null]) { + result.thumbnail = nil; + } + return result; +} +- (NSDictionary *)toMap { + return [NSDictionary + dictionaryWithObjectsAndKeys: + (self.title ? self.title : [NSNull null]), @"title", + (self.subtitle ? self.subtitle : [NSNull null]), @"subtitle", + (self.author ? self.author : [NSNull null]), @"author", + (self.summary ? self.summary : [NSNull null]), @"summary", + (self.publishDate ? self.publishDate : [NSNull null]), @"publishDate", + (self.pageCount ? self.pageCount : [NSNull null]), @"pageCount", + (self.thumbnail ? [self.thumbnail toMap] : [NSNull null]), + @"thumbnail", nil]; +} +@end + +@implementation BKThumbnail ++ (BKThumbnail *)fromMap:(NSDictionary *)dict { + BKThumbnail *result = [[BKThumbnail alloc] init]; + result.url = dict[@"url"]; + if ((NSNull *)result.url == [NSNull null]) { + result.url = nil; + } + return result; +} +- (NSDictionary *)toMap { + return [NSDictionary + dictionaryWithObjectsAndKeys:(self.url ? self.url : [NSNull null]), + @"url", nil]; +} +@end + +@interface BKFlutterBookApiCodecReader : FlutterStandardReader +@end +@implementation BKFlutterBookApiCodecReader +- (nullable id)readValueOfType:(UInt8)type { + switch (type) { + case 128: + return [BKBook fromMap:[self readValue]]; + + default: + return [super readValueOfType:type]; + } +} +@end + +@interface BKFlutterBookApiCodecWriter : FlutterStandardWriter +@end +@implementation BKFlutterBookApiCodecWriter +- (void)writeValue:(id)value { + if ([value isKindOfClass:[BKBook class]]) { + [self writeByte:128]; + [self writeValue:[value toMap]]; + } else { + [super writeValue:value]; + } +} +@end + +@interface BKFlutterBookApiCodecReaderWriter : FlutterStandardReaderWriter +@end +@implementation BKFlutterBookApiCodecReaderWriter +- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { + return [[BKFlutterBookApiCodecWriter alloc] initWithData:data]; +} +- (FlutterStandardReader *)readerWithData:(NSData *)data { + return [[BKFlutterBookApiCodecReader alloc] initWithData:data]; +} +@end + +NSObject *BKFlutterBookApiGetCodec() { + static dispatch_once_t s_pred = 0; + static FlutterStandardMessageCodec *s_sharedObject = nil; + dispatch_once(&s_pred, ^{ + BKFlutterBookApiCodecReaderWriter *readerWriter = + [[BKFlutterBookApiCodecReaderWriter alloc] init]; + s_sharedObject = + [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; + }); + return s_sharedObject; +} + +@interface BKFlutterBookApi () +@property(nonatomic, strong) NSObject *binaryMessenger; +@end + +@implementation BKFlutterBookApi +- (instancetype)initWithBinaryMessenger: + (NSObject *)binaryMessenger { + self = [super init]; + if (self) { + _binaryMessenger = binaryMessenger; + } + return self; +} + +- (void)displayBookDetailsBook:(BKBook *)arg_book + completion:(void (^)(NSError *_Nullable))completion { + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName: + @"dev.flutter.pigeon.FlutterBookApi.displayBookDetails" + binaryMessenger:self.binaryMessenger + codec:BKFlutterBookApiGetCodec()]; + [channel sendMessage:@[ arg_book ] + reply:^(id reply) { + completion(nil); + }]; +} +@end +@interface BKHostBookApiCodecReader : FlutterStandardReader +@end +@implementation BKHostBookApiCodecReader +- (nullable id)readValueOfType:(UInt8)type { + switch (type) { + case 128: + return [BKBook fromMap:[self readValue]]; + + default: + return [super readValueOfType:type]; + } +} +@end + +@interface BKHostBookApiCodecWriter : FlutterStandardWriter +@end +@implementation BKHostBookApiCodecWriter +- (void)writeValue:(id)value { + if ([value isKindOfClass:[BKBook class]]) { + [self writeByte:128]; + [self writeValue:[value toMap]]; + } else { + [super writeValue:value]; + } +} +@end + +@interface BKHostBookApiCodecReaderWriter : FlutterStandardReaderWriter +@end +@implementation BKHostBookApiCodecReaderWriter +- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { + return [[BKHostBookApiCodecWriter alloc] initWithData:data]; +} +- (FlutterStandardReader *)readerWithData:(NSData *)data { + return [[BKHostBookApiCodecReader alloc] initWithData:data]; +} +@end + +NSObject *BKHostBookApiGetCodec() { + static dispatch_once_t s_pred = 0; + static FlutterStandardMessageCodec *s_sharedObject = nil; + dispatch_once(&s_pred, ^{ + BKHostBookApiCodecReaderWriter *readerWriter = + [[BKHostBookApiCodecReaderWriter alloc] init]; + s_sharedObject = + [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; + }); + return s_sharedObject; +} + +void BKHostBookApiSetup(id binaryMessenger, + NSObject *api) { + { + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:@"dev.flutter.pigeon.HostBookApi.cancel" + binaryMessenger:binaryMessenger + codec:BKHostBookApiGetCodec()]; + if (api) { + NSCAssert( + [api respondsToSelector:@selector(cancelWithError:)], + @"BKHostBookApi api doesn't respond to @selector(cancelWithError:)"); + [channel + setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + [api cancelWithError:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName: + @"dev.flutter.pigeon.HostBookApi.finishEditingBook" + binaryMessenger:binaryMessenger + codec:BKHostBookApiGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(finishEditingBookBook: + error:)], + @"BKHostBookApi api doesn't respond to " + @"@selector(finishEditingBookBook:error:)"); + [channel + setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + BKBook *arg_book = args[0]; + FlutterError *error; + [api finishEditingBookBook:arg_book error:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } +} diff --git a/add_to_app/books/ios_books/Podfile b/add_to_app/books/ios_books/Podfile new file mode 100644 index 0000000..b8246b0 --- /dev/null +++ b/add_to_app/books/ios_books/Podfile @@ -0,0 +1,16 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' + +flutter_application_path = '../flutter_module_books/' +load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb') + +target 'IosBooks' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + # Pods for IosBooks + install_all_flutter_pods(flutter_application_path) +end + +post_install do |installer| + flutter_post_install(installer) if defined?(flutter_post_install) +end diff --git a/add_to_app/fullscreen/README.md b/add_to_app/fullscreen/README.md new file mode 100644 index 0000000..0f378d5 --- /dev/null +++ b/add_to_app/fullscreen/README.md @@ -0,0 +1,55 @@ +# fullscreen + +Embeds a full screen instance of Flutter into an existing iOS or Android app. + +## Description + +These apps showcase a relatively straightforward integration of +`flutter_module_fullscreen`: + +* The Flutter module is built along with the app when the app is built. +* The Flutter engine is warmed up at app launch. +* The Flutter view is presented with a full-screen Activity or + UIViewController. +* The Flutter view is a navigational leaf node; it does not launch any new, + native Activities or UIViewControllers in response to user actions. + +If you are new to Flutter's add-to-app APIs, these projects are a great place +to begin learning how to use them. + +## 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_fullscreen/ + flutter pub get + + # For Android builds: + open -a "Android Studio" ../android_fullscreen # macOS only + # Or open the ../android_fullscreen folder in Android Studio for other platforms. + + # For iOS builds: + cd ../ios_fullscreen + pod install + open IOSFullScreen.xcworkspace + # Then, click "Run" in Xcode to launch the app into your Simulator or device +``` + +## Requirements + +* Flutter +* Android + * Android Studio +* iOS + * Xcode + * Cocoapods + +## Questions/issues + +See [add_to_app/README.md](../README.md) for further help. diff --git a/add_to_app/fullscreen/android_fullscreen/.gitignore b/add_to_app/fullscreen/android_fullscreen/.gitignore new file mode 100644 index 0000000..603b140 --- /dev/null +++ b/add_to_app/fullscreen/android_fullscreen/.gitignore @@ -0,0 +1,14 @@ +*.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 diff --git a/add_to_app/fullscreen/android_fullscreen/README.md b/add_to_app/fullscreen/android_fullscreen/README.md new file mode 100644 index 0000000..8e22b36 --- /dev/null +++ b/add_to_app/fullscreen/android_fullscreen/README.md @@ -0,0 +1,14 @@ +# android_fullscreen + +An example Android application used in the Flutter add-to-app samples. For more +information on how to use it, see the [README](../README.md) file located in the +[/add_to_app](/add_to_app) directory of this repo. + +## 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). diff --git a/add_to_app/fullscreen/android_fullscreen/app/.gitignore b/add_to_app/fullscreen/android_fullscreen/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/add_to_app/fullscreen/android_fullscreen/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/add_to_app/fullscreen/android_fullscreen/app/build.gradle b/add_to_app/fullscreen/android_fullscreen/app/build.gradle new file mode 100644 index 0000000..b0792ec --- /dev/null +++ b/add_to_app/fullscreen/android_fullscreen/app/build.gradle @@ -0,0 +1,42 @@ +apply plugin: 'com.android.application' + +apply plugin: 'kotlin-android' + +android { + compileSdk 33 + defaultConfig { + applicationId "dev.flutter.example.androidfullscreen" + minSdkVersion 21 + targetSdkVersion 33 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + multiDexEnabled true + } + buildTypes { + release { + minifyEnabled false + signingConfig debug.signingConfig + } + } + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + namespace 'dev.flutter.example.androidfullscreen' +} + +dependencies { + implementation 'androidx.multidex:multidex:2.0.1' + implementation project(':flutter') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.core:core-ktx:1.10.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + testImplementation 'junit:junit:4.12' + androidTestImplementation project(':espresso') + androidTestImplementation "com.google.truth:truth:1.1.3" + androidTestImplementation 'androidx.test:runner:1.5.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + api 'androidx.test:core:1.5.0' +} diff --git a/add_to_app/fullscreen/android_fullscreen/app/src/androidTest/java/dev/flutter/example/androidfullscreen/ExampleInstrumentedTest.kt b/add_to_app/fullscreen/android_fullscreen/app/src/androidTest/java/dev/flutter/example/androidfullscreen/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..74383f8 --- /dev/null +++ b/add_to_app/fullscreen/android_fullscreen/app/src/androidTest/java/dev/flutter/example/androidfullscreen/ExampleInstrumentedTest.kt @@ -0,0 +1,71 @@ +// 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.androidfullscreen + +import androidx.test.core.app.ActivityScenario +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.flutter.EspressoFlutter.onFlutterWidget +import androidx.test.espresso.flutter.action.FlutterActions.click +import androidx.test.espresso.flutter.assertion.FlutterAssertions.matches +import androidx.test.espresso.flutter.matcher.FlutterMatchers +import androidx.test.espresso.flutter.matcher.FlutterMatchers.withText +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class MainActivityTest { + @Before + fun setUp() { + ActivityScenario.launch(MainActivity::class.java) + } + + @Test + fun flutterTextUpdatesOnClick() { + // Launch Flutter module. + onView(withId(R.id.launch_button)).perform(androidx.test.espresso.action.ViewActions.click()) + + // Verify state is inited correctly. + onFlutterWidget(withText("Taps: 0")) + .check(matches(FlutterMatchers.isExisting())) + + // Verify the increment button works. + onFlutterWidget(withText("Tap me!")).perform(click()) + onFlutterWidget(withText("Taps: 1")) + .check(matches(FlutterMatchers.isExisting())) + } + + @Test + fun nativeTextViewUpdatesOnClick() { + // Verify Android TextView is inited correctly. + onView(withId(R.id.counter_label)).check( + androidx.test.espresso.assertion.ViewAssertions.matches( + // TODO(redbrogdon): This should be a check for 0 rather than 1. Because our state + // management is hardcoded into the Application object, though, and that object is + // reused across tests, this test begins with a counter already incremented by the + // previous one. This situation can be corrected via DI or a number of other + // approaches. + androidx.test.espresso.matcher.ViewMatchers.withText("Current count: 1") + ) + ) + + // Launch Flutter module. + onView(withId(R.id.launch_button)).perform(androidx.test.espresso.action.ViewActions.click()) + + // Increment count. + onFlutterWidget(withText("Tap me!")).perform(click()) + + // Exit Flutter module and verify that the Android TextView is updated correctly. + onFlutterWidget(withText("Exit this screen")).perform(click()) + onView(withId(R.id.counter_label)).check( + androidx.test.espresso.assertion.ViewAssertions.matches( + // TODO(redbrogdon): s/2/1 + androidx.test.espresso.matcher.ViewMatchers.withText("Current count: 2") + ) + ) + } +} diff --git a/add_to_app/fullscreen/android_fullscreen/app/src/debug/AndroidManifest.xml b/add_to_app/fullscreen/android_fullscreen/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..a9e3aab --- /dev/null +++ b/add_to_app/fullscreen/android_fullscreen/app/src/debug/AndroidManifest.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/add_to_app/fullscreen/android_fullscreen/app/src/debug/res/xml/network_security_config.xml b/add_to_app/fullscreen/android_fullscreen/app/src/debug/res/xml/network_security_config.xml new file mode 100644 index 0000000..3032fe0 --- /dev/null +++ b/add_to_app/fullscreen/android_fullscreen/app/src/debug/res/xml/network_security_config.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/add_to_app/fullscreen/android_fullscreen/app/src/main/AndroidManifest.xml b/add_to_app/fullscreen/android_fullscreen/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f69f249 --- /dev/null +++ b/add_to_app/fullscreen/android_fullscreen/app/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + diff --git a/add_to_app/fullscreen/android_fullscreen/app/src/main/java/dev/flutter/example/androidfullscreen/MainActivity.kt b/add_to_app/fullscreen/android_fullscreen/app/src/main/java/dev/flutter/example/androidfullscreen/MainActivity.kt new file mode 100644 index 0000000..dc32af3 --- /dev/null +++ b/add_to_app/fullscreen/android_fullscreen/app/src/main/java/dev/flutter/example/androidfullscreen/MainActivity.kt @@ -0,0 +1,37 @@ +// 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.androidfullscreen + +import android.os.Bundle +import android.widget.Button +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : AppCompatActivity() { + private lateinit var counterLabel: TextView + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + counterLabel = findViewById(R.id.counter_label) + + val button = findViewById + + + + + + + + + + + + + + + + + + + + diff --git a/add_to_app/fullscreen/ios_fullscreen/IOSFullScreen/Info-Debug.plist b/add_to_app/fullscreen/ios_fullscreen/IOSFullScreen/Info-Debug.plist new file mode 100644 index 0000000..72b874a --- /dev/null +++ b/add_to_app/fullscreen/ios_fullscreen/IOSFullScreen/Info-Debug.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + NSBonjourServices + + _dartobservatory._tcp + + NSLocalNetworkUsageDescription + Allow Flutter tools on your computer to connect and debug your application. This prompt will not appear on release builds. + + diff --git a/add_to_app/fullscreen/ios_fullscreen/IOSFullScreen/Info-Release.plist b/add_to_app/fullscreen/ios_fullscreen/IOSFullScreen/Info-Release.plist new file mode 100644 index 0000000..16be3b6 --- /dev/null +++ b/add_to_app/fullscreen/ios_fullscreen/IOSFullScreen/Info-Release.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/add_to_app/fullscreen/ios_fullscreen/IOSFullScreen/ViewController.swift b/add_to_app/fullscreen/ios_fullscreen/IOSFullScreen/ViewController.swift new file mode 100644 index 0000000..85a4e1f --- /dev/null +++ b/add_to_app/fullscreen/ios_fullscreen/IOSFullScreen/ViewController.swift @@ -0,0 +1,50 @@ +// 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. + +import UIKit +import Flutter + +class ViewController: UIViewController { + + @IBOutlet weak var counterLabel: UILabel! + + var methodChannel : FlutterMethodChannel? + var count = 0 + + override func viewDidLoad() { + super.viewDidLoad() + + if let flutterEngine = (UIApplication.shared.delegate as? AppDelegate)?.flutterEngine { + methodChannel = FlutterMethodChannel(name: "dev.flutter.example/counter", + binaryMessenger: flutterEngine.binaryMessenger) + methodChannel?.setMethodCallHandler({ [weak self] + (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in + if let strongSelf = self { + switch(call.method) { + case "incrementCounter": + strongSelf.count += 1 + strongSelf.counterLabel.text = "Current counter: \(strongSelf.count)" + strongSelf.reportCounter() + case "requestCounter": + strongSelf.reportCounter() + default: + // Unrecognized method name + print("Unrecognized method name: \(call.method)") + } + } + }) + } + } + + func reportCounter() { + methodChannel?.invokeMethod("reportCounter", arguments: count) + } + + @IBAction func buttonWasTapped(_ sender: Any) { + if let flutterEngine = (UIApplication.shared.delegate as? AppDelegate)?.flutterEngine { + let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil) + self.present(flutterViewController, animated: true, completion: nil) + } + } +} diff --git a/add_to_app/fullscreen/ios_fullscreen/IOSFullScreenTests/IOSFullScreenTests.swift b/add_to_app/fullscreen/ios_fullscreen/IOSFullScreenTests/IOSFullScreenTests.swift new file mode 100644 index 0000000..0eafb49 --- /dev/null +++ b/add_to_app/fullscreen/ios_fullscreen/IOSFullScreenTests/IOSFullScreenTests.swift @@ -0,0 +1,30 @@ +// 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. + +import XCTest +@testable import IOSFullScreen + +class IOSFullScreenTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/add_to_app/fullscreen/ios_fullscreen/IOSFullScreenTests/Info.plist b/add_to_app/fullscreen/ios_fullscreen/IOSFullScreenTests/Info.plist new file mode 100644 index 0000000..6c40a6c --- /dev/null +++ b/add_to_app/fullscreen/ios_fullscreen/IOSFullScreenTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/add_to_app/fullscreen/ios_fullscreen/IOSFullScreenUITests/IOSFullScreenUITests.swift b/add_to_app/fullscreen/ios_fullscreen/IOSFullScreenUITests/IOSFullScreenUITests.swift new file mode 100644 index 0000000..1def489 --- /dev/null +++ b/add_to_app/fullscreen/ios_fullscreen/IOSFullScreenUITests/IOSFullScreenUITests.swift @@ -0,0 +1,30 @@ +// 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. + +import XCTest + +class IOSFullScreenUITests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. + XCUIApplication().launch() + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // Use recording to get started writing UI tests. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + +} diff --git a/add_to_app/fullscreen/ios_fullscreen/IOSFullScreenUITests/Info.plist b/add_to_app/fullscreen/ios_fullscreen/IOSFullScreenUITests/Info.plist new file mode 100644 index 0000000..6c40a6c --- /dev/null +++ b/add_to_app/fullscreen/ios_fullscreen/IOSFullScreenUITests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/add_to_app/fullscreen/ios_fullscreen/Podfile b/add_to_app/fullscreen/ios_fullscreen/Podfile new file mode 100644 index 0000000..1daf6b9 --- /dev/null +++ b/add_to_app/fullscreen/ios_fullscreen/Podfile @@ -0,0 +1,28 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' + +flutter_application_path = '../flutter_module_fullscreen' +load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb') + +target 'IOSFullScreen' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + # Pods for IOSFullScreen + install_all_flutter_pods(flutter_application_path) + + target 'IOSFullScreenTests' do + inherit! :search_paths + # Pods for testing + end + + target 'IOSFullScreenUITests' do + inherit! :search_paths + # Pods for testing + end + +end + +post_install do |installer| + flutter_post_install(installer) if defined?(flutter_post_install) +end diff --git a/add_to_app/fullscreen/ios_fullscreen/README.md b/add_to_app/fullscreen/ios_fullscreen/README.md new file mode 100644 index 0000000..e3ff162 --- /dev/null +++ b/add_to_app/fullscreen/ios_fullscreen/README.md @@ -0,0 +1,14 @@ +# ios_fullscreen + +An example iOS application used in the Flutter add-to-app samples. For more +information on how to use it, see the [README](../README.md) file located in the +[/add_to_app](/add_to_app) directory of this repo. + +## 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). diff --git a/add_to_app/ios_content_resizing/README.md b/add_to_app/ios_content_resizing/README.md new file mode 100644 index 0000000..9eb5160 --- /dev/null +++ b/add_to_app/ios_content_resizing/README.md @@ -0,0 +1,36 @@ +# ios_content_resizing + +Embeds an instance of Flutter into an existing iOS app that will dynamically resize itself based off of it's content. + +## Description + +This sample showcases the integration of a self-resizing Flutter view within a native UIKit environment. The Flutter module features an expanding column of widgets. As items are added, the hosting UIView will automatically adjusts its [intrinsicContentSize](https://developer.apple.com/documentation/uikit/uiview/intrinsiccontentsize) to accommodate the new content size. + +## 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/ + flutter pub get + cd ../ios_content_resizing + pod install + open ios_content_resizing.xcworkspace + # Then, click "Run" in Xcode to launch the app into your Simulator or device +``` + +## Requirements + +* Flutter +* iOS + * Xcode + * Cocoapods + +## Questions/issues + +See [add_to_app/README.md](../README.md) for further help. diff --git a/add_to_app/ios_content_resizing/flutter_module/.gitignore b/add_to_app/ios_content_resizing/flutter_module/.gitignore new file mode 100644 index 0000000..5d88593 --- /dev/null +++ b/add_to_app/ios_content_resizing/flutter_module/.gitignore @@ -0,0 +1,48 @@ +.DS_Store +.dart_tool/ + +.pub/ + +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +migrate_working_dir/ + +*.swp +profile + +DerivedData/ + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +build/ +.android/ +.ios/ +.flutter-plugins-dependencies + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json diff --git a/add_to_app/ios_content_resizing/flutter_module/.metadata b/add_to_app/ios_content_resizing/flutter_module/.metadata new file mode 100644 index 0000000..d6ca04b --- /dev/null +++ b/add_to_app/ios_content_resizing/flutter_module/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "27ccc2e66f3fd85ad388e3ba038a89251aef41b7" + channel: "master" + +project_type: module diff --git a/add_to_app/ios_content_resizing/flutter_module/README.md b/add_to_app/ios_content_resizing/flutter_module/README.md new file mode 100644 index 0000000..68e5071 --- /dev/null +++ b/add_to_app/ios_content_resizing/flutter_module/README.md @@ -0,0 +1,11 @@ +# flutter_module + +A new Flutter module project. + +## Getting Started + +For help getting started with Flutter development, view the online +[documentation](https://flutter.dev/). + +For instructions integrating Flutter modules to your existing applications, +see the [add-to-app documentation](https://flutter.dev/to/add-to-app). diff --git a/add_to_app/ios_content_resizing/flutter_module/analysis_options.yaml b/add_to_app/ios_content_resizing/flutter_module/analysis_options.yaml new file mode 100644 index 0000000..e69de29 diff --git a/add_to_app/ios_content_resizing/flutter_module/lib/main.dart b/add_to_app/ios_content_resizing/flutter_module/lib/main.dart new file mode 100644 index 0000000..24ef27f --- /dev/null +++ b/add_to_app/ios_content_resizing/flutter_module/lib/main.dart @@ -0,0 +1,53 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +void main() { + runApp(const ResizeApp()); +} + +class ResizeApp extends StatefulWidget { + const ResizeApp({super.key}); + + @override + State createState() => _ResizeAppState(); +} + +class _ResizeAppState extends State { + int _listSize = 1; + void _addToList() { + setState(() { + _listSize++; + }); + } + + @override + Widget build(BuildContext context) { + return Center( + heightFactor: 1, + child: Directionality( + textDirection: TextDirection.ltr, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (int i = 0; i < _listSize; i++) + Container( + color: HSVColor.fromAHSV(1, (10.0 * i), 1, 1).toColor(), + height: 50, + width: 200, + child: Center( + child: Text( + 'Flutter Widget $i', + style: const TextStyle(fontSize: 16, color: Colors.black), + ), + ), + ), + TextButton(onPressed: _addToList, child: Text('Listception!')), + ], + ), + ), + ); + } +} diff --git a/add_to_app/ios_content_resizing/flutter_module/pubspec.yaml b/add_to_app/ios_content_resizing/flutter_module/pubspec.yaml new file mode 100644 index 0000000..222719e --- /dev/null +++ b/add_to_app/ios_content_resizing/flutter_module/pubspec.yaml @@ -0,0 +1,87 @@ +name: flutter_module +description: "A new Flutter module project." + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# +# This version is used _only_ for the Runner app, which is used if you just do +# a `flutter run`. It has no impact on any other native host app that you embed +# your Flutter project into. +version: 1.0.0+1 + +environment: + sdk: ^3.11.0-200.1.beta + +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +flutter: + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add Flutter specific assets to your application, add an assets section, + # like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add Flutter specific custom fonts to your application, add a fonts + # section here, in this "flutter" section. Each entry in this list should + # have a "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package + + + # This section identifies your Flutter project as a module meant for + # embedding in a native host app. These identifiers should _not_ ordinarily + # be changed after generation - they are used to ensure that the tooling can + # maintain consistency when adding or modifying assets and plugins. + # They also do not have any bearing on your native host application's + # identifiers, which may be completely independent or the same as these. + module: + androidX: true + androidPackage: com.example.flutter_module + iosBundleIdentifier: com.example.flutterModule diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/Podfile b/add_to_app/ios_content_resizing/ios_content_resizing/Podfile new file mode 100644 index 0000000..a17a105 --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/Podfile @@ -0,0 +1,32 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '15.0' + +flutter_application_path = '../flutter_module' +load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb') + + +target 'ios_content_resizing' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + # Pods for ios_content_resizing + + install_all_flutter_pods(flutter_application_path) + + target 'ios_content_resizingTests' do + inherit! :search_paths + # Pods for testing + end + + target 'ios_content_resizingUITests' do + # Pods for testing + end + +end + + +post_install do |installer| + flutter_post_install(installer) if defined?(flutter_post_install) +end + + diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Manifest.lock b/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Manifest.lock new file mode 100644 index 0000000..00cca2c --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Manifest.lock @@ -0,0 +1,22 @@ +PODS: + - Flutter (1.0.0) + - FlutterPluginRegistrant (0.0.1): + - Flutter + +DEPENDENCIES: + - Flutter (from `../flutter_module/.ios/Flutter`) + - FlutterPluginRegistrant (from `../flutter_module/.ios/Flutter/FlutterPluginRegistrant`) + +EXTERNAL SOURCES: + Flutter: + :path: "../flutter_module/.ios/Flutter" + FlutterPluginRegistrant: + :path: "../flutter_module/.ios/Flutter/FlutterPluginRegistrant" + +SPEC CHECKSUMS: + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + FlutterPluginRegistrant: 1bf2b93dcc6a731089dfe77f7a867be153c5008f + +PODFILE CHECKSUM: 8512f9d26835e64e2656751293a9f1e09c986b9d + +COCOAPODS: 1.16.2 diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Pods.xcodeproj/project.pbxproj b/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Pods.xcodeproj/project.pbxproj new file mode 100644 index 0000000..8c0ad06 --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Pods.xcodeproj/project.pbxproj @@ -0,0 +1,1276 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXAggregateTarget section */ + 1EFDDC32A34D56D411E640A81DCD9E73 /* Flutter */ = { + isa = PBXAggregateTarget; + buildConfigurationList = B26E11467D023FA6CDCE36F75198812F /* Build configuration list for PBXAggregateTarget "Flutter" */; + buildPhases = ( + ); + dependencies = ( + ); + name = Flutter; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 0B55D92CD2BAF933FE9D276939406D2C /* Pods-ios_content_resizingTests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = BA027B63B056E637B52DC48E997871D7 /* Pods-ios_content_resizingTests-dummy.m */; }; + 1C8B15B53492E657E17E19CD85AA35C0 /* Pods-ios_content_resizing-ios_content_resizingUITests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 740A60CC3FE2346AA1FEB529CDCCD439 /* Pods-ios_content_resizing-ios_content_resizingUITests-dummy.m */; }; + 203789EDA9BC17C4D2BB55B087DE25B8 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 384DDA2CB25005BD6479B5987C619DD4 /* Foundation.framework */; }; + 44F46818A4A76D2DC7F7FDADAB40E781 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 2CE7A004F669D9407A99593AB2AFD491 /* GeneratedPluginRegistrant.m */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; + 4E3441E90BAB7D083993C56E292FA52C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 384DDA2CB25005BD6479B5987C619DD4 /* Foundation.framework */; }; + 4EE0C80B8A3C1107458579AFB4E41741 /* GeneratedPluginRegistrant.h in Headers */ = {isa = PBXBuildFile; fileRef = 7E2D1DC1EC3C3E20E7FB2A0FF3EF4FCE /* GeneratedPluginRegistrant.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5A79D8994BC80675EE4A3F50CA6FEF4F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 384DDA2CB25005BD6479B5987C619DD4 /* Foundation.framework */; }; + 8DFAE2FA34C28A0530B72F4B5D3F0623 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 384DDA2CB25005BD6479B5987C619DD4 /* Foundation.framework */; }; + ACC932BEF12584E78B91E93346CB63E2 /* FlutterPluginRegistrant-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = DCC08935243F50B63245B117C34B45F3 /* FlutterPluginRegistrant-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B3889D09053A313F7A70C12C7D2B587C /* Pods-ios_content_resizing-ios_content_resizingUITests-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = CC33182B193B07DB144E4A6C9E01AB79 /* Pods-ios_content_resizing-ios_content_resizingUITests-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B6AE8F15F2F866E8B52DC17217A64E71 /* Pods-ios_content_resizingTests-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 715630C735F4805B3A805CF50A9585F3 /* Pods-ios_content_resizingTests-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C31439779167DBFB0C2D6E1D2F3C673C /* Pods-ios_content_resizing-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 31098EA55F47B77CF6DE1E5F5057F68A /* Pods-ios_content_resizing-dummy.m */; }; + D065E4911A1DC5E493EEF7DB517B7214 /* FlutterPluginRegistrant-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 2AAF585701677AEC4242C6CA033275B4 /* FlutterPluginRegistrant-dummy.m */; }; + D082208849F456F5518E6230A683096B /* Pods-ios_content_resizing-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = F732C0211A045562F5E572138528CEB7 /* Pods-ios_content_resizing-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 0F89DBD5A9470B70308A8E62C10B4DC1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1EFDDC32A34D56D411E640A81DCD9E73; + remoteInfo = Flutter; + }; + 2C9791B40262CCA18DEA3CC7B7E1C667 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1EFDDC32A34D56D411E640A81DCD9E73; + remoteInfo = Flutter; + }; + 30CCE7DF2B096D489C0337201A23FEAB /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 83DAA8F43D74F8D51203DE23C7C3A3F5; + remoteInfo = FlutterPluginRegistrant; + }; + 78511F1C1FD5C9BCB89672C1D4CF0475 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = CD1FE7E172044780FE5855099C6F9044; + remoteInfo = "Pods-ios_content_resizing"; + }; + 7948C6A42ED494BC018F1377A88CB2A6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1EFDDC32A34D56D411E640A81DCD9E73; + remoteInfo = Flutter; + }; + FC692C20666B5D147B676FA8171775D9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 83DAA8F43D74F8D51203DE23C7C3A3F5; + remoteInfo = FlutterPluginRegistrant; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 1B2A7B60A690627987F4A32728B1C441 /* Pods-ios_content_resizing-ios_content_resizingUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-ios_content_resizing-ios_content_resizingUITests.debug.xcconfig"; sourceTree = ""; }; + 1CD0339A7DDFF44C520DC672454F4CE6 /* Pods-ios_content_resizing-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-ios_content_resizing-acknowledgements.markdown"; sourceTree = ""; }; + 1D1E78871CC9F2B1032560E19646993A /* FlutterPluginRegistrant.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FlutterPluginRegistrant.release.xcconfig; sourceTree = ""; }; + 1DB7953AE371D07C9F6F2864A55214EC /* Pods-ios_content_resizingTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-ios_content_resizingTests.release.xcconfig"; sourceTree = ""; }; + 2AAF585701677AEC4242C6CA033275B4 /* FlutterPluginRegistrant-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "FlutterPluginRegistrant-dummy.m"; sourceTree = ""; }; + 2ABF8D83AB2BF46EE272C48561AC5A9E /* Pods-ios_content_resizing.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-ios_content_resizing.release.xcconfig"; sourceTree = ""; }; + 2CE7A004F669D9407A99593AB2AFD491 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = GeneratedPluginRegistrant.m; path = Classes/GeneratedPluginRegistrant.m; sourceTree = ""; }; + 31098EA55F47B77CF6DE1E5F5057F68A /* Pods-ios_content_resizing-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-ios_content_resizing-dummy.m"; sourceTree = ""; }; + 384DDA2CB25005BD6479B5987C619DD4 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + 3E69713BE606BC991F1C594C917D928F /* Pods-ios_content_resizing-ios_content_resizingUITests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-ios_content_resizing-ios_content_resizingUITests-acknowledgements.markdown"; sourceTree = ""; }; + 4A162C470ECD31CC6B84AE1812A68341 /* Pods-ios_content_resizing-ios_content_resizingUITests-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-ios_content_resizing-ios_content_resizingUITests-acknowledgements.plist"; sourceTree = ""; }; + 4C51364643265E0627F1B989ABA783AC /* FlutterPluginRegistrant-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "FlutterPluginRegistrant-Info.plist"; sourceTree = ""; }; + 4F9125ED2B32F99F97EDBFBE15D053F3 /* Pods-ios_content_resizingTests.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-ios_content_resizingTests.modulemap"; sourceTree = ""; }; + 550F9C4A71235EA01D58F70B41B5E3B9 /* Pods-ios_content_resizingTests-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-ios_content_resizingTests-acknowledgements.plist"; sourceTree = ""; }; + 5793332C56B5C51F3F1A35E29F0F9B5C /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; path = README.md; sourceTree = ""; }; + 5A9ED177DD476853C074763AE7635D34 /* FlutterPluginRegistrant.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = FlutterPluginRegistrant.debug.xcconfig; sourceTree = ""; }; + 5E6A76AF8DCCA1B2712836D571D61A68 /* Flutter.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; path = Flutter.podspec; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 6056F33DB108BE122160D138498C99F8 /* Pods-ios_content_resizing-ios_content_resizingUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-ios_content_resizing-ios_content_resizingUITests.release.xcconfig"; sourceTree = ""; }; + 715630C735F4805B3A805CF50A9585F3 /* Pods-ios_content_resizingTests-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-ios_content_resizingTests-umbrella.h"; sourceTree = ""; }; + 7190D3A2421CEF0390C6C11ED312EB1B /* Flutter.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Flutter.release.xcconfig; sourceTree = ""; }; + 740A60CC3FE2346AA1FEB529CDCCD439 /* Pods-ios_content_resizing-ios_content_resizingUITests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-ios_content_resizing-ios_content_resizingUITests-dummy.m"; sourceTree = ""; }; + 7E2D1DC1EC3C3E20E7FB2A0FF3EF4FCE /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = GeneratedPluginRegistrant.h; path = Classes/GeneratedPluginRegistrant.h; sourceTree = ""; }; + 808964E349CF6A9C44A0909D0D6EEE8E /* Pods-ios_content_resizing.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-ios_content_resizing.debug.xcconfig"; sourceTree = ""; }; + 80CEA24FF2084B8F57DFDFCC07F2064A /* Pods-ios_content_resizing-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-ios_content_resizing-acknowledgements.plist"; sourceTree = ""; }; + 848315FC91D61DDA12BFE10AF7903DD9 /* Pods-ios_content_resizingTests */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = "Pods-ios_content_resizingTests"; path = Pods_ios_content_resizingTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 863E29E35DF7586D71558E0FB794DDBC /* Pods-ios_content_resizing.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-ios_content_resizing.modulemap"; sourceTree = ""; }; + 89F5E495C830BAE2B7079C84C449077F /* FlutterPluginRegistrant-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "FlutterPluginRegistrant-prefix.pch"; sourceTree = ""; }; + 95DFED33AEBE20CFC59AC2D89DF45F13 /* Pods-ios_content_resizingTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-ios_content_resizingTests.debug.xcconfig"; sourceTree = ""; }; + 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + B1B78154C420D3C3E3BB30C73471AF58 /* Pods-ios_content_resizing */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = "Pods-ios_content_resizing"; path = Pods_ios_content_resizing.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B6420A4EFE167F7BACB091FE384D2AFB /* FlutterPluginRegistrant.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; path = FlutterPluginRegistrant.podspec; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + BA027B63B056E637B52DC48E997871D7 /* Pods-ios_content_resizingTests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-ios_content_resizingTests-dummy.m"; sourceTree = ""; }; + BB6445B421DA90DD6610EE80D499E3B4 /* Flutter.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Flutter.debug.xcconfig; sourceTree = ""; }; + CC33182B193B07DB144E4A6C9E01AB79 /* Pods-ios_content_resizing-ios_content_resizingUITests-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-ios_content_resizing-ios_content_resizingUITests-umbrella.h"; sourceTree = ""; }; + D8740969C1272A3D14589F37950232B7 /* Pods-ios_content_resizing-ios_content_resizingUITests.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-ios_content_resizing-ios_content_resizingUITests.modulemap"; sourceTree = ""; }; + D8DBE60F83CC9855059B3B56F3C86603 /* FlutterPluginRegistrant.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = FlutterPluginRegistrant.modulemap; sourceTree = ""; }; + DAC271DF29EEC36C2D6DF4B29D00059D /* Pods-ios_content_resizing-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-ios_content_resizing-Info.plist"; sourceTree = ""; }; + DCC08935243F50B63245B117C34B45F3 /* FlutterPluginRegistrant-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "FlutterPluginRegistrant-umbrella.h"; sourceTree = ""; }; + DEE6C5FCB90FB050631DE9252A0FF907 /* Pods-ios_content_resizing-ios_content_resizingUITests */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = "Pods-ios_content_resizing-ios_content_resizingUITests"; path = Pods_ios_content_resizing_ios_content_resizingUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DF7C6EBB9161EEF650EC12B4BB67E628 /* FlutterPluginRegistrant */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = FlutterPluginRegistrant; path = FlutterPluginRegistrant.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + EB01D38E9B9397013BDA6F2ECD3C116A /* Pods-ios_content_resizingTests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-ios_content_resizingTests-acknowledgements.markdown"; sourceTree = ""; }; + EC58DCEFE8DA0A2BC696E7AD13C037B4 /* Pods-ios_content_resizingTests-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-ios_content_resizingTests-Info.plist"; sourceTree = ""; }; + F0FC688EB853C97544D47655599785D8 /* Pods-ios_content_resizing-ios_content_resizingUITests-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-ios_content_resizing-ios_content_resizingUITests-Info.plist"; sourceTree = ""; }; + F732C0211A045562F5E572138528CEB7 /* Pods-ios_content_resizing-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-ios_content_resizing-umbrella.h"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 3C14517AA188AA0F4644710BDF6F2E0B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 203789EDA9BC17C4D2BB55B087DE25B8 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 830D63870BCE1E7548BFBF07AF254BC6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8DFAE2FA34C28A0530B72F4B5D3F0623 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C85264A7AE5B8D2D6D41BECD6C886F30 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5A79D8994BC80675EE4A3F50CA6FEF4F /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DE8E25759FD7427C882BA4EB71D22E80 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4E3441E90BAB7D083993C56E292FA52C /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 17E661ACE046111F65EB8BC53399F9D5 /* Targets Support Files */ = { + isa = PBXGroup; + children = ( + C8D1B05D3D5016D84F1E8CFC2829BA53 /* Pods-ios_content_resizing */, + AEE7DDCA547716D81E9370F489774C49 /* Pods-ios_content_resizing-ios_content_resizingUITests */, + D89266006DC36009E817D04F8788D146 /* Pods-ios_content_resizingTests */, + ); + name = "Targets Support Files"; + sourceTree = ""; + }; + 2479EE86B1BDE660ABC7156E54E5A81C /* Support Files */ = { + isa = PBXGroup; + children = ( + D8DBE60F83CC9855059B3B56F3C86603 /* FlutterPluginRegistrant.modulemap */, + 2AAF585701677AEC4242C6CA033275B4 /* FlutterPluginRegistrant-dummy.m */, + 4C51364643265E0627F1B989ABA783AC /* FlutterPluginRegistrant-Info.plist */, + 89F5E495C830BAE2B7079C84C449077F /* FlutterPluginRegistrant-prefix.pch */, + DCC08935243F50B63245B117C34B45F3 /* FlutterPluginRegistrant-umbrella.h */, + 5A9ED177DD476853C074763AE7635D34 /* FlutterPluginRegistrant.debug.xcconfig */, + 1D1E78871CC9F2B1032560E19646993A /* FlutterPluginRegistrant.release.xcconfig */, + ); + name = "Support Files"; + path = "../../../../ios_content_resizing/Pods/Target Support Files/FlutterPluginRegistrant"; + sourceTree = ""; + }; + 6F7106898BE13FDCFDCF877524DD896B /* Products */ = { + isa = PBXGroup; + children = ( + DF7C6EBB9161EEF650EC12B4BB67E628 /* FlutterPluginRegistrant */, + B1B78154C420D3C3E3BB30C73471AF58 /* Pods-ios_content_resizing */, + DEE6C5FCB90FB050631DE9252A0FF907 /* Pods-ios_content_resizing-ios_content_resizingUITests */, + 848315FC91D61DDA12BFE10AF7903DD9 /* Pods-ios_content_resizingTests */, + ); + name = Products; + sourceTree = ""; + }; + A95135DC3DC55EDD6E2AE851A351550A /* Development Pods */ = { + isa = PBXGroup; + children = ( + C4828404A16E02F8503F54C1BEFB50AD /* Flutter */, + E236746D72969B68FE0982CAC2FA077D /* FlutterPluginRegistrant */, + ); + name = "Development Pods"; + sourceTree = ""; + }; + AEE7DDCA547716D81E9370F489774C49 /* Pods-ios_content_resizing-ios_content_resizingUITests */ = { + isa = PBXGroup; + children = ( + D8740969C1272A3D14589F37950232B7 /* Pods-ios_content_resizing-ios_content_resizingUITests.modulemap */, + 3E69713BE606BC991F1C594C917D928F /* Pods-ios_content_resizing-ios_content_resizingUITests-acknowledgements.markdown */, + 4A162C470ECD31CC6B84AE1812A68341 /* Pods-ios_content_resizing-ios_content_resizingUITests-acknowledgements.plist */, + 740A60CC3FE2346AA1FEB529CDCCD439 /* Pods-ios_content_resizing-ios_content_resizingUITests-dummy.m */, + F0FC688EB853C97544D47655599785D8 /* Pods-ios_content_resizing-ios_content_resizingUITests-Info.plist */, + CC33182B193B07DB144E4A6C9E01AB79 /* Pods-ios_content_resizing-ios_content_resizingUITests-umbrella.h */, + 1B2A7B60A690627987F4A32728B1C441 /* Pods-ios_content_resizing-ios_content_resizingUITests.debug.xcconfig */, + 6056F33DB108BE122160D138498C99F8 /* Pods-ios_content_resizing-ios_content_resizingUITests.release.xcconfig */, + ); + name = "Pods-ios_content_resizing-ios_content_resizingUITests"; + path = "Target Support Files/Pods-ios_content_resizing-ios_content_resizingUITests"; + sourceTree = ""; + }; + C4828404A16E02F8503F54C1BEFB50AD /* Flutter */ = { + isa = PBXGroup; + children = ( + C82424DE68A1A19BFC1B640369F4BC62 /* Pod */, + E30233976B4062D1B8D8CBF9AB58FDEF /* Support Files */, + ); + name = Flutter; + path = ../../flutter_module/.ios/Flutter; + sourceTree = ""; + }; + C82424DE68A1A19BFC1B640369F4BC62 /* Pod */ = { + isa = PBXGroup; + children = ( + 5E6A76AF8DCCA1B2712836D571D61A68 /* Flutter.podspec */, + 5793332C56B5C51F3F1A35E29F0F9B5C /* README.md */, + ); + name = Pod; + sourceTree = ""; + }; + C8D1B05D3D5016D84F1E8CFC2829BA53 /* Pods-ios_content_resizing */ = { + isa = PBXGroup; + children = ( + 863E29E35DF7586D71558E0FB794DDBC /* Pods-ios_content_resizing.modulemap */, + 1CD0339A7DDFF44C520DC672454F4CE6 /* Pods-ios_content_resizing-acknowledgements.markdown */, + 80CEA24FF2084B8F57DFDFCC07F2064A /* Pods-ios_content_resizing-acknowledgements.plist */, + 31098EA55F47B77CF6DE1E5F5057F68A /* Pods-ios_content_resizing-dummy.m */, + DAC271DF29EEC36C2D6DF4B29D00059D /* Pods-ios_content_resizing-Info.plist */, + F732C0211A045562F5E572138528CEB7 /* Pods-ios_content_resizing-umbrella.h */, + 808964E349CF6A9C44A0909D0D6EEE8E /* Pods-ios_content_resizing.debug.xcconfig */, + 2ABF8D83AB2BF46EE272C48561AC5A9E /* Pods-ios_content_resizing.release.xcconfig */, + ); + name = "Pods-ios_content_resizing"; + path = "Target Support Files/Pods-ios_content_resizing"; + sourceTree = ""; + }; + CAF78B8E3930ADF8B64CF30FC13AA025 /* Pod */ = { + isa = PBXGroup; + children = ( + B6420A4EFE167F7BACB091FE384D2AFB /* FlutterPluginRegistrant.podspec */, + ); + name = Pod; + sourceTree = ""; + }; + CF1408CF629C7361332E53B88F7BD30C = { + isa = PBXGroup; + children = ( + 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, + A95135DC3DC55EDD6E2AE851A351550A /* Development Pods */, + D210D550F4EA176C3123ED886F8F87F5 /* Frameworks */, + 6F7106898BE13FDCFDCF877524DD896B /* Products */, + 17E661ACE046111F65EB8BC53399F9D5 /* Targets Support Files */, + ); + sourceTree = ""; + }; + D210D550F4EA176C3123ED886F8F87F5 /* Frameworks */ = { + isa = PBXGroup; + children = ( + E4801F62A6B08CD9B5410329F1A18FDE /* iOS */, + ); + name = Frameworks; + sourceTree = ""; + }; + D89266006DC36009E817D04F8788D146 /* Pods-ios_content_resizingTests */ = { + isa = PBXGroup; + children = ( + 4F9125ED2B32F99F97EDBFBE15D053F3 /* Pods-ios_content_resizingTests.modulemap */, + EB01D38E9B9397013BDA6F2ECD3C116A /* Pods-ios_content_resizingTests-acknowledgements.markdown */, + 550F9C4A71235EA01D58F70B41B5E3B9 /* Pods-ios_content_resizingTests-acknowledgements.plist */, + BA027B63B056E637B52DC48E997871D7 /* Pods-ios_content_resizingTests-dummy.m */, + EC58DCEFE8DA0A2BC696E7AD13C037B4 /* Pods-ios_content_resizingTests-Info.plist */, + 715630C735F4805B3A805CF50A9585F3 /* Pods-ios_content_resizingTests-umbrella.h */, + 95DFED33AEBE20CFC59AC2D89DF45F13 /* Pods-ios_content_resizingTests.debug.xcconfig */, + 1DB7953AE371D07C9F6F2864A55214EC /* Pods-ios_content_resizingTests.release.xcconfig */, + ); + name = "Pods-ios_content_resizingTests"; + path = "Target Support Files/Pods-ios_content_resizingTests"; + sourceTree = ""; + }; + E236746D72969B68FE0982CAC2FA077D /* FlutterPluginRegistrant */ = { + isa = PBXGroup; + children = ( + 7E2D1DC1EC3C3E20E7FB2A0FF3EF4FCE /* GeneratedPluginRegistrant.h */, + 2CE7A004F669D9407A99593AB2AFD491 /* GeneratedPluginRegistrant.m */, + CAF78B8E3930ADF8B64CF30FC13AA025 /* Pod */, + 2479EE86B1BDE660ABC7156E54E5A81C /* Support Files */, + ); + name = FlutterPluginRegistrant; + path = ../../flutter_module/.ios/Flutter/FlutterPluginRegistrant; + sourceTree = ""; + }; + E30233976B4062D1B8D8CBF9AB58FDEF /* Support Files */ = { + isa = PBXGroup; + children = ( + BB6445B421DA90DD6610EE80D499E3B4 /* Flutter.debug.xcconfig */, + 7190D3A2421CEF0390C6C11ED312EB1B /* Flutter.release.xcconfig */, + ); + name = "Support Files"; + path = "../../../ios_content_resizing/Pods/Target Support Files/Flutter"; + sourceTree = ""; + }; + E4801F62A6B08CD9B5410329F1A18FDE /* iOS */ = { + isa = PBXGroup; + children = ( + 384DDA2CB25005BD6479B5987C619DD4 /* Foundation.framework */, + ); + name = iOS; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 0FD43C857895FACDAB7AA8C0FF502499 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + B6AE8F15F2F866E8B52DC17217A64E71 /* Pods-ios_content_resizingTests-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 32F610179E9D72EE3A16EB3843931EE5 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ACC932BEF12584E78B91E93346CB63E2 /* FlutterPluginRegistrant-umbrella.h in Headers */, + 4EE0C80B8A3C1107458579AFB4E41741 /* GeneratedPluginRegistrant.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 51C7632BD4576CB1075F90A036533FE9 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + B3889D09053A313F7A70C12C7D2B587C /* Pods-ios_content_resizing-ios_content_resizingUITests-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B2B5AE61D5B2BAC16D2CCA2B5E4F8DEE /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + D082208849F456F5518E6230A683096B /* Pods-ios_content_resizing-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 81D87E4496B08116037E5861D2F209C8 /* Pods-ios_content_resizing-ios_content_resizingUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6B2F3D8F2303282D58B664BE8F2D4EA8 /* Build configuration list for PBXNativeTarget "Pods-ios_content_resizing-ios_content_resizingUITests" */; + buildPhases = ( + 51C7632BD4576CB1075F90A036533FE9 /* Headers */, + 6558434F3ED48ADA5D330EDA3E26529C /* Sources */, + DE8E25759FD7427C882BA4EB71D22E80 /* Frameworks */, + A566FEC23C5B98D2E72A7B131DA712B0 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + EA4B8DFAE72877B05FABD41CFF09E680 /* PBXTargetDependency */, + EDA8F941BE29B16B5B697A2DED6651F5 /* PBXTargetDependency */, + ); + name = "Pods-ios_content_resizing-ios_content_resizingUITests"; + productName = Pods_ios_content_resizing_ios_content_resizingUITests; + productReference = DEE6C5FCB90FB050631DE9252A0FF907 /* Pods-ios_content_resizing-ios_content_resizingUITests */; + productType = "com.apple.product-type.framework"; + }; + 83DAA8F43D74F8D51203DE23C7C3A3F5 /* FlutterPluginRegistrant */ = { + isa = PBXNativeTarget; + buildConfigurationList = C9C0BE58A278116122B314654A0C4A57 /* Build configuration list for PBXNativeTarget "FlutterPluginRegistrant" */; + buildPhases = ( + 32F610179E9D72EE3A16EB3843931EE5 /* Headers */, + EE402EE2699B3EFF89477200DF3CE148 /* Sources */, + C85264A7AE5B8D2D6D41BECD6C886F30 /* Frameworks */, + 41C85A835F7246370834586E809DA47B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 3796C178DFE7E4BF73050579F753935D /* PBXTargetDependency */, + ); + name = FlutterPluginRegistrant; + productName = FlutterPluginRegistrant; + productReference = DF7C6EBB9161EEF650EC12B4BB67E628 /* FlutterPluginRegistrant */; + productType = "com.apple.product-type.framework"; + }; + CD1FE7E172044780FE5855099C6F9044 /* Pods-ios_content_resizing */ = { + isa = PBXNativeTarget; + buildConfigurationList = F446D705FA553D5DDA096E4FA9C58C9A /* Build configuration list for PBXNativeTarget "Pods-ios_content_resizing" */; + buildPhases = ( + B2B5AE61D5B2BAC16D2CCA2B5E4F8DEE /* Headers */, + D8AB908807A6536C9CCC151F2A5AC435 /* Sources */, + 3C14517AA188AA0F4644710BDF6F2E0B /* Frameworks */, + 2C5BDD3A05AF10694FB31F41E7136D9A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 28E6E08F0100197B5A7C30DEE65648FD /* PBXTargetDependency */, + 1DE7FB42D09CCE770F342A633F255B1E /* PBXTargetDependency */, + ); + name = "Pods-ios_content_resizing"; + productName = Pods_ios_content_resizing; + productReference = B1B78154C420D3C3E3BB30C73471AF58 /* Pods-ios_content_resizing */; + productType = "com.apple.product-type.framework"; + }; + E4EA532EB8D753070F4018E9EB3C09A2 /* Pods-ios_content_resizingTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9C81C4875C3CAE4CE50BCD8564492388 /* Build configuration list for PBXNativeTarget "Pods-ios_content_resizingTests" */; + buildPhases = ( + 0FD43C857895FACDAB7AA8C0FF502499 /* Headers */, + 5CEB1B2249C5D86647F8F2BEFA578F1C /* Sources */, + 830D63870BCE1E7548BFBF07AF254BC6 /* Frameworks */, + AF5798AF7A08861DE74D1251C1BE5D0C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 97EE237CECB95E15153F7C32A348F503 /* PBXTargetDependency */, + ); + name = "Pods-ios_content_resizingTests"; + productName = Pods_ios_content_resizingTests; + productReference = 848315FC91D61DDA12BFE10AF7903DD9 /* Pods-ios_content_resizingTests */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + BFDFE7DC352907FC980B868725387E98 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1600; + LastUpgradeCheck = 1600; + }; + buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */; + compatibilityVersion = "Xcode 16.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + en, + ); + mainGroup = CF1408CF629C7361332E53B88F7BD30C; + minimizedProjectReferenceProxies = 0; + preferredProjectObjectVersion = 77; + productRefGroup = 6F7106898BE13FDCFDCF877524DD896B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 1EFDDC32A34D56D411E640A81DCD9E73 /* Flutter */, + 83DAA8F43D74F8D51203DE23C7C3A3F5 /* FlutterPluginRegistrant */, + CD1FE7E172044780FE5855099C6F9044 /* Pods-ios_content_resizing */, + 81D87E4496B08116037E5861D2F209C8 /* Pods-ios_content_resizing-ios_content_resizingUITests */, + E4EA532EB8D753070F4018E9EB3C09A2 /* Pods-ios_content_resizingTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 2C5BDD3A05AF10694FB31F41E7136D9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 41C85A835F7246370834586E809DA47B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A566FEC23C5B98D2E72A7B131DA712B0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AF5798AF7A08861DE74D1251C1BE5D0C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 5CEB1B2249C5D86647F8F2BEFA578F1C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0B55D92CD2BAF933FE9D276939406D2C /* Pods-ios_content_resizingTests-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6558434F3ED48ADA5D330EDA3E26529C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1C8B15B53492E657E17E19CD85AA35C0 /* Pods-ios_content_resizing-ios_content_resizingUITests-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D8AB908807A6536C9CCC151F2A5AC435 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C31439779167DBFB0C2D6E1D2F3C673C /* Pods-ios_content_resizing-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + EE402EE2699B3EFF89477200DF3CE148 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D065E4911A1DC5E493EEF7DB517B7214 /* FlutterPluginRegistrant-dummy.m in Sources */, + 44F46818A4A76D2DC7F7FDADAB40E781 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 1DE7FB42D09CCE770F342A633F255B1E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FlutterPluginRegistrant; + target = 83DAA8F43D74F8D51203DE23C7C3A3F5 /* FlutterPluginRegistrant */; + targetProxy = 30CCE7DF2B096D489C0337201A23FEAB /* PBXContainerItemProxy */; + }; + 28E6E08F0100197B5A7C30DEE65648FD /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Flutter; + target = 1EFDDC32A34D56D411E640A81DCD9E73 /* Flutter */; + targetProxy = 7948C6A42ED494BC018F1377A88CB2A6 /* PBXContainerItemProxy */; + }; + 3796C178DFE7E4BF73050579F753935D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Flutter; + target = 1EFDDC32A34D56D411E640A81DCD9E73 /* Flutter */; + targetProxy = 0F89DBD5A9470B70308A8E62C10B4DC1 /* PBXContainerItemProxy */; + }; + 97EE237CECB95E15153F7C32A348F503 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "Pods-ios_content_resizing"; + target = CD1FE7E172044780FE5855099C6F9044 /* Pods-ios_content_resizing */; + targetProxy = 78511F1C1FD5C9BCB89672C1D4CF0475 /* PBXContainerItemProxy */; + }; + EA4B8DFAE72877B05FABD41CFF09E680 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Flutter; + target = 1EFDDC32A34D56D411E640A81DCD9E73 /* Flutter */; + targetProxy = 2C9791B40262CCA18DEA3CC7B7E1C667 /* PBXContainerItemProxy */; + }; + EDA8F941BE29B16B5B697A2DED6651F5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = FlutterPluginRegistrant; + target = 83DAA8F43D74F8D51203DE23C7C3A3F5 /* FlutterPluginRegistrant */; + targetProxy = FC692C20666B5D147B676FA8171775D9 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 040F80BD2D4B620F91B295DA27374820 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 95DFED33AEBE20CFC59AC2D89DF45F13 /* Pods-ios_content_resizingTests.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = NO; + ENABLE_MODULE_VERIFIER = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + "EXCLUDED_ARCHS[sdk=iphoneos*]" = "$(inherited) armv7"; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "$(inherited) i386"; + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "\"/Users/louisehsu/Development/flutter/bin/cache/artifacts/engine/ios/Flutter.xcframework/ios-arm64\"", + "$(inherited)", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "\"/Users/louisehsu/Development/flutter/bin/cache/artifacts/engine/ios/Flutter.xcframework/ios-arm64_x86_64-simulator\"", + "$(inherited)", + ); + INFOPLIST_FILE = "Target Support Files/Pods-ios_content_resizingTests/Pods-ios_content_resizingTests-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-ios_content_resizingTests/Pods-ios_content_resizingTests.modulemap"; + ONLY_ACTIVE_ARCH = NO; + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + Flutter, + ); + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + "VALID_ARCHS[sdk=iphonesimulator*]" = "$(ARCHS_STANDARD)"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 1602B4EBAE0C64817990D65F3711E6C6 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6056F33DB108BE122160D138498C99F8 /* Pods-ios_content_resizing-ios_content_resizingUITests.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = NO; + ENABLE_MODULE_VERIFIER = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + "EXCLUDED_ARCHS[sdk=iphoneos*]" = "$(inherited) armv7"; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "$(inherited) i386"; + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "\"/Users/louisehsu/Development/flutter/bin/cache/artifacts/engine/ios-release/Flutter.xcframework/ios-arm64\"", + "$(inherited)", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "\"/Users/louisehsu/Development/flutter/bin/cache/artifacts/engine/ios-release/Flutter.xcframework/ios-arm64_x86_64-simulator\"", + "$(inherited)", + ); + INFOPLIST_FILE = "Target Support Files/Pods-ios_content_resizing-ios_content_resizingUITests/Pods-ios_content_resizing-ios_content_resizingUITests-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-ios_content_resizing-ios_content_resizingUITests/Pods-ios_content_resizing-ios_content_resizingUITests.modulemap"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + Flutter, + ); + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + "VALID_ARCHS[sdk=iphonesimulator*]" = "$(ARCHS_STANDARD)"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 7CA996BECDFC2548E01D9AF66C171E89 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1B2A7B60A690627987F4A32728B1C441 /* Pods-ios_content_resizing-ios_content_resizingUITests.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = NO; + ENABLE_MODULE_VERIFIER = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + "EXCLUDED_ARCHS[sdk=iphoneos*]" = "$(inherited) armv7"; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "$(inherited) i386"; + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "\"/Users/louisehsu/Development/flutter/bin/cache/artifacts/engine/ios/Flutter.xcframework/ios-arm64\"", + "$(inherited)", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "\"/Users/louisehsu/Development/flutter/bin/cache/artifacts/engine/ios/Flutter.xcframework/ios-arm64_x86_64-simulator\"", + "$(inherited)", + ); + INFOPLIST_FILE = "Target Support Files/Pods-ios_content_resizing-ios_content_resizingUITests/Pods-ios_content_resizing-ios_content_resizingUITests-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-ios_content_resizing-ios_content_resizingUITests/Pods-ios_content_resizing-ios_content_resizingUITests.modulemap"; + ONLY_ACTIVE_ARCH = NO; + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + Flutter, + ); + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + "VALID_ARCHS[sdk=iphonesimulator*]" = "$(ARCHS_STANDARD)"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + A3CA710B05DD0D38FF0C61760F3B99FB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "POD_CONFIGURATION_DEBUG=1", + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Debug; + }; + B0AD22997967310A1650F12017E3C2C7 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1D1E78871CC9F2B1032560E19646993A /* FlutterPluginRegistrant.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = NO; + ENABLE_MODULE_VERIFIER = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + "EXCLUDED_ARCHS[sdk=iphoneos*]" = "$(inherited) armv7"; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "$(inherited) i386"; + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "\"/Users/louisehsu/Development/flutter/bin/cache/artifacts/engine/ios-release/Flutter.xcframework/ios-arm64\"", + "$(inherited)", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "\"/Users/louisehsu/Development/flutter/bin/cache/artifacts/engine/ios-release/Flutter.xcframework/ios-arm64_x86_64-simulator\"", + "$(inherited)", + ); + GCC_PREFIX_HEADER = "Target Support Files/FlutterPluginRegistrant/FlutterPluginRegistrant-prefix.pch"; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = "Target Support Files/FlutterPluginRegistrant/FlutterPluginRegistrant-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/FlutterPluginRegistrant/FlutterPluginRegistrant.modulemap"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + Flutter, + ); + PRODUCT_MODULE_NAME = FlutterPluginRegistrant; + PRODUCT_NAME = FlutterPluginRegistrant; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_INSTALL_OBJC_HEADER = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + "VALID_ARCHS[sdk=iphonesimulator*]" = "$(ARCHS_STANDARD)"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + CB3A369004330A68D6CB0D804AE27792 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7190D3A2421CEF0390C6C11ED312EB1B /* Flutter.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_OBJC_WEAK = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + D8D83461AC915BEDCB4A478E1E489628 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BB6445B421DA90DD6610EE80D499E3B4 /* Flutter.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_OBJC_WEAK = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + ONLY_ACTIVE_ARCH = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + DFFBA46802CC7CCEBC5643E3F5B22C07 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5A9ED177DD476853C074763AE7635D34 /* FlutterPluginRegistrant.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = NO; + ENABLE_MODULE_VERIFIER = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + "EXCLUDED_ARCHS[sdk=iphoneos*]" = "$(inherited) armv7"; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "$(inherited) i386"; + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "\"/Users/louisehsu/Development/flutter/bin/cache/artifacts/engine/ios/Flutter.xcframework/ios-arm64\"", + "$(inherited)", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "\"/Users/louisehsu/Development/flutter/bin/cache/artifacts/engine/ios/Flutter.xcframework/ios-arm64_x86_64-simulator\"", + "$(inherited)", + ); + GCC_PREFIX_HEADER = "Target Support Files/FlutterPluginRegistrant/FlutterPluginRegistrant-prefix.pch"; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = "Target Support Files/FlutterPluginRegistrant/FlutterPluginRegistrant-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/FlutterPluginRegistrant/FlutterPluginRegistrant.modulemap"; + ONLY_ACTIVE_ARCH = NO; + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + Flutter, + ); + PRODUCT_MODULE_NAME = FlutterPluginRegistrant; + PRODUCT_NAME = FlutterPluginRegistrant; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_INSTALL_OBJC_HEADER = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + "VALID_ARCHS[sdk=iphonesimulator*]" = "$(ARCHS_STANDARD)"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + E2364721D6682F8B9E80045232309A09 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "POD_CONFIGURATION_RELEASE=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Release; + }; + F955CE243E6CED064AA3566C5AB2B4C0 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2ABF8D83AB2BF46EE272C48561AC5A9E /* Pods-ios_content_resizing.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = NO; + ENABLE_MODULE_VERIFIER = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + "EXCLUDED_ARCHS[sdk=iphoneos*]" = "$(inherited) armv7"; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "$(inherited) i386"; + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "\"/Users/louisehsu/Development/flutter/bin/cache/artifacts/engine/ios-release/Flutter.xcframework/ios-arm64\"", + "$(inherited)", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "\"/Users/louisehsu/Development/flutter/bin/cache/artifacts/engine/ios-release/Flutter.xcframework/ios-arm64_x86_64-simulator\"", + "$(inherited)", + ); + INFOPLIST_FILE = "Target Support Files/Pods-ios_content_resizing/Pods-ios_content_resizing-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-ios_content_resizing/Pods-ios_content_resizing.modulemap"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + Flutter, + ); + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + "VALID_ARCHS[sdk=iphonesimulator*]" = "$(ARCHS_STANDARD)"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + FCDBA9CEDD692CC12B081D6F97592E39 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1DB7953AE371D07C9F6F2864A55214EC /* Pods-ios_content_resizingTests.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = NO; + ENABLE_MODULE_VERIFIER = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + "EXCLUDED_ARCHS[sdk=iphoneos*]" = "$(inherited) armv7"; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "$(inherited) i386"; + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "\"/Users/louisehsu/Development/flutter/bin/cache/artifacts/engine/ios-release/Flutter.xcframework/ios-arm64\"", + "$(inherited)", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "\"/Users/louisehsu/Development/flutter/bin/cache/artifacts/engine/ios-release/Flutter.xcframework/ios-arm64_x86_64-simulator\"", + "$(inherited)", + ); + INFOPLIST_FILE = "Target Support Files/Pods-ios_content_resizingTests/Pods-ios_content_resizingTests-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-ios_content_resizingTests/Pods-ios_content_resizingTests.modulemap"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + Flutter, + ); + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + "VALID_ARCHS[sdk=iphonesimulator*]" = "$(ARCHS_STANDARD)"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + FD819B07C2E487E567F75DEA02AA0254 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 808964E349CF6A9C44A0909D0D6EEE8E /* Pods-ios_content_resizing.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = NO; + ENABLE_MODULE_VERIFIER = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + "EXCLUDED_ARCHS[sdk=iphoneos*]" = "$(inherited) armv7"; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "$(inherited) i386"; + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "\"/Users/louisehsu/Development/flutter/bin/cache/artifacts/engine/ios/Flutter.xcframework/ios-arm64\"", + "$(inherited)", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "\"/Users/louisehsu/Development/flutter/bin/cache/artifacts/engine/ios/Flutter.xcframework/ios-arm64_x86_64-simulator\"", + "$(inherited)", + ); + INFOPLIST_FILE = "Target Support Files/Pods-ios_content_resizing/Pods-ios_content_resizing-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-ios_content_resizing/Pods-ios_content_resizing.modulemap"; + ONLY_ACTIVE_ARCH = NO; + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + Flutter, + ); + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + "VALID_ARCHS[sdk=iphonesimulator*]" = "$(ARCHS_STANDARD)"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A3CA710B05DD0D38FF0C61760F3B99FB /* Debug */, + E2364721D6682F8B9E80045232309A09 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6B2F3D8F2303282D58B664BE8F2D4EA8 /* Build configuration list for PBXNativeTarget "Pods-ios_content_resizing-ios_content_resizingUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7CA996BECDFC2548E01D9AF66C171E89 /* Debug */, + 1602B4EBAE0C64817990D65F3711E6C6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9C81C4875C3CAE4CE50BCD8564492388 /* Build configuration list for PBXNativeTarget "Pods-ios_content_resizingTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 040F80BD2D4B620F91B295DA27374820 /* Debug */, + FCDBA9CEDD692CC12B081D6F97592E39 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + B26E11467D023FA6CDCE36F75198812F /* Build configuration list for PBXAggregateTarget "Flutter" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D8D83461AC915BEDCB4A478E1E489628 /* Debug */, + CB3A369004330A68D6CB0D804AE27792 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C9C0BE58A278116122B314654A0C4A57 /* Build configuration list for PBXNativeTarget "FlutterPluginRegistrant" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DFFBA46802CC7CCEBC5643E3F5B22C07 /* Debug */, + B0AD22997967310A1650F12017E3C2C7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F446D705FA553D5DDA096E4FA9C58C9A /* Build configuration list for PBXNativeTarget "Pods-ios_content_resizing" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FD819B07C2E487E567F75DEA02AA0254 /* Debug */, + F955CE243E6CED064AA3566C5AB2B4C0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = BFDFE7DC352907FC980B868725387E98 /* Project object */; +} diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Pods.xcodeproj/xcuserdata/louisehsu.xcuserdatad/xcschemes/Flutter.xcscheme b/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Pods.xcodeproj/xcuserdata/louisehsu.xcuserdatad/xcschemes/Flutter.xcscheme new file mode 100644 index 0000000..efb76cc --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Pods.xcodeproj/xcuserdata/louisehsu.xcuserdatad/xcschemes/Flutter.xcscheme @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Pods.xcodeproj/xcuserdata/louisehsu.xcuserdatad/xcschemes/FlutterPluginRegistrant.xcscheme b/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Pods.xcodeproj/xcuserdata/louisehsu.xcuserdatad/xcschemes/FlutterPluginRegistrant.xcscheme new file mode 100644 index 0000000..629d12c --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Pods.xcodeproj/xcuserdata/louisehsu.xcuserdatad/xcschemes/FlutterPluginRegistrant.xcscheme @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Pods.xcodeproj/xcuserdata/louisehsu.xcuserdatad/xcschemes/Pods-ios_content_resizing-ios_content_resizingUITests.xcscheme b/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Pods.xcodeproj/xcuserdata/louisehsu.xcuserdatad/xcschemes/Pods-ios_content_resizing-ios_content_resizingUITests.xcscheme new file mode 100644 index 0000000..f6460fe --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Pods.xcodeproj/xcuserdata/louisehsu.xcuserdatad/xcschemes/Pods-ios_content_resizing-ios_content_resizingUITests.xcscheme @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Pods.xcodeproj/xcuserdata/louisehsu.xcuserdatad/xcschemes/Pods-ios_content_resizing.xcscheme b/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Pods.xcodeproj/xcuserdata/louisehsu.xcuserdatad/xcschemes/Pods-ios_content_resizing.xcscheme new file mode 100644 index 0000000..ff91729 --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Pods.xcodeproj/xcuserdata/louisehsu.xcuserdatad/xcschemes/Pods-ios_content_resizing.xcscheme @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Pods.xcodeproj/xcuserdata/louisehsu.xcuserdatad/xcschemes/Pods-ios_content_resizingTests.xcscheme b/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Pods.xcodeproj/xcuserdata/louisehsu.xcuserdatad/xcschemes/Pods-ios_content_resizingTests.xcscheme new file mode 100644 index 0000000..c080fe8 --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Pods.xcodeproj/xcuserdata/louisehsu.xcuserdatad/xcschemes/Pods-ios_content_resizingTests.xcscheme @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Pods.xcodeproj/xcuserdata/louisehsu.xcuserdatad/xcschemes/xcschememanagement.plist b/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Pods.xcodeproj/xcuserdata/louisehsu.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..b602389 --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/Pods/Pods.xcodeproj/xcuserdata/louisehsu.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,46 @@ + + + + + SchemeUserState + + Flutter.xcscheme + + isShown + + orderHint + 0 + + FlutterPluginRegistrant.xcscheme + + isShown + + orderHint + 1 + + Pods-ios_content_resizing-ios_content_resizingUITests.xcscheme + + isShown + + orderHint + 3 + + Pods-ios_content_resizing.xcscheme + + isShown + + orderHint + 2 + + Pods-ios_content_resizingTests.xcscheme + + isShown + + orderHint + 4 + + + SuppressBuildableAutocreation + + + diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/.gitignore b/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/.gitignore new file mode 100644 index 0000000..cdecf14 --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/.gitignore @@ -0,0 +1,41 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +*.swp +profile + +DerivedData/ + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +build/ +.android/ +.ios/ +.flutter-plugins diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/.metadata b/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/.metadata new file mode 100644 index 0000000..194fb3c --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 532a8fed41a4f6595965f02f3edf9666ba5ebf44 + channel: master + +project_type: module diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/README.md b/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/README.md new file mode 100644 index 0000000..092c7d1 --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/README.md @@ -0,0 +1,14 @@ +# flutter_module + +An example Flutter module used in the Flutter add-to-app samples. For more +information on how to use it, see the [README.md](../README.md) parent +directory. + +## 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). diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/analysis_options.yaml b/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/analysis_options.yaml new file mode 100644 index 0000000..e69de29 diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/lib/main.dart b/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/lib/main.dart new file mode 100644 index 0000000..15cfcba --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/lib/main.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; + +void main() { + runApp(const MyApp()); +} + +/// The main application widget for the Fruit Catalog. +class MyApp extends StatefulWidget { + /// Creates the [MyApp]. + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + int _counter = 1; + void _incrementCounter() { + setState(() { + if (_counter > 40) { + _counter = 1; + } + _counter++; + }); + } + + @override + Widget build(BuildContext context) { + return Center( + heightFactor: 1, + child: Directionality( + textDirection: TextDirection.ltr, + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + for (int i = 0; i < _counter; i++) + Text( + "Hello from Flutter $i", + style: TextStyle(color: Colors.pink), + ), + Padding( + padding: const EdgeInsets.fromLTRB(8.0, 50, 8.0, 8.0), + child: ElevatedButton( + onPressed: _incrementCounter, + child: Text("Add to list"), + ), + ), + ], + ), + ), + ); + } +} diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/pubspec.yaml b/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/pubspec.yaml new file mode 100644 index 0000000..e6b38a3 --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/pubspec.yaml @@ -0,0 +1,35 @@ +name: flutter_module +description: An example Flutter module. + +version: 1.0.0+1 + +environment: + sdk: ^3.7.0-0 + +dependencies: + flutter: + sdk: flutter + provider: ^6.0.2 + +dev_dependencies: + analysis_defaults: + path: ../../../../analysis_defaults + flutter_test: + sdk: flutter + flutter_driver: + sdk: flutter + espresso: ">=0.2.0 <0.5.0" + +flutter: + uses-material-design: true + + # This section identifies your Flutter project as a module meant for + # embedding in a native host app. These identifiers should _not_ ordinarily + # be changed after generation - they are used to ensure that the tooling can + # maintain consistency when adding or modifying assets and plugins. + # They also do not have any bearing on your native host application's + # identifiers, which may be completely independent or the same as these. + module: + androidX: true + androidPackage: dev.flutter.example.flutter_module + iosBundleIdentifier: dev.flutter.example.flutterModule diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/test/widget_test.dart b/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/test/widget_test.dart new file mode 100644 index 0000000..e16884a --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/test/widget_test.dart @@ -0,0 +1,47 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:flutter_module/main.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; + +class MockCounterModel extends ChangeNotifier implements CounterModel { + int _count = 0; + + @override + int get count => _count; + + @override + void increment() { + _count++; + notifyListeners(); + } +} + +void main() { + testWidgets('MiniView smoke test', (tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: ChangeNotifierProvider.value( + value: MockCounterModel(), + child: const Contents(), + ), + ), + ); + + // Verify that our counter starts at 0. + expect(find.text('Taps: 0'), findsOneWidget); + expect(find.text('Taps: 1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.text('Tap me!')); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('Taps: 0'), findsNothing); + expect(find.text('Taps: 1'), findsOneWidget); + }); +} diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/test_driver/example.dart b/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/test_driver/example.dart new file mode 100644 index 0000000..5f1e453 --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/flutter_module/test_driver/example.dart @@ -0,0 +1,13 @@ +// Copyright 2020 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. + +import 'package:flutter_driver/driver_extension.dart'; +import 'package:flutter_module/main.dart' as app; + +// This alternate entrypoint is used for espresso testing. See +// https://pub.dev/packages/espresso for details. +void main() { + enableFlutterDriverExtension(); + app.main(); +} diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing.xcodeproj/project.pbxproj b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing.xcodeproj/project.pbxproj new file mode 100644 index 0000000..05ee1e0 --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing.xcodeproj/project.pbxproj @@ -0,0 +1,719 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 7CAC012226BEEB661A102697 /* Pods_ios_content_resizingTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E04F6A34F21CD3BE6A257DDB /* Pods_ios_content_resizingTests.framework */; }; + 7F699BB7742B5FAB4C12CE5C /* Pods_ios_content_resizing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 636EB75E6827B94D35B9E15C /* Pods_ios_content_resizing.framework */; }; + E843104071C29E6CE30D637B /* Pods_ios_content_resizing_ios_content_resizingUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 979B0953B3CB94157C961BB5 /* Pods_ios_content_resizing_ios_content_resizingUITests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + F23EDFE72EE0E81C00A217AA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F23EDFC82EE0E81A00A217AA /* Project object */; + proxyType = 1; + remoteGlobalIDString = F23EDFCF2EE0E81A00A217AA; + remoteInfo = ios_content_resizing; + }; + F23EDFF12EE0E81C00A217AA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F23EDFC82EE0E81A00A217AA /* Project object */; + proxyType = 1; + remoteGlobalIDString = F23EDFCF2EE0E81A00A217AA; + remoteInfo = ios_content_resizing; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 21AF0E08BF01F3EA6AF43D73 /* Pods-ios_content_resizing-ios_content_resizingUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios_content_resizing-ios_content_resizingUITests.debug.xcconfig"; path = "Target Support Files/Pods-ios_content_resizing-ios_content_resizingUITests/Pods-ios_content_resizing-ios_content_resizingUITests.debug.xcconfig"; sourceTree = ""; }; + 2FEEB1287F59F7D0F950708C /* Pods-ios_content_resizingTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios_content_resizingTests.release.xcconfig"; path = "Target Support Files/Pods-ios_content_resizingTests/Pods-ios_content_resizingTests.release.xcconfig"; sourceTree = ""; }; + 5FED92584E697C8B2564E54E /* Pods-ios_content_resizingTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios_content_resizingTests.debug.xcconfig"; path = "Target Support Files/Pods-ios_content_resizingTests/Pods-ios_content_resizingTests.debug.xcconfig"; sourceTree = ""; }; + 636EB75E6827B94D35B9E15C /* Pods_ios_content_resizing.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ios_content_resizing.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 979B0953B3CB94157C961BB5 /* Pods_ios_content_resizing_ios_content_resizingUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ios_content_resizing_ios_content_resizingUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C170EDA9CDBAFCCCE6D0E1C0 /* Pods-ios_content_resizing.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios_content_resizing.release.xcconfig"; path = "Target Support Files/Pods-ios_content_resizing/Pods-ios_content_resizing.release.xcconfig"; sourceTree = ""; }; + D301423152989400BB7B71AC /* Pods-ios_content_resizing-ios_content_resizingUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios_content_resizing-ios_content_resizingUITests.release.xcconfig"; path = "Target Support Files/Pods-ios_content_resizing-ios_content_resizingUITests/Pods-ios_content_resizing-ios_content_resizingUITests.release.xcconfig"; sourceTree = ""; }; + D59E29ECBED0852D37A99053 /* Pods-ios_content_resizing.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ios_content_resizing.debug.xcconfig"; path = "Target Support Files/Pods-ios_content_resizing/Pods-ios_content_resizing.debug.xcconfig"; sourceTree = ""; }; + E04F6A34F21CD3BE6A257DDB /* Pods_ios_content_resizingTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ios_content_resizingTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F23EDFD02EE0E81A00A217AA /* ios_content_resizing.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ios_content_resizing.app; sourceTree = BUILT_PRODUCTS_DIR; }; + F23EDFE62EE0E81C00A217AA /* ios_content_resizingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ios_content_resizingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + F23EDFF02EE0E81C00A217AA /* ios_content_resizingUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ios_content_resizingUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + F23EDFF82EE0E81C00A217AA /* Exceptions for "ios_content_resizing" folder in "ios_content_resizing" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = F23EDFCF2EE0E81A00A217AA /* ios_content_resizing */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + F23EDFD22EE0E81A00A217AA /* ios_content_resizing */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + F23EDFF82EE0E81C00A217AA /* Exceptions for "ios_content_resizing" folder in "ios_content_resizing" target */, + ); + path = ios_content_resizing; + sourceTree = ""; + }; + F23EDFE92EE0E81C00A217AA /* ios_content_resizingTests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + ); + path = ios_content_resizingTests; + sourceTree = ""; + }; + F23EDFF32EE0E81C00A217AA /* ios_content_resizingUITests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + ); + path = ios_content_resizingUITests; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + F23EDFCD2EE0E81A00A217AA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7F699BB7742B5FAB4C12CE5C /* Pods_ios_content_resizing.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F23EDFE32EE0E81C00A217AA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7CAC012226BEEB661A102697 /* Pods_ios_content_resizingTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F23EDFED2EE0E81C00A217AA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E843104071C29E6CE30D637B /* Pods_ios_content_resizing_ios_content_resizingUITests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 26A2B7F96A58CC7941B47702 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 636EB75E6827B94D35B9E15C /* Pods_ios_content_resizing.framework */, + 979B0953B3CB94157C961BB5 /* Pods_ios_content_resizing_ios_content_resizingUITests.framework */, + E04F6A34F21CD3BE6A257DDB /* Pods_ios_content_resizingTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 429AA23544B1D6221E46F99C /* Pods */ = { + isa = PBXGroup; + children = ( + D59E29ECBED0852D37A99053 /* Pods-ios_content_resizing.debug.xcconfig */, + C170EDA9CDBAFCCCE6D0E1C0 /* Pods-ios_content_resizing.release.xcconfig */, + 21AF0E08BF01F3EA6AF43D73 /* Pods-ios_content_resizing-ios_content_resizingUITests.debug.xcconfig */, + D301423152989400BB7B71AC /* Pods-ios_content_resizing-ios_content_resizingUITests.release.xcconfig */, + 5FED92584E697C8B2564E54E /* Pods-ios_content_resizingTests.debug.xcconfig */, + 2FEEB1287F59F7D0F950708C /* Pods-ios_content_resizingTests.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + F23EDFC72EE0E81A00A217AA = { + isa = PBXGroup; + children = ( + F23EDFD22EE0E81A00A217AA /* ios_content_resizing */, + F23EDFE92EE0E81C00A217AA /* ios_content_resizingTests */, + F23EDFF32EE0E81C00A217AA /* ios_content_resizingUITests */, + F23EDFD12EE0E81A00A217AA /* Products */, + 429AA23544B1D6221E46F99C /* Pods */, + 26A2B7F96A58CC7941B47702 /* Frameworks */, + ); + sourceTree = ""; + }; + F23EDFD12EE0E81A00A217AA /* Products */ = { + isa = PBXGroup; + children = ( + F23EDFD02EE0E81A00A217AA /* ios_content_resizing.app */, + F23EDFE62EE0E81C00A217AA /* ios_content_resizingTests.xctest */, + F23EDFF02EE0E81C00A217AA /* ios_content_resizingUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + F23EDFCF2EE0E81A00A217AA /* ios_content_resizing */ = { + isa = PBXNativeTarget; + buildConfigurationList = F23EDFF92EE0E81C00A217AA /* Build configuration list for PBXNativeTarget "ios_content_resizing" */; + buildPhases = ( + 04B5F7353A97C8C66DB1345D /* [CP] Check Pods Manifest.lock */, + 0096AB54D225CF9ABA303477 /* [CP-User] Run Flutter Build flutter_module Script */, + F23EDFCC2EE0E81A00A217AA /* Sources */, + F23EDFCD2EE0E81A00A217AA /* Frameworks */, + F23EDFCE2EE0E81A00A217AA /* Resources */, + 81C6A5B8754ABF6932230914 /* [CP-User] Embed Flutter Build flutter_module Script */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + F23EDFD22EE0E81A00A217AA /* ios_content_resizing */, + ); + name = ios_content_resizing; + productName = ios_content_resizing; + productReference = F23EDFD02EE0E81A00A217AA /* ios_content_resizing.app */; + productType = "com.apple.product-type.application"; + }; + F23EDFE52EE0E81C00A217AA /* ios_content_resizingTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = F23EDFFE2EE0E81C00A217AA /* Build configuration list for PBXNativeTarget "ios_content_resizingTests" */; + buildPhases = ( + D845714EE96E84A345CC5AFB /* [CP] Check Pods Manifest.lock */, + F23EDFE22EE0E81C00A217AA /* Sources */, + F23EDFE32EE0E81C00A217AA /* Frameworks */, + F23EDFE42EE0E81C00A217AA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + F23EDFE82EE0E81C00A217AA /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + F23EDFE92EE0E81C00A217AA /* ios_content_resizingTests */, + ); + name = ios_content_resizingTests; + productName = ios_content_resizingTests; + productReference = F23EDFE62EE0E81C00A217AA /* ios_content_resizingTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + F23EDFEF2EE0E81C00A217AA /* ios_content_resizingUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = F23EE0012EE0E81C00A217AA /* Build configuration list for PBXNativeTarget "ios_content_resizingUITests" */; + buildPhases = ( + CD035F8822407F4FA59AE066 /* [CP] Check Pods Manifest.lock */, + F23EDFEC2EE0E81C00A217AA /* Sources */, + F23EDFED2EE0E81C00A217AA /* Frameworks */, + F23EDFEE2EE0E81C00A217AA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + F23EDFF22EE0E81C00A217AA /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + F23EDFF32EE0E81C00A217AA /* ios_content_resizingUITests */, + ); + name = ios_content_resizingUITests; + productName = ios_content_resizingUITests; + productReference = F23EDFF02EE0E81C00A217AA /* ios_content_resizingUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + F23EDFC82EE0E81A00A217AA /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1640; + LastUpgradeCheck = 1640; + TargetAttributes = { + F23EDFCF2EE0E81A00A217AA = { + CreatedOnToolsVersion = 16.4; + }; + F23EDFE52EE0E81C00A217AA = { + CreatedOnToolsVersion = 16.4; + TestTargetID = F23EDFCF2EE0E81A00A217AA; + }; + F23EDFEF2EE0E81C00A217AA = { + CreatedOnToolsVersion = 16.4; + TestTargetID = F23EDFCF2EE0E81A00A217AA; + }; + }; + }; + buildConfigurationList = F23EDFCB2EE0E81A00A217AA /* Build configuration list for PBXProject "ios_content_resizing" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = F23EDFC72EE0E81A00A217AA; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = F23EDFD12EE0E81A00A217AA /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + F23EDFCF2EE0E81A00A217AA /* ios_content_resizing */, + F23EDFE52EE0E81C00A217AA /* ios_content_resizingTests */, + F23EDFEF2EE0E81C00A217AA /* ios_content_resizingUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + F23EDFCE2EE0E81A00A217AA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F23EDFE42EE0E81C00A217AA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F23EDFEE2EE0E81C00A217AA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 0096AB54D225CF9ABA303477 /* [CP-User] Run Flutter Build flutter_module Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + name = "[CP-User] Run Flutter Build flutter_module Script"; + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -e\nset -u\nsource \"${SRCROOT}/../flutter_module/.ios/Flutter/flutter_export_environment.sh\"\nexport VERBOSE_SCRIPT_LOGGING=1 && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/xcode_backend.sh build"; + }; + 04B5F7353A97C8C66DB1345D /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ios_content_resizing-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 81C6A5B8754ABF6932230914 /* [CP-User] Embed Flutter Build flutter_module Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + name = "[CP-User] Embed Flutter Build flutter_module Script"; + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -e\nset -u\nsource \"${SRCROOT}/../flutter_module/.ios/Flutter/flutter_export_environment.sh\"\nexport VERBOSE_SCRIPT_LOGGING=1 && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/xcode_backend.sh embed_and_thin"; + }; + CD035F8822407F4FA59AE066 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ios_content_resizing-ios_content_resizingUITests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + D845714EE96E84A345CC5AFB /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ios_content_resizingTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + F23EDFCC2EE0E81A00A217AA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F23EDFE22EE0E81C00A217AA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F23EDFEC2EE0E81C00A217AA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + F23EDFE82EE0E81C00A217AA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F23EDFCF2EE0E81A00A217AA /* ios_content_resizing */; + targetProxy = F23EDFE72EE0E81C00A217AA /* PBXContainerItemProxy */; + }; + F23EDFF22EE0E81C00A217AA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F23EDFCF2EE0E81A00A217AA /* ios_content_resizing */; + targetProxy = F23EDFF12EE0E81C00A217AA /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + F23EDFFA2EE0E81C00A217AA /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D59E29ECBED0852D37A99053 /* Pods-ios_content_resizing.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = S8QB4VV633; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ios_content_resizing/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "dev.flutter.plugins.ios-content-resizing"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + F23EDFFB2EE0E81C00A217AA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C170EDA9CDBAFCCCE6D0E1C0 /* Pods-ios_content_resizing.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = S8QB4VV633; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ios_content_resizing/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "dev.flutter.plugins.ios-content-resizing"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + F23EDFFC2EE0E81C00A217AA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = S8QB4VV633; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + F23EDFFD2EE0E81C00A217AA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = S8QB4VV633; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + F23EDFFF2EE0E81C00A217AA /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5FED92584E697C8B2564E54E /* Pods-ios_content_resizingTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = S8QB4VV633; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "dev.flutter.plugins.ios-content-resizingTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ios_content_resizing.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/ios_content_resizing"; + }; + name = Debug; + }; + F23EE0002EE0E81C00A217AA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2FEEB1287F59F7D0F950708C /* Pods-ios_content_resizingTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = S8QB4VV633; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "dev.flutter.plugins.ios-content-resizingTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ios_content_resizing.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/ios_content_resizing"; + }; + name = Release; + }; + F23EE0022EE0E81C00A217AA /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 21AF0E08BF01F3EA6AF43D73 /* Pods-ios_content_resizing-ios_content_resizingUITests.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = S8QB4VV633; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "dev.flutter.plugins.ios-content-resizingUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = ios_content_resizing; + }; + name = Debug; + }; + F23EE0032EE0E81C00A217AA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D301423152989400BB7B71AC /* Pods-ios_content_resizing-ios_content_resizingUITests.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = S8QB4VV633; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "dev.flutter.plugins.ios-content-resizingUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = ios_content_resizing; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + F23EDFCB2EE0E81A00A217AA /* Build configuration list for PBXProject "ios_content_resizing" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F23EDFFC2EE0E81C00A217AA /* Debug */, + F23EDFFD2EE0E81C00A217AA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F23EDFF92EE0E81C00A217AA /* Build configuration list for PBXNativeTarget "ios_content_resizing" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F23EDFFA2EE0E81C00A217AA /* Debug */, + F23EDFFB2EE0E81C00A217AA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F23EDFFE2EE0E81C00A217AA /* Build configuration list for PBXNativeTarget "ios_content_resizingTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F23EDFFF2EE0E81C00A217AA /* Debug */, + F23EE0002EE0E81C00A217AA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F23EE0012EE0E81C00A217AA /* Build configuration list for PBXNativeTarget "ios_content_resizingUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F23EE0022EE0E81C00A217AA /* Debug */, + F23EE0032EE0E81C00A217AA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = F23EDFC82EE0E81A00A217AA /* Project object */; +} diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing.xcodeproj/project.xcworkspace/xcuserdata/louisehsu.xcuserdatad/UserInterfaceState.xcuserstate b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing.xcodeproj/project.xcworkspace/xcuserdata/louisehsu.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..943ac77 Binary files /dev/null and b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing.xcodeproj/project.xcworkspace/xcuserdata/louisehsu.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing.xcodeproj/xcuserdata/louisehsu.xcuserdatad/xcschemes/xcschememanagement.plist b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing.xcodeproj/xcuserdata/louisehsu.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..4ccf2d1 --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing.xcodeproj/xcuserdata/louisehsu.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + ios_content_resizing.xcscheme_^#shared#^_ + + orderHint + 5 + + + + diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing.xcworkspace/contents.xcworkspacedata b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..b278bcd --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing.xcworkspace/xcuserdata/louisehsu.xcuserdatad/UserInterfaceState.xcuserstate b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing.xcworkspace/xcuserdata/louisehsu.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..49fa579 Binary files /dev/null and b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing.xcworkspace/xcuserdata/louisehsu.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/AppDelegate.swift b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/AppDelegate.swift new file mode 100644 index 0000000..1576025 --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/AppDelegate.swift @@ -0,0 +1,14 @@ +import UIKit +import Flutter + +@main +class AppDelegate: FlutterAppDelegate { + // Make the engine lazy to be created when it's first needed. + lazy var flutterEngine = FlutterEngine(name: "my_flutter_engine") + + override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Pre-warms the FlutterEngine. + flutterEngine.run() + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/Assets.xcassets/AccentColor.colorset/Contents.json b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/Assets.xcassets/AppIcon.appiconset/Contents.json b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..2305880 --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/Assets.xcassets/Contents.json b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/Base.lproj/LaunchScreen.storyboard b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..865e932 --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/Base.lproj/Main.storyboard b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/Base.lproj/Main.storyboard new file mode 100644 index 0000000..25a7638 --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/Base.lproj/Main.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/Info.plist b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/Info.plist new file mode 100644 index 0000000..dd3c9af --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/Info.plist @@ -0,0 +1,25 @@ + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + + diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/SceneDelegate.swift b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/SceneDelegate.swift new file mode 100644 index 0000000..a968d29 --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/SceneDelegate.swift @@ -0,0 +1,52 @@ +// +// SceneDelegate.swift +// ios_content_resizing +// +// Created by Louise Hsu on 12/3/25. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let _ = (scene as? UIWindowScene) else { return } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/ViewController.swift b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/ViewController.swift new file mode 100644 index 0000000..1fe4b1e --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizing/ViewController.swift @@ -0,0 +1,87 @@ +import Flutter +import UIKit + +class ViewController: UIViewController { + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .systemGroupedBackground + showStyledScrollView() + } + + func showStyledScrollView() { + let scrollView = UIScrollView() + let stackView = UIStackView() + stackView.axis = .vertical + stackView.spacing = 12 + + let engine = FlutterEngine() + engine.run() + + for index in 1...50 { + if index == 1 { + let flutterViewController = FlutterViewController(engine: engine, nibName: nil, bundle: nil) + flutterViewController.isAutoResizable = true + addChild(flutterViewController) + + flutterViewController.view.backgroundColor = .clear + + let cardView = UIView() + cardView.backgroundColor = .systemBackground + cardView.layer.cornerRadius = 12 + cardView.layer.masksToBounds = true + + flutterViewController.view.translatesAutoresizingMaskIntoConstraints = false + cardView.addSubview(flutterViewController.view) + + NSLayoutConstraint.activate([ + flutterViewController.view.topAnchor.constraint(equalTo: cardView.topAnchor), + flutterViewController.view.leadingAnchor.constraint(equalTo: cardView.leadingAnchor), + flutterViewController.view.trailingAnchor.constraint(equalTo: cardView.trailingAnchor), + flutterViewController.view.bottomAnchor.constraint(equalTo: cardView.bottomAnchor) + ]) + + stackView.addArrangedSubview(cardView) + flutterViewController.didMove(toParent: self) + + } else { + let label = UILabel() + label.text = "iOS Label \(index)" + label.translatesAutoresizingMaskIntoConstraints = false + + let cardView = UIView() + cardView.backgroundColor = .systemBackground + cardView.layer.cornerRadius = 12 + cardView.addSubview(label) + + NSLayoutConstraint.activate([ + label.topAnchor.constraint(equalTo: cardView.topAnchor, constant: 16), + label.bottomAnchor.constraint(equalTo: cardView.bottomAnchor, constant: -16), + label.leadingAnchor.constraint(equalTo: cardView.leadingAnchor, constant: 16), + label.trailingAnchor.constraint(equalTo: cardView.trailingAnchor, constant: -16) + ]) + + stackView.addArrangedSubview(cardView) + } + } + + scrollView.translatesAutoresizingMaskIntoConstraints = false + stackView.translatesAutoresizingMaskIntoConstraints = false + + view.addSubview(scrollView) + scrollView.addSubview(stackView) + + NSLayoutConstraint.activate([ + scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), + scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), + scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor) + ]) + + NSLayoutConstraint.activate([ + stackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor, constant: 20), + stackView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor, constant: -20), + stackView.leadingAnchor.constraint(equalTo: scrollView.frameLayoutGuide.leadingAnchor, constant: 20), + stackView.trailingAnchor.constraint(equalTo: scrollView.frameLayoutGuide.trailingAnchor, constant: -20) + ]) + } +} diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizingTests/ios_content_resizingTests.swift b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizingTests/ios_content_resizingTests.swift new file mode 100644 index 0000000..346452a --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizingTests/ios_content_resizingTests.swift @@ -0,0 +1,36 @@ +// +// ios_content_resizingTests.swift +// ios_content_resizingTests +// +// Created by Louise Hsu on 12/3/25. +// + +import XCTest +@testable import ios_content_resizing + +final class ios_content_resizingTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + // Any test you write for XCTest can be annotated as throws and async. + // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. + // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizingUITests/ios_content_resizingUITests.swift b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizingUITests/ios_content_resizingUITests.swift new file mode 100644 index 0000000..2c0f47a --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizingUITests/ios_content_resizingUITests.swift @@ -0,0 +1,41 @@ +// +// ios_content_resizingUITests.swift +// ios_content_resizingUITests +// +// Created by Louise Hsu on 12/3/25. +// + +import XCTest + +final class ios_content_resizingUITests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + @MainActor + func testExample() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + @MainActor + func testLaunchPerformance() throws { + // This measures how long it takes to launch your application. + measure(metrics: [XCTApplicationLaunchMetric()]) { + XCUIApplication().launch() + } + } +} diff --git a/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizingUITests/ios_content_resizingUITestsLaunchTests.swift b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizingUITests/ios_content_resizingUITestsLaunchTests.swift new file mode 100644 index 0000000..dec91af --- /dev/null +++ b/add_to_app/ios_content_resizing/ios_content_resizing/ios_content_resizingUITests/ios_content_resizingUITestsLaunchTests.swift @@ -0,0 +1,33 @@ +// +// ios_content_resizingUITestsLaunchTests.swift +// ios_content_resizingUITests +// +// Created by Louise Hsu on 12/3/25. +// + +import XCTest + +final class ios_content_resizingUITestsLaunchTests: XCTestCase { + + override class var runsForEachTargetApplicationUIConfiguration: Bool { + true + } + + override func setUpWithError() throws { + continueAfterFailure = false + } + + @MainActor + func testLaunch() throws { + let app = XCUIApplication() + app.launch() + + // Insert steps here to perform after app launch but before taking a screenshot, + // such as logging into a test account or navigating somewhere in the app + + let attachment = XCTAttachment(screenshot: app.screenshot()) + attachment.name = "Launch Screen" + attachment.lifetime = .keepAlways + add(attachment) + } +} diff --git a/add_to_app/multiple_flutters/README.md b/add_to_app/multiple_flutters/README.md new file mode 100644 index 0000000..76b5883 --- /dev/null +++ b/add_to_app/multiple_flutters/README.md @@ -0,0 +1,80 @@ +# multiple_flutters + +This is a sample that shows how to use the Flutter Engine Groups API to embed +multiple instances of Flutter into an existing Android or iOS project. + +## Getting Started + +For iOS instructions, see: +[multiple_flutters_ios](./multiple_flutters_ios/README.md). + +For Android instructions, see: +[multiple_flutters_android](./multiple_flutters_android/README.md). + +## Requirements + +* Flutter -- after Flutter v2 +* Android + * Android Studio +* iOS + * Xcode + * Cocoapods + +## Flutter Engine Group + +These examples use the Flutter Engine Group APIs on the host platform which +allows engines to share memory and CPU intensive resources. This leads to easier +embedding of Flutter into an existing app since multiple entrypoints can be +maintained via a FlutterFragment on Android or a UIViewController on iOS. +Before FlutterEngineGroup, users had to juggle the usage of a small number of +engines judiciously. + +More info on those API's can be found in the source +code: + +- iOS - + [FlutterEngineGroup.h](https://github.com/flutter/engine/blob/master/shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h) +- Android - + [FlutterEngineGroup.java](https://github.com/flutter/engine/blob/master/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java) + +## Important Files + +### iOS + +- [DataModel.swift](./multiple_flutters_ios/MultipleFluttersIos/HostViewController.swift) + β€” Observable data model that is the source of truth for the host platform and Flutter. +- [HostViewController.swift](./multiple_flutters_ios/MultipleFluttersIos/HostViewController.swift) + β€” A UIViewController that synchronizes to the DataModel. +- [main.dart](./multiple_flutters_module/lib/main.dart) β€” The Flutter source + code. +- [SingleFlutterViewController.swift](./multiple_flutters_ios/MultipleFluttersIos/SingleFlutterViewController.swift) + β€” A subclass of FlutterViewController that synchronizes with the DataModel. +- [DoubleFlutterViewController.swift](./multiple_flutters_ios/MultipleFluttersIos/DoubleFlutterViewController.swift) + β€” A UIViewController that embeds multiple Flutter instances. + +### Android + +## Data Synchronization Description + +This sample code performs data synchronization to share data between the host +platform and multiple instances of Flutter by combining the +[Observer design pattern](https://en.wikipedia.org/wiki/Observer_pattern) and +[Flutter platform channels](https://flutter.dev/docs/development/platform-integration/platform-channels). +Here is how it works: + +- The definitive source of truth for the data lives in the host platform data + model. +- Every host view displaying Flutter content maintains: a Flutter engine, a + bidirectional platform channel, and a subscription to the host data model. +- Flutter instances maintain a copy of the data they are interested in reading, + and this data is seeded by the host when the instance is first displayed. +- Mutations from Flutter code are sent to the host platform via the channel. The + host platform performs the mutations, and then notifies all host view + controllers and Flutter engines of the new value. +- Mutations from host code happen directly on the data model who notifies host + view controllers and Flutter engines of the new value. + +This is just one possible way to synchronize the data between the host platform +and multiple Flutter instances. A more complete implementation is proposed in +the work of +[flutter/issues/72030](https://github.com/flutter/flutter/issues/72030). diff --git a/add_to_app/multiple_flutters/multiple_flutters_android/.gitignore b/add_to_app/multiple_flutters/multiple_flutters_android/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_android/.gitignore @@ -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 diff --git a/add_to_app/multiple_flutters/multiple_flutters_android/README.md b/add_to_app/multiple_flutters/multiple_flutters_android/README.md new file mode 100644 index 0000000..b6b394e --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_android/README.md @@ -0,0 +1,17 @@ +# multiple_flutters_android + +This is an add-to-app sample that uses the Flutter engine group API to host +multiple instances of Flutter in the app. + +## Getting Started + +```sh +cd ../multiple_flutters_module +flutter pub get +cd - +open -a "Android Studio" multiple_flutters_android/ # macOS command +# (build and run) +``` + +For more information see +[multiple_flutters_module](../multiple_flutters_module/README.md). diff --git a/add_to_app/multiple_flutters/multiple_flutters_android/app/.gitignore b/add_to_app/multiple_flutters/multiple_flutters_android/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_android/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/add_to_app/multiple_flutters/multiple_flutters_android/app/build.gradle b/add_to_app/multiple_flutters/multiple_flutters_android/app/build.gradle new file mode 100644 index 0000000..6b925d2 --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_android/app/build.gradle @@ -0,0 +1,51 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' +} + +android { + signingConfigs { + self { + } + } + compileSdk 36 + + defaultConfig { + applicationId "dev.flutter.multipleflutters" + 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' + signingConfig debug.signingConfig + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + namespace 'dev.flutter.multipleflutters' +} + +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' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.2.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' + implementation project(':flutter') +} \ No newline at end of file diff --git a/add_to_app/multiple_flutters/multiple_flutters_android/app/proguard-rules.pro b/add_to_app/multiple_flutters/multiple_flutters_android/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_android/app/proguard-rules.pro @@ -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 \ No newline at end of file diff --git a/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/AndroidManifest.xml b/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..4be9623 --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/AndroidManifest.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/java/dev/flutter/multipleflutters/App.kt b/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/java/dev/flutter/multipleflutters/App.kt new file mode 100644 index 0000000..47cd857 --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/java/dev/flutter/multipleflutters/App.kt @@ -0,0 +1,18 @@ +package dev.flutter.multipleflutters + +import android.app.Application +import io.flutter.embedding.engine.FlutterEngineGroup + +/** + * Application class for this app. + * + * This holds onto our engine group. + */ +class App : Application() { + lateinit var engines: FlutterEngineGroup + + override fun onCreate() { + super.onCreate() + engines = FlutterEngineGroup(this) + } +} diff --git a/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/java/dev/flutter/multipleflutters/DataModel.kt b/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/java/dev/flutter/multipleflutters/DataModel.kt new file mode 100644 index 0000000..d405870 --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/java/dev/flutter/multipleflutters/DataModel.kt @@ -0,0 +1,41 @@ +package dev.flutter.multipleflutters + +import java.lang.ref.WeakReference + +/** + * Interface for getting notifications when the DataModel is updated. + */ +interface DataModelObserver { + fun onCountUpdate(newCount: Int) +} + +/** + * A singleton/observable data model for the data shared between Flutter and the host platform. + * + * This is the definitive source of truth for all data. + */ +class DataModel { + companion object { + val instance = DataModel() + } + + private val observers = mutableListOf>() + + public var counter = 0 + set(value) { + field = value + for (observer in observers) { + observer.get()?.onCountUpdate(value) + } + } + + fun addObserver(observer: DataModelObserver) { + observers.add(WeakReference(observer)) + } + + fun removeObserver(observer: DataModelObserver) { + observers.removeIf { + if (it.get() != null) it.get() == observer else true + } + } +} diff --git a/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/java/dev/flutter/multipleflutters/DoubleFlutterActivity.kt b/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/java/dev/flutter/multipleflutters/DoubleFlutterActivity.kt new file mode 100644 index 0000000..a1ef4b7 --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/java/dev/flutter/multipleflutters/DoubleFlutterActivity.kt @@ -0,0 +1,91 @@ +package dev.flutter.multipleflutters + +import android.content.Intent +import android.os.Bundle +import android.widget.FrameLayout +import android.widget.LinearLayout +import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.FragmentManager +import io.flutter.embedding.android.FlutterFragment +import io.flutter.embedding.engine.FlutterEngineCache + +/** + * An activity that displays 2 FlutterFragments vertically. + */ +class DoubleFlutterActivity : FragmentActivity(), EngineBindingsDelegate { + private val topBindings: EngineBindings by lazy { + EngineBindings(activity = this, delegate = this, entrypoint = "topMain") + } + private val bottomBindings: EngineBindings by lazy { + EngineBindings(activity = this, delegate = this, entrypoint = "bottomMain") + } + private val numberOfFlutters = 2 + private val engineCountStart : Int + private companion object { + var engineCounter = 0 + } + + init { + engineCountStart = engineCounter + engineCounter += numberOfFlutters + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val root = LinearLayout(this) + root.layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT + ) + root.orientation = LinearLayout.VERTICAL + root.weightSum = numberOfFlutters.toFloat() + + val fragmentManager: FragmentManager = supportFragmentManager + + setContentView(root) + + for (i in 0 until numberOfFlutters) { + val engineId = engineCountStart + i + val containerId = 12345 + engineId + val flutterContainer = FrameLayout(this) + root.addView(flutterContainer) + flutterContainer.id = containerId + flutterContainer.layoutParams = LinearLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT, + 1.0f + ) + val engine = if (i == 0) topBindings.engine else bottomBindings.engine + FlutterEngineCache.getInstance().put(engineId.toString(), engine) + val flutterFragment = + FlutterFragment.withCachedEngine(engineId.toString()).build() + fragmentManager + .beginTransaction() + .add( + containerId, + flutterFragment + ) + .commit() + } + + topBindings.attach() + bottomBindings.attach() + } + + override fun onDestroy() { + for (i in 0 until numberOfFlutters) { + val engineId = engineCountStart + i + FlutterEngineCache.getInstance().remove(engineId.toString()) + } + + super.onDestroy() + + bottomBindings.detach() + topBindings.detach() + } + + override fun onNext() { + val flutterIntent = Intent(this, MainActivity::class.java) + startActivity(flutterIntent) + } +} diff --git a/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/java/dev/flutter/multipleflutters/EngineBindings.kt b/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/java/dev/flutter/multipleflutters/EngineBindings.kt new file mode 100644 index 0000000..5c5003f --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/java/dev/flutter/multipleflutters/EngineBindings.kt @@ -0,0 +1,85 @@ +package dev.flutter.multipleflutters + +import android.app.Activity +import io.flutter.FlutterInjector +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.embedding.engine.dart.DartExecutor +import io.flutter.plugin.common.MethodChannel + +/** + * This interface represents the notifications an EngineBindings may be receiving from the Flutter + * instance. + * + * What methods this interface has depends on the messages that are sent over the EngineBinding's + * channel in `main.dart`. Messages that interact with the DataModel are handled automatically + * by the EngineBindings. + * + * @see main.dart for what messages are getting sent from Flutter. + */ +interface EngineBindingsDelegate { + fun onNext() +} + +/** + * This binds a FlutterEngine instance with the DataModel and a channel for communicating with that + * engine. + * + * Messages involving the DataModel are handled by the EngineBindings, other messages are forwarded + * to the EngineBindingsDelegate. + * + * @see main.dart for what messages are getting sent from Flutter. + */ +class EngineBindings(activity: Activity, delegate: EngineBindingsDelegate, entrypoint: String) : + DataModelObserver { + val channel: MethodChannel + val engine: FlutterEngine + val delegate: EngineBindingsDelegate + + init { + val app = activity.applicationContext as App + // This has to be lazy to avoid creation before the FlutterEngineGroup. + val dartEntrypoint = + DartExecutor.DartEntrypoint( + FlutterInjector.instance().flutterLoader().findAppBundlePath(), entrypoint + ) + engine = app.engines.createAndRunEngine(activity, dartEntrypoint) + this.delegate = delegate + channel = MethodChannel(engine.dartExecutor.binaryMessenger, "multiple-flutters") + } + + /** + * This setups the messaging connections on the platform channel and the DataModel. + */ + fun attach() { + DataModel.instance.addObserver(this) + channel.invokeMethod("setCount", DataModel.instance.counter) + channel.setMethodCallHandler { call, result -> + when (call.method) { + "incrementCount" -> { + DataModel.instance.counter = DataModel.instance.counter + 1 + result.success(null) + } + "next" -> { + this.delegate.onNext() + result.success(null) + } + else -> { + result.notImplemented() + } + } + } + } + + /** + * This tears down the messaging connections on the platform channel and the DataModel. + */ + fun detach() { + engine.destroy(); + DataModel.instance.removeObserver(this) + channel.setMethodCallHandler(null) + } + + override fun onCountUpdate(newCount: Int) { + channel.invokeMethod("setCount", newCount) + } +} diff --git a/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/java/dev/flutter/multipleflutters/MainActivity.kt b/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/java/dev/flutter/multipleflutters/MainActivity.kt new file mode 100644 index 0000000..8653a9c --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/java/dev/flutter/multipleflutters/MainActivity.kt @@ -0,0 +1,61 @@ +package dev.flutter.multipleflutters + +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.view.View +import android.widget.TextView + +/** + * The activity that uses standard platform UI APIs. + * + * This doesn't talk directly to or render Flutter content and represents a part of your app that + * would potentially already be written that wants to interface with Flutter. + */ +class MainActivity : AppCompatActivity(), DataModelObserver { + private lateinit var countView: TextView + private val mainActivityIdentifier: Int + + private companion object { + /** A count that makes every other MainActivity have 1 or 2 Flutter instances. */ + var mainActivityCount = 0 + } + + init { + mainActivityIdentifier = mainActivityCount + mainActivityCount += 1 + } + + /** Implemented from AppCompactActivity. */ + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + DataModel.instance.addObserver(this) + countView = findViewById(R.id.count) + countView.text = DataModel.instance.counter.toString() + } + + /** Implemented from AppCompactActivity. */ + override fun onDestroy() { + super.onDestroy() + DataModel.instance.removeObserver(this) + } + + /** Event from `activity_main.xml`. */ + fun onClickNext(view: View) { + val nextClass = + if (mainActivityIdentifier % 2 == 0) SingleFlutterActivity::class.java else DoubleFlutterActivity::class.java + val flutterIntent = Intent(this, nextClass) + startActivity(flutterIntent) + } + + /** Event from `activity_main.xml`. */ + fun onClickAdd(view: View) { + DataModel.instance.counter = DataModel.instance.counter + 1 + } + + /** Implemented from DataModelObserver. */ + override fun onCountUpdate(newCount: Int) { + countView.text = newCount.toString() + } +} diff --git a/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/java/dev/flutter/multipleflutters/SingleFlutterActivity.kt b/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/java/dev/flutter/multipleflutters/SingleFlutterActivity.kt new file mode 100644 index 0000000..7316e53 --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/java/dev/flutter/multipleflutters/SingleFlutterActivity.kt @@ -0,0 +1,37 @@ +package dev.flutter.multipleflutters + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine + +/** + * This is an Activity that displays one instance of Flutter. + * + * EngineBindings is used to bridge communication between the Flutter instance and the DataModel. + */ +class SingleFlutterActivity : FlutterActivity(), EngineBindingsDelegate { + private val engineBindings: EngineBindings by lazy { + EngineBindings(activity = this, delegate = this, entrypoint = "main") + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + engineBindings.attach() + } + + override fun onDestroy() { + super.onDestroy() + engineBindings.detach() + } + + override fun provideFlutterEngine(context: Context): FlutterEngine? { + return engineBindings.engine + } + + override fun onNext() { + val flutterIntent = Intent(this, MainActivity::class.java) + startActivity(flutterIntent) + } +} diff --git a/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/res/drawable/ic_launcher_background.xml b/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/res/layout/activity_main.xml b/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..33bc095 --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/add_to_app/multiple_flutters/multiple_flutters_ios/MultipleFluttersIos/DataModel.swift b/add_to_app/multiple_flutters/multiple_flutters_ios/MultipleFluttersIos/DataModel.swift new file mode 100644 index 0000000..26ad3cb --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_ios/MultipleFluttersIos/DataModel.swift @@ -0,0 +1,56 @@ +// Copyright 2021 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. + +import Foundation + +/// A protocol that receives updates when the datamodel is changed. +protocol DataModelObserver: AnyObject { + func onCountUpdate(newCount: Int64) +} + +/// A wrapper object around a weak reference to an object that implements DataModelObserver. +/// +/// This is required since you can't directly hold weak references to protocols in data structures. +private struct DataModelObserverWrapper { + weak var wrapped: DataModelObserver? + + init(_ wrapped: DataModelObserver) { + self.wrapped = wrapped + } +} + +/// A singleton data model that is observable. +class DataModel { + private var _count: Int64 = 0 + var count: Int64 { + get { + return self._count + } + set { + self._count = newValue + for observer in observers { + if let wrapped = observer.wrapped { + wrapped.onCountUpdate(newCount: self._count) + } + } + } + } + private var observers: [DataModelObserverWrapper] = [] + static let shared = DataModel() + + func addObserver(observer: DataModelObserver) { + observers.append(DataModelObserverWrapper(observer)) + } + + func removeObserver(observer: DataModelObserver) { + observers.removeAll { (element: DataModelObserverWrapper) -> Bool in + if let wrapped = element.wrapped { + return wrapped === observer + } else { + // Handle observers who dealloc'd without removing themselves. + return true + } + } + } +} diff --git a/add_to_app/multiple_flutters/multiple_flutters_ios/MultipleFluttersIos/DoubleFlutterViewController.swift b/add_to_app/multiple_flutters/multiple_flutters_ios/MultipleFluttersIos/DoubleFlutterViewController.swift new file mode 100644 index 0000000..8d354bc --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_ios/MultipleFluttersIos/DoubleFlutterViewController.swift @@ -0,0 +1,29 @@ +// Copyright 2021 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. + +import UIKit + +/// A UIViewController that hosts two instances of SingleFlutterViewController, splitting the screen +/// between them. +class DoubleFlutterViewController: UIViewController { + private let topFlutter: SingleFlutterViewController = SingleFlutterViewController( + withEntrypoint: "topMain") + private let bottomFlutter: SingleFlutterViewController = SingleFlutterViewController( + withEntrypoint: "bottomMain") + + override func viewDidLoad() { + addChild(topFlutter) + addChild(bottomFlutter) + let safeFrame = self.view.safeAreaLayoutGuide.layoutFrame + let halfHeight = safeFrame.height / 2.0 + topFlutter.view.frame = CGRect( + x: safeFrame.minX, y: safeFrame.minY, width: safeFrame.width, height: halfHeight) + bottomFlutter.view.frame = CGRect( + x: safeFrame.minX, y: topFlutter.view.frame.maxY, width: safeFrame.width, height: halfHeight) + self.view.addSubview(topFlutter.view) + self.view.addSubview(bottomFlutter.view) + topFlutter.didMove(toParent: self) + bottomFlutter.didMove(toParent: self) + } +} diff --git a/add_to_app/multiple_flutters/multiple_flutters_ios/MultipleFluttersIos/HostViewController.swift b/add_to_app/multiple_flutters/multiple_flutters_ios/MultipleFluttersIos/HostViewController.swift new file mode 100644 index 0000000..060c243 --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_ios/MultipleFluttersIos/HostViewController.swift @@ -0,0 +1,43 @@ +// Copyright 2021 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. + +import Flutter +import UIKit + +/// UIViewController associated with the NativeViewCount storyboard scene. +class HostViewController: UIViewController, DataModelObserver { + @IBOutlet weak var countView: UILabel! + + deinit { + DataModel.shared.removeObserver(observer: self) + } + + func onCountUpdate(newCount: Int64) { + self.countView.text = String(format: "%d", newCount) + } + + override func viewDidLoad() { + super.viewDidLoad() + DataModel.shared.addObserver(observer: self) + onCountUpdate(newCount: DataModel.shared.count) + } + + @IBAction func onAddCount() { + DataModel.shared.count = DataModel.shared.count + 1 + } + + @IBAction func onNext() { + let navController = self.navigationController! + // Based on the number of view controllers in the stack we push a + // SingleFlutterViewController or DoubleFlutterViewController to alternatve + // between the two. + if navController.viewControllers.count % 4 == 1 { + let vc = SingleFlutterViewController(withEntrypoint: nil) + navController.pushViewController(vc, animated: true) + } else { + let vc = DoubleFlutterViewController() + navController.pushViewController(vc, animated: true) + } + } +} diff --git a/add_to_app/multiple_flutters/multiple_flutters_ios/MultipleFluttersIos/Info.plist b/add_to_app/multiple_flutters/multiple_flutters_ios/MultipleFluttersIos/Info.plist new file mode 100644 index 0000000..5b531f7 --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_ios/MultipleFluttersIos/Info.plist @@ -0,0 +1,66 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/add_to_app/multiple_flutters/multiple_flutters_ios/MultipleFluttersIos/SceneDelegate.swift b/add_to_app/multiple_flutters/multiple_flutters_ios/MultipleFluttersIos/SceneDelegate.swift new file mode 100644 index 0000000..ae755e8 --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_ios/MultipleFluttersIos/SceneDelegate.swift @@ -0,0 +1,32 @@ +// Copyright 2021 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. + +import UIKit + +/// The UIWindowSceneDelegate for the app. +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? + + func scene( + _ scene: UIScene, willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + guard let _ = (scene as? UIWindowScene) else { return } + } + + func sceneDidDisconnect(_ scene: UIScene) { + } + + func sceneDidBecomeActive(_ scene: UIScene) { + } + + func sceneWillResignActive(_ scene: UIScene) { + } + + func sceneWillEnterForeground(_ scene: UIScene) { + } + + func sceneDidEnterBackground(_ scene: UIScene) { + } +} diff --git a/add_to_app/multiple_flutters/multiple_flutters_ios/MultipleFluttersIos/SingleFlutterViewController.swift b/add_to_app/multiple_flutters/multiple_flutters_ios/MultipleFluttersIos/SingleFlutterViewController.swift new file mode 100644 index 0000000..892c87c --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_ios/MultipleFluttersIos/SingleFlutterViewController.swift @@ -0,0 +1,59 @@ +// Copyright 2021 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. + +import Flutter +import FlutterPluginRegistrant +import Foundation + +/// A FlutterViewController intended for the MyApp widget in the Flutter module. +/// +/// This view controller maintains a connection to the Flutter instance and syncs it with the +/// datamodel. In practice you should override the other init methods or switch to composition +/// instead of inheritence. +class SingleFlutterViewController: FlutterViewController, DataModelObserver { + private var channel: FlutterMethodChannel? + + init(withEntrypoint entryPoint: String?) { + let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate + let newEngine = appDelegate.engines.makeEngine(withEntrypoint: entryPoint, libraryURI: nil) + GeneratedPluginRegistrant.register(with: newEngine) + super.init(engine: newEngine, nibName: nil, bundle: nil) + DataModel.shared.addObserver(observer: self) + } + + deinit { + DataModel.shared.removeObserver(observer: self) + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func onCountUpdate(newCount: Int64) { + if let channel = channel { + channel.invokeMethod("setCount", arguments: newCount) + } + } + + override func viewDidLoad() { + super.viewDidLoad() + channel = FlutterMethodChannel( + name: "multiple-flutters", binaryMessenger: self.engine!.binaryMessenger) + channel!.invokeMethod("setCount", arguments: DataModel.shared.count) + let navController = self.navigationController! + channel!.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in + if call.method == "incrementCount" { + DataModel.shared.count = DataModel.shared.count + 1 + result(nil) + } else if call.method == "next" { + let storyboard = UIStoryboard(name: "Main", bundle: nil) + let vc = storyboard.instantiateViewController(withIdentifier: "NativeViewCount") + navController.pushViewController(vc, animated: true) + result(nil) + } else { + result(FlutterMethodNotImplemented) + } + } + } +} diff --git a/add_to_app/multiple_flutters/multiple_flutters_ios/Podfile b/add_to_app/multiple_flutters/multiple_flutters_ios/Podfile new file mode 100644 index 0000000..64e1388 --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_ios/Podfile @@ -0,0 +1,15 @@ +# Copyright 2021 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. + +flutter_application_path = '../multiple_flutters_module' +load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb') + +target 'MultipleFluttersIos' do + use_frameworks! + install_all_flutter_pods(flutter_application_path) +end + +post_install do |installer| + flutter_post_install(installer) if defined?(flutter_post_install) +end diff --git a/add_to_app/multiple_flutters/multiple_flutters_ios/README.md b/add_to_app/multiple_flutters/multiple_flutters_ios/README.md new file mode 100644 index 0000000..767969b --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_ios/README.md @@ -0,0 +1,17 @@ +# multiple_flutters_ios + +This is an add-to-app sample that uses the Flutter Engine Group API to host +multiple instances of Flutter in an iOS app. + +## Getting Started + +```sh +cd ../multiple_flutters_module +flutter pub get +cd - +pod install +open MultipleFluttersIos.xcworkspace +# (build and run) +``` + +For more information see: [multiple_flutters/README.md](../README.md) diff --git a/add_to_app/multiple_flutters/multiple_flutters_module/.gitignore b/add_to_app/multiple_flutters/multiple_flutters_module/.gitignore new file mode 100644 index 0000000..ff612b3 --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_module/.gitignore @@ -0,0 +1,48 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +*.swp +profile + +DerivedData/ + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +build/ +.android/ +.ios/ +.flutter-plugins +.flutter-plugins-dependencies + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json diff --git a/add_to_app/multiple_flutters/multiple_flutters_module/.metadata b/add_to_app/multiple_flutters/multiple_flutters_module/.metadata new file mode 100644 index 0000000..bdc27cb --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_module/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 1d3f6971600f6e3fb144a30fab2b889e34af0c22 + channel: master + +project_type: module diff --git a/add_to_app/multiple_flutters/multiple_flutters_module/README.md b/add_to_app/multiple_flutters/multiple_flutters_module/README.md new file mode 100644 index 0000000..819761c --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_module/README.md @@ -0,0 +1,5 @@ +# multiple_flutters_module + +This is the Flutter module that is embedded in the `multiple_flutters` projects. + +See also: [multiple_flutters/README.md](../README.md) \ No newline at end of file diff --git a/add_to_app/multiple_flutters/multiple_flutters_module/analysis_options.yaml b/add_to_app/multiple_flutters/multiple_flutters_module/analysis_options.yaml new file mode 100644 index 0000000..e2badd7 --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_module/analysis_options.yaml @@ -0,0 +1 @@ +include: ../../../analysis_options.yaml diff --git a/add_to_app/multiple_flutters/multiple_flutters_module/lib/main.dart b/add_to_app/multiple_flutters/multiple_flutters_module/lib/main.dart new file mode 100644 index 0000000..7464360 --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_module/lib/main.dart @@ -0,0 +1,111 @@ +// Copyright 2021 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. + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:url_launcher/url_launcher.dart' as launcher; + +void main() => runApp(const MyApp(color: Colors.blue)); + +@pragma('vm:entry-point') +void topMain() => runApp(const MyApp(color: Colors.green)); + +@pragma('vm:entry-point') +void bottomMain() => runApp(const MyApp(color: Colors.purple)); + +class MyApp extends StatelessWidget { + const MyApp({super.key, required this.color}); + + final MaterialColor color; + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + colorSchemeSeed: color, + appBarTheme: AppBarTheme( + backgroundColor: color, + foregroundColor: Colors.white, + elevation: 8, + ), + ), + home: const MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + int? _counter = 0; + late MethodChannel _channel; + + @override + void initState() { + super.initState(); + _channel = const MethodChannel('multiple-flutters'); + _channel.setMethodCallHandler((call) async { + if (call.method == "setCount") { + // A notification that the host platform's data model has been updated. + setState(() { + _counter = call.arguments as int?; + }); + } else { + throw Exception('not implemented ${call.method}'); + } + }); + } + + void _incrementCounter() { + // Mutations to the data model are forwarded to the host platform. + _channel.invokeMethod("incrementCount", _counter); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(widget.title)), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('You have pushed the button this many times:'), + Text( + '$_counter', + style: Theme.of(context).textTheme.headlineMedium, + ), + TextButton( + onPressed: _incrementCounter, + child: const Text('Add'), + ), + TextButton( + onPressed: () { + _channel.invokeMethod("next", _counter); + }, + child: const Text('Next'), + ), + ElevatedButton( + onPressed: () async { + // Use the url_launcher plugin to open the Flutter docs in + // a browser. + final url = Uri.parse('https://flutter.dev/docs'); + if (await launcher.canLaunchUrl(url)) { + await launcher.launchUrl(url); + } + }, + child: const Text('Open Flutter Docs'), + ), + ], + ), + ), + ); + } +} diff --git a/add_to_app/multiple_flutters/multiple_flutters_module/pubspec.yaml b/add_to_app/multiple_flutters/multiple_flutters_module/pubspec.yaml new file mode 100644 index 0000000..0b7755f --- /dev/null +++ b/add_to_app/multiple_flutters/multiple_flutters_module/pubspec.yaml @@ -0,0 +1,28 @@ +name: multiple_flutters_module +description: A module that is embedded in the multiple_flutters_ios and multiple_flutters_android sample code. +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + flutter: + sdk: flutter + url_launcher: ^6.0.6 + + cupertino_icons: ^1.0.0 + +dev_dependencies: + analysis_defaults: + path: ../../../analysis_defaults + flutter_test: + sdk: flutter + +flutter: + uses-material-design: true + + module: + androidX: true + androidPackage: com.example.multiple_flutters_module + iosBundleIdentifier: com.example.multipleFluttersModule diff --git a/add_to_app/plugin/README.md b/add_to_app/plugin/README.md new file mode 100644 index 0000000..a483e75 --- /dev/null +++ b/add_to_app/plugin/README.md @@ -0,0 +1,53 @@ +# plugin + +Embeds a full screen Flutter instance that is using plugins into an existing iOS +or Android app. + +## Description + +These apps are similar to the samples in [`fullscreen`](../fullscreen), with the +following differences: + +* They include the native code (Kotlin or Swift) required to initialize plugins + at Flutter engine creation time. +* Their Flutter view includes an additional button that opens the Flutter docs + in the mobile device's browser. + +If you're interested in learning what additional steps an app needs to take in +order to use a Flutter module that relies on plugins, these projects can help. + +## 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_using_plugin # macOS only + # Or open the ../android_using_plugin folder in Android Studio for other platforms. + + # For iOS builds: + cd ../ios_using_plugin + pod install + open IOSUsingPlugin.xcworkspace +``` + +## Requirements + +* Flutter +* Android + * Android Studio +* iOS + * Xcode + * Cocoapods + +## Questions/issues + +See [add_to_app/README.md](../README.md) for further help. diff --git a/add_to_app/plugin/android_using_plugin/.gitignore b/add_to_app/plugin/android_using_plugin/.gitignore new file mode 100644 index 0000000..603b140 --- /dev/null +++ b/add_to_app/plugin/android_using_plugin/.gitignore @@ -0,0 +1,14 @@ +*.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 diff --git a/add_to_app/plugin/android_using_plugin/README.md b/add_to_app/plugin/android_using_plugin/README.md new file mode 100644 index 0000000..670a334 --- /dev/null +++ b/add_to_app/plugin/android_using_plugin/README.md @@ -0,0 +1,14 @@ +# android_using_plugin + +An example Android application used in the Flutter add-to-app samples. For more +information on how to use it, see the [README](../README.md) file located in the +[/add_to_app](/add_to_app) directory of this repo. + +## 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). diff --git a/add_to_app/plugin/android_using_plugin/app/.gitignore b/add_to_app/plugin/android_using_plugin/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/add_to_app/plugin/android_using_plugin/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/add_to_app/plugin/android_using_plugin/app/build.gradle b/add_to_app/plugin/android_using_plugin/app/build.gradle new file mode 100644 index 0000000..f8586e5 --- /dev/null +++ b/add_to_app/plugin/android_using_plugin/app/build.gradle @@ -0,0 +1,44 @@ +apply plugin: 'com.android.application' + +apply plugin: 'kotlin-android' + +android { + compileSdk 35 + defaultConfig { + applicationId "dev.flutter.example.androidusingplugin" + minSdkVersion 21 + targetSdk 35 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + } + } + // Keep java and kotlin versions in sync. + compileOptions { + sourceCompatibility 17 + targetCompatibility 17 + } + kotlinOptions { + jvmTarget = '17' + } + + namespace 'dev.flutter.example.androidusingplugin' + lint { + checkReleaseBuilds false + } +} + +dependencies { + implementation project(':flutter') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'androidx.core:core-ktx:1.15.0' + implementation 'androidx.constraintlayout:constraintlayout:2.2.0' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.2.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' +} diff --git a/add_to_app/plugin/android_using_plugin/app/src/androidTest/java/dev/flutter/example/androidusingplugin/ExampleInstrumentedTest.kt b/add_to_app/plugin/android_using_plugin/app/src/androidTest/java/dev/flutter/example/androidusingplugin/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..756609f --- /dev/null +++ b/add_to_app/plugin/android_using_plugin/app/src/androidTest/java/dev/flutter/example/androidusingplugin/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package dev.flutter.example.androidusingplugin + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("dev.flutter.example.androidusingplugin", appContext.packageName) + } +} diff --git a/add_to_app/plugin/android_using_plugin/app/src/main/AndroidManifest.xml b/add_to_app/plugin/android_using_plugin/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f0161e6 --- /dev/null +++ b/add_to_app/plugin/android_using_plugin/app/src/main/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/add_to_app/plugin/android_using_plugin/app/src/main/java/dev/flutter/example/androidusingplugin/MainActivity.kt b/add_to_app/plugin/android_using_plugin/app/src/main/java/dev/flutter/example/androidusingplugin/MainActivity.kt new file mode 100644 index 0000000..ce9eecd --- /dev/null +++ b/add_to_app/plugin/android_using_plugin/app/src/main/java/dev/flutter/example/androidusingplugin/MainActivity.kt @@ -0,0 +1,36 @@ +// 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.androidusingplugin + +import android.os.Bundle +import android.widget.Button +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : AppCompatActivity() { + private lateinit var counterLabel: TextView + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + counterLabel = findViewById(R.id.counter_label) + val button = findViewById + + + + + + + + + + + + + + + + + + + + diff --git a/add_to_app/plugin/ios_using_plugin/IOSUsingPlugin/Info-Debug.plist b/add_to_app/plugin/ios_using_plugin/IOSUsingPlugin/Info-Debug.plist new file mode 100644 index 0000000..72b874a --- /dev/null +++ b/add_to_app/plugin/ios_using_plugin/IOSUsingPlugin/Info-Debug.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + NSBonjourServices + + _dartobservatory._tcp + + NSLocalNetworkUsageDescription + Allow Flutter tools on your computer to connect and debug your application. This prompt will not appear on release builds. + + diff --git a/add_to_app/plugin/ios_using_plugin/IOSUsingPlugin/Info-Release.plist b/add_to_app/plugin/ios_using_plugin/IOSUsingPlugin/Info-Release.plist new file mode 100644 index 0000000..16be3b6 --- /dev/null +++ b/add_to_app/plugin/ios_using_plugin/IOSUsingPlugin/Info-Release.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/add_to_app/plugin/ios_using_plugin/IOSUsingPlugin/ViewController.swift b/add_to_app/plugin/ios_using_plugin/IOSUsingPlugin/ViewController.swift new file mode 100644 index 0000000..2be0786 --- /dev/null +++ b/add_to_app/plugin/ios_using_plugin/IOSUsingPlugin/ViewController.swift @@ -0,0 +1,51 @@ +// 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. + +import UIKit +import Flutter + +class ViewController: UIViewController { + + @IBOutlet weak var counterLabel: UILabel! + + var methodChannel : FlutterMethodChannel? + var count = 0 + + override func viewDidLoad() { + super.viewDidLoad() + + if let flutterEngine = (UIApplication.shared.delegate as? AppDelegate)?.flutterEngine { + methodChannel = FlutterMethodChannel(name: "dev.flutter.example/counter", + binaryMessenger: flutterEngine.binaryMessenger) + methodChannel?.setMethodCallHandler({ [weak self] + (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in + if let strongSelf = self { + switch(call.method) { + case "incrementCounter": + strongSelf.count += 1 + strongSelf.counterLabel.text = "Current counter: \(strongSelf.count)" + strongSelf.reportCounter() + case "requestCounter": + strongSelf.reportCounter() + default: + // Unrecognized method name + print("Unrecognized method name: \(call.method)") + } + } + }) + } + } + + func reportCounter() { + methodChannel?.invokeMethod("reportCounter", arguments: count) + } + + @IBAction func buttonWasTapped(_ sender: Any) { + if let flutterEngine = (UIApplication.shared.delegate as? AppDelegate)?.flutterEngine { + let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil) + self.present(flutterViewController, animated: false, completion: nil) + } + } +} + diff --git a/add_to_app/plugin/ios_using_plugin/IOSUsingPluginTests/IOSUsingPluginTests.swift b/add_to_app/plugin/ios_using_plugin/IOSUsingPluginTests/IOSUsingPluginTests.swift new file mode 100644 index 0000000..e7ecb84 --- /dev/null +++ b/add_to_app/plugin/ios_using_plugin/IOSUsingPluginTests/IOSUsingPluginTests.swift @@ -0,0 +1,30 @@ +// 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. + +import XCTest +@testable import IOSUsingPlugin + +class IOSUsingPluginTests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/add_to_app/plugin/ios_using_plugin/IOSUsingPluginTests/Info.plist b/add_to_app/plugin/ios_using_plugin/IOSUsingPluginTests/Info.plist new file mode 100644 index 0000000..6c40a6c --- /dev/null +++ b/add_to_app/plugin/ios_using_plugin/IOSUsingPluginTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/add_to_app/plugin/ios_using_plugin/IOSUsingPluginUITests/IOSUsingPluginUITests.swift b/add_to_app/plugin/ios_using_plugin/IOSUsingPluginUITests/IOSUsingPluginUITests.swift new file mode 100644 index 0000000..53dc23b --- /dev/null +++ b/add_to_app/plugin/ios_using_plugin/IOSUsingPluginUITests/IOSUsingPluginUITests.swift @@ -0,0 +1,30 @@ +// 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. + +import XCTest + +class IOSUsingPluginUITests: XCTestCase { + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. + XCUIApplication().launch() + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() { + // Use recording to get started writing UI tests. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + +} diff --git a/add_to_app/plugin/ios_using_plugin/IOSUsingPluginUITests/Info.plist b/add_to_app/plugin/ios_using_plugin/IOSUsingPluginUITests/Info.plist new file mode 100644 index 0000000..6c40a6c --- /dev/null +++ b/add_to_app/plugin/ios_using_plugin/IOSUsingPluginUITests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/add_to_app/plugin/ios_using_plugin/Podfile b/add_to_app/plugin/ios_using_plugin/Podfile new file mode 100644 index 0000000..726737d --- /dev/null +++ b/add_to_app/plugin/ios_using_plugin/Podfile @@ -0,0 +1,27 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' + +flutter_application_path = '../flutter_module_using_plugin' +load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb') + +target 'IOSUsingPlugin' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + # Pods for IOSUsingPlugin + install_all_flutter_pods(flutter_application_path) + + target 'IOSUsingPluginTests' do + inherit! :search_paths + # Pods for testing + end + + target 'IOSUsingPluginUITests' do + inherit! :search_paths + # Pods for testing + end +end + +post_install do |installer| + flutter_post_install(installer) if defined?(flutter_post_install) +end diff --git a/add_to_app/plugin/ios_using_plugin/README.md b/add_to_app/plugin/ios_using_plugin/README.md new file mode 100644 index 0000000..2b363ea --- /dev/null +++ b/add_to_app/plugin/ios_using_plugin/README.md @@ -0,0 +1,14 @@ +# ios_using_plugin + +An example iOS application used in the Flutter add-to-app samples. For more +information on how to use it, see the [README](../README.md) file located in the +[/add_to_app](/add_to_app) directory of this repo. + +## 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). diff --git a/add_to_app/prebuilt_module/README.md b/add_to_app/prebuilt_module/README.md new file mode 100644 index 0000000..4e5a244 --- /dev/null +++ b/add_to_app/prebuilt_module/README.md @@ -0,0 +1,88 @@ +# prebuild_module + +Embeds a full screen instance of Flutter as a prebuilt library that can be +loaded into an existing iOS or Android app. + +## Description + +These apps are essentially identical to `android_fullscreen` and +`ios_fullscreen`, respectively, with one key difference. Rather than being set +up to compile the `flutter_module` from source each time the app is built, they +import a the module as a prebuilt `aar` (Android) or framework (iOS). This can +be useful for teams that don't want to require every developer working on the +app to have the Flutter toolchain installed on their local machines. + +Prior to building either project for the first time, the `flutter_module` needs +to be built. + +**Building for `android_using_prebuilt_module`** + +To build `flutter_module` as an aar, run this command from the `flutter_module` +directory: + +```bash +flutter build aar +``` + +It will produce `aar` files for debug, profile, and release mode. The Android +app is configured to import the appropriate `aar` based on its own build +configuration, so if you build a debug version of the app, it will look +for the debug `aar`, and so on. + +If the `flutter_module` project is updated, the `aar` files must be rebuilt via +one of the commands above in order for those changes to appear in the app. + +**Building for `ios_using_prebuilt_module`** + +To build `flutter_module` as a set of frameworks, run this command from the +`flutter_module` directory: + +```bash +flutter build ios-framework --xcframework --output=../ios_using_prebuilt_module/Flutter +``` + +This will output frameworks for debug, profile, and release modes into +`ios_using_prebuilt_module/Flutter`. The project file for +`ios_using_prebuilt_module` has been configured to find the frameworks there. + +For more information on how to modify an existing iOS app to reference prebuilt +Flutter frameworks, see this article in the Flutter GitHub wiki: + +https://flutter.dev/docs/development/add-to-app/ios/project-setup + +## 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/ + flutter pub get + + # For Android builds: + flutter build aar + open -a "Android Studio" ../android_using_prebuilt_module/ # macOS only + # Or open the ../android_using_prebuilt_module folder in Android Studio for + # other platforms. + + # For iOS builds: + flutter build ios-framework --xcframework --output=../ios_using_prebuilt_module/Flutter + open ../ios_using_prebuilt_module/IOSUsingPrebuiltModule.xcodeproj +``` + +## Requirements + +* Flutter +* Android + * Android Studio +* iOS + * Xcode + * Cocoapods + +## Questions/issues + +See [add_to_app/README.md](../README.md) for further help. diff --git a/add_to_app/prebuilt_module/android_using_prebuilt_module/.gitignore b/add_to_app/prebuilt_module/android_using_prebuilt_module/.gitignore new file mode 100644 index 0000000..603b140 --- /dev/null +++ b/add_to_app/prebuilt_module/android_using_prebuilt_module/.gitignore @@ -0,0 +1,14 @@ +*.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 diff --git a/add_to_app/prebuilt_module/android_using_prebuilt_module/README.md b/add_to_app/prebuilt_module/android_using_prebuilt_module/README.md new file mode 100644 index 0000000..d03a84c --- /dev/null +++ b/add_to_app/prebuilt_module/android_using_prebuilt_module/README.md @@ -0,0 +1,14 @@ +# android_using_prebuilt_module + +An example Android application used in the Flutter add-to-app samples. For more +information on how to use it, see the [README](../README.md) file located in the +[/add_to_app](/add_to_app) directory of this repo. + +## 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). diff --git a/add_to_app/prebuilt_module/android_using_prebuilt_module/app/.gitignore b/add_to_app/prebuilt_module/android_using_prebuilt_module/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/add_to_app/prebuilt_module/android_using_prebuilt_module/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/add_to_app/prebuilt_module/android_using_prebuilt_module/app/build.gradle b/add_to_app/prebuilt_module/android_using_prebuilt_module/app/build.gradle new file mode 100644 index 0000000..676223f --- /dev/null +++ b/add_to_app/prebuilt_module/android_using_prebuilt_module/app/build.gradle @@ -0,0 +1,62 @@ +apply plugin: 'com.android.application' + +apply plugin: 'kotlin-android' + +android { + compileSdk 35 + defaultConfig { + applicationId "dev.flutter.example.androidusingprebuiltmodule" + minSdkVersion 21 + targetSdkVersion 35 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + multiDexEnabled true + } + buildTypes { + release { + minifyEnabled false + } + } + compileOptions { + sourceCompatibility 17 + targetCompatibility 17 + } + kotlinOptions { + jvmTarget = '17' + } + namespace 'dev.flutter.example.androidusingprebuiltmodule' +} + +repositories { + maven { + // This maven repo is created when you run `flutter build aar`. It contains compiled code + // and resources for flutter_module itself. + url '../../flutter_module/build/host/outputs/repo' + } + maven { + // This maven repo contains artifacts for Flutter's Android embedding. + url 'https://storage.googleapis.com/download.flutter.io' + } +} + +dependencies { + releaseImplementation ('dev.flutter.example.flutter_module:flutter_release:1.0@aar') { + transitive = true + } + + debugImplementation ('dev.flutter.example.flutter_module:flutter_debug:1.0@aar') { + transitive = true + } + + implementation 'androidx.multidex:multidex:2.0.1' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'androidx.core:core-ktx:1.13.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation "com.google.truth:truth:1.1.3" + androidTestImplementation 'androidx.test:runner:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + api 'androidx.test:core:1.2.1' +} diff --git a/add_to_app/prebuilt_module/android_using_prebuilt_module/app/src/androidTest/java/dev/flutter/example/androidusingprebuiltmodule/ExampleInstrumentedTest.kt b/add_to_app/prebuilt_module/android_using_prebuilt_module/app/src/androidTest/java/dev/flutter/example/androidusingprebuiltmodule/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..fdef677 --- /dev/null +++ b/add_to_app/prebuilt_module/android_using_prebuilt_module/app/src/androidTest/java/dev/flutter/example/androidusingprebuiltmodule/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package dev.flutter.example.androidusingprebuiltmodule + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("dev.flutter.example.androidusingprebuiltmodule", appContext.packageName) + } +} diff --git a/add_to_app/prebuilt_module/android_using_prebuilt_module/app/src/main/AndroidManifest.xml b/add_to_app/prebuilt_module/android_using_prebuilt_module/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..9f282c3 --- /dev/null +++ b/add_to_app/prebuilt_module/android_using_prebuilt_module/app/src/main/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/add_to_app/prebuilt_module/android_using_prebuilt_module/app/src/main/java/dev/flutter/example/androidusingprebuiltmodule/MainActivity.kt b/add_to_app/prebuilt_module/android_using_prebuilt_module/app/src/main/java/dev/flutter/example/androidusingprebuiltmodule/MainActivity.kt new file mode 100644 index 0000000..867aee5 --- /dev/null +++ b/add_to_app/prebuilt_module/android_using_prebuilt_module/app/src/main/java/dev/flutter/example/androidusingprebuiltmodule/MainActivity.kt @@ -0,0 +1,39 @@ +// 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.androidusingprebuiltmodule + +import android.annotation.SuppressLint +import android.os.Bundle +import android.widget.Button +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : AppCompatActivity() { + private lateinit var counterLabel: TextView + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + counterLabel = findViewById(R.id.counter_label) + + val button = findViewById + + + + + + + + + + + + + + + + + + + + diff --git a/add_to_app/prebuilt_module/ios_using_prebuilt_module/IOSUsingPrebuiltModule/Info-Debug.plist b/add_to_app/prebuilt_module/ios_using_prebuilt_module/IOSUsingPrebuiltModule/Info-Debug.plist new file mode 100644 index 0000000..72b874a --- /dev/null +++ b/add_to_app/prebuilt_module/ios_using_prebuilt_module/IOSUsingPrebuiltModule/Info-Debug.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + NSBonjourServices + + _dartobservatory._tcp + + NSLocalNetworkUsageDescription + Allow Flutter tools on your computer to connect and debug your application. This prompt will not appear on release builds. + + diff --git a/add_to_app/prebuilt_module/ios_using_prebuilt_module/IOSUsingPrebuiltModule/Info-Release.plist b/add_to_app/prebuilt_module/ios_using_prebuilt_module/IOSUsingPrebuiltModule/Info-Release.plist new file mode 100644 index 0000000..16be3b6 --- /dev/null +++ b/add_to_app/prebuilt_module/ios_using_prebuilt_module/IOSUsingPrebuiltModule/Info-Release.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/add_to_app/prebuilt_module/ios_using_prebuilt_module/IOSUsingPrebuiltModule/ViewController.swift b/add_to_app/prebuilt_module/ios_using_prebuilt_module/IOSUsingPrebuiltModule/ViewController.swift new file mode 100644 index 0000000..615c49d --- /dev/null +++ b/add_to_app/prebuilt_module/ios_using_prebuilt_module/IOSUsingPrebuiltModule/ViewController.swift @@ -0,0 +1,51 @@ +// 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. + +import UIKit +import Flutter + +class ViewController: UIViewController { + + @IBOutlet weak var counterLabel: UILabel! + + var methodChannel : FlutterMethodChannel? + var count = 0 + + override func viewDidLoad() { + super.viewDidLoad() + + if let flutterEngine = (UIApplication.shared.delegate as? AppDelegate)?.flutterEngine { + methodChannel = FlutterMethodChannel(name: "dev.flutter.example/counter", + binaryMessenger: flutterEngine.binaryMessenger) + methodChannel?.setMethodCallHandler({ [weak self] + (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in + if let strongSelf = self { + switch(call.method) { + case "incrementCounter": + strongSelf.count += 1 + strongSelf.counterLabel.text = "Current counter: \(strongSelf.count)" + strongSelf.reportCounter() + case "requestCounter": + strongSelf.reportCounter() + default: + // Unrecognized method name + print("Unrecognized method name: \(call.method)") + } + } + }) + } + } + + func reportCounter() { + methodChannel?.invokeMethod("reportCounter", arguments: count) + } + + @IBAction func buttonWasTapped(_ sender: Any) { + if let flutterEngine = (UIApplication.shared.delegate as? AppDelegate)?.flutterEngine { + let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil) + self.present(flutterViewController, animated: false, completion: nil) + } + } +} + diff --git a/add_to_app/prebuilt_module/ios_using_prebuilt_module/README.md b/add_to_app/prebuilt_module/ios_using_prebuilt_module/README.md new file mode 100644 index 0000000..ddd5f41 --- /dev/null +++ b/add_to_app/prebuilt_module/ios_using_prebuilt_module/README.md @@ -0,0 +1,23 @@ +# ios_using_prebuilt_module + +An example iOS application used in the Flutter add-to-app samples. For more +information on how to use it, see the [README](../README.md) file located in the +[/add_to_app](/add_to_app) directory of this repo. + +## 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). + +## Requirements + +* Flutter +* Android + * Android Studio +* iOS + * Xcode + * Cocoapods diff --git a/analysis_defaults/.gitignore b/analysis_defaults/.gitignore new file mode 100644 index 0000000..f48998b --- /dev/null +++ b/analysis_defaults/.gitignore @@ -0,0 +1,4 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ +.build/ diff --git a/analysis_defaults/lib/flutter.yaml b/analysis_defaults/lib/flutter.yaml new file mode 100644 index 0000000..34ab6e1 --- /dev/null +++ b/analysis_defaults/lib/flutter.yaml @@ -0,0 +1,24 @@ +include: package:flutter_lints/flutter.yaml + +formatter: + trailing_commas: preserve + page_width: 79 + +analyzer: + language: + strict-casts: true + strict-inference: true + +linter: + rules: + avoid_types_on_closure_parameters: true + avoid_void_async: true + cancel_subscriptions: true + close_sinks: true + directives_ordering: true + test_types_in_equals: true + throw_in_finally: true + unawaited_futures: true + unnecessary_breaks: true + unnecessary_statements: true + use_super_parameters: true diff --git a/analysis_defaults/pubspec.yaml b/analysis_defaults/pubspec.yaml new file mode 100644 index 0000000..21a9b80 --- /dev/null +++ b/analysis_defaults/pubspec.yaml @@ -0,0 +1,14 @@ +name: analysis_defaults +description: Analysis defaults for flutter/samples +publish_to: none +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +# NOTE: Code is not allowed in this package. Do not add more dependencies. +# The `flutter_lints` dependency is required for `lib/flutter.yaml`. +dependencies: + flutter_lints: ^6.0.0 + + diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..2273994 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,11 @@ +include: package:analysis_defaults/flutter.yaml + +analyzer: + exclude: + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** diff --git a/android_splash_screen/.gitignore b/android_splash_screen/.gitignore new file mode 100644 index 0000000..fa14136 --- /dev/null +++ b/android_splash_screen/.gitignore @@ -0,0 +1,71 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +local.properties + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.gradle +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# See https://www.dartlang.org/guides/libraries/private-files + +# Files and directories created by pub +.dart_tool/ +.packages +build/ +# If you're building an application, you may want to check-in your pubspec.lock +pubspec.lock + +# Directory created by dartdoc +# If you don't generate documentation locally you can remove this line. +doc/api/ + +# Avoid committing generated Javascript files: +*.dart.js +*.info.json # Produced by the --dump-info flag. +*.js # When generated by dart2js. Don't specify *.js if your + # project includes source files written in JavaScript. +*.js_ +*.js.deps +*.js.map + diff --git a/android_splash_screen/.metadata b/android_splash_screen/.metadata new file mode 100644 index 0000000..56bfc2c --- /dev/null +++ b/android_splash_screen/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: f4abaa0735eba4dfd8f33f73363911d63931fe03 + channel: stable + +project_type: app diff --git a/android_splash_screen/README.md b/android_splash_screen/README.md new file mode 100644 index 0000000..54c46db --- /dev/null +++ b/android_splash_screen/README.md @@ -0,0 +1,35 @@ +# Splash Screen Sample + +A Flutter app that exemplifies how to implement an animated splash screen for Android devices running at least Android 12, the version that supplies the new [SplashScreen API](https://developer.android.com/about/versions/12/features/splash-screen). + +**NOTE:** There is a pub package available to implement static splash screens in your Flutter app: [flutter_native_splash](https://pub.dev/packages/flutter_native_splash). + +## Goals +- Demonstrate the compatibility of animated splash screens and Flutter apps running on Android +- Demonstrate the smoothness achievable by a splash screen as a transition to the Flutter UI + +## The important bits + +### Remove deprecated code + +When creating a Flutter app, the Android code generated may include the deprecated implementation of a splash screen. This includes a definition of `io.flutter.embedding.android.SplashScreenDrawable` in the ` /android/app/src/main/AndroidManifest.xml` file and an implementation of `provideSplashScreen()` in the `/android/app/src/main/kotlin/MainActivity.kt` file. Make sure to remove this code. + +**NOTE:** This should no longer be a concern as of Flutter 2.5. + +### Modify Android build files +In order to support the Android 12 SplashScreen API, you need to: +1. Update the Android `compileSdkVersion` to 31 in the `/android/app/build.gradle` file, and +2. Update the `ext.kotlin_version` to the latest Kotlin extension version (1.5.31 at the time of publication) in the `/android/build.gradle` file. + +### Timing the splash screen animation +In order to ensure a smooth transition between the animated splash screen and the Flutter UI displaying for the first time, be sure to handle both the case where the Flutter UI is ready to be displayed before the animation finishes and vice versa. This can be done by overriding `onFlutterUiDisplayed()` and `onFlutterUiNoLongerDisplayed()` in `/android/app/src/main/kotlin/com/example/splash-screen-sample/MainActivity.kt`, the implementation of `FlutterActivity` in this project. + +## Questions/Issues + +If you have a general question about splash screens or their implementation in Flutter, the best places to go are: + +* [Android 12 Splash Screen Documentation](https://developer.android.com/about/versions/12/features/splash-screen) +* [Flutter Guidance on Adding a Splash Screen to Your App](https://flutter.dev/docs/development/ui/advanced/splash-screen?tab=android-splash-alignment-kotlin-tab) + +If you run into an issue with the sample itself, please file an issue +in the [main Flutter repo](https://github.com/flutter/flutter/issues). diff --git a/android_splash_screen/analysis_options.yaml b/android_splash_screen/analysis_options.yaml new file mode 100644 index 0000000..5e2133e --- /dev/null +++ b/android_splash_screen/analysis_options.yaml @@ -0,0 +1 @@ +include: ../analysis_options.yaml diff --git a/android_splash_screen/android/.gitignore b/android_splash_screen/android/.gitignore new file mode 100644 index 0000000..0a741cb --- /dev/null +++ b/android_splash_screen/android/.gitignore @@ -0,0 +1,11 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties diff --git a/android_splash_screen/android/app/build.gradle b/android_splash_screen/android/app/build.gradle new file mode 100644 index 0000000..958b5b0 --- /dev/null +++ b/android_splash_screen/android/app/build.gradle @@ -0,0 +1,83 @@ +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +android { + namespace "com.example.splash_screen_sample" + compileSdkVersion 34 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.splash_screen_sample" + minSdkVersion flutter.minSdkVersion + targetSdkVersion 34 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "androidx.core:core-splashscreen:1.0.0-alpha02" + implementation "androidx.core:core:1.5.0-alpha05" + implementation "androidx.core:core-ktx:1.6.0" + implementation 'androidx.constraintlayout:constraintlayout:1.1.2' + implementation "androidx.core:core-animation:1.0.0-alpha02" + implementation "androidx.interpolator:interpolator:1.0.0" + def appcompat_version = "1.3.1" + + implementation "androidx.appcompat:appcompat:$appcompat_version" + // For loading and tinting drawables on older versions of the platform + implementation "androidx.appcompat:appcompat-resources:$appcompat_version" + implementation "androidx.constraintlayout:constraintlayout:2.1.0" +} diff --git a/android_splash_screen/android/app/src/debug/AndroidManifest.xml b/android_splash_screen/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..0dea87e --- /dev/null +++ b/android_splash_screen/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android_splash_screen/android/app/src/main/AndroidManifest.xml b/android_splash_screen/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ad41ca2 --- /dev/null +++ b/android_splash_screen/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + diff --git a/android_splash_screen/android/app/src/main/kotlin/com/example/splash_screen_sample/MainActivity.kt b/android_splash_screen/android/app/src/main/kotlin/com/example/splash_screen_sample/MainActivity.kt new file mode 100644 index 0000000..e7d858e --- /dev/null +++ b/android_splash_screen/android/app/src/main/kotlin/com/example/splash_screen_sample/MainActivity.kt @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ + +package com.example.splash_screen_sample + +import android.animation.AnimatorSet +import android.animation.ObjectAnimator +import android.animation.ValueAnimator +import android.os.Bundle +import android.transition.AutoTransition +import android.transition.Transition +import android.transition.TransitionManager +import android.view.animation.AccelerateDecelerateInterpolator +import android.view.View +import android.widget.FrameLayout +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import androidx.core.animation.doOnEnd +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import androidx.core.splashscreen.SplashScreenViewProvider +import androidx.core.view.postDelayed +import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.interpolator.view.animation.FastOutLinearInInterpolator +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() { + + var flutterUIReady : Boolean = false + var initialAnimationFinished : Boolean = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // This activity will be handling the splash screen transition. + val splashScreen = installSplashScreen() + + // The splash screen goes edge to edge, so for a smooth transition to our app, also + // want to draw edge to edge. + WindowCompat.setDecorFitsSystemWindows(window, false) + val insetsController = WindowCompat.getInsetsController(window, window.decorView) + insetsController?.isAppearanceLightNavigationBars = true + insetsController?.isAppearanceLightStatusBars = true + + // The content view needs to be set before calling setOnExitAnimationListener + // to ensure that the SplashScreenView is attached to the right view root. + val rootLayout = findViewById(android.R.id.content) as FrameLayout + View.inflate(this, R.layout.main_activity_2, rootLayout) + + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.container)) { view, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + view.setPadding(insets.left, insets.top, insets.right, insets.bottom) + windowInsets.inset(insets) + } + + // Setting an OnExitAnimationListener on the splash screen indicates + // to the system that the application will handle the exit animation. + // The listener will be called once the app is ready. + splashScreen.setOnExitAnimationListener { splashScreenViewProvider -> + onSplashScreenExit(splashScreenViewProvider) + } + } + + override fun onFlutterUiDisplayed(){ + flutterUIReady = true + + if (initialAnimationFinished) { + hideSplashScreenAnimation() + } + } + + override fun onFlutterUiNoLongerDisplayed(){ + flutterUIReady = false + } + + /** + * Hides the splash screen only when the entire animation has finished and the Flutter UI is ready to display. + */ + private fun hideSplashScreenAnimation(){ + val splashView = findViewById(R.id.container) as ConstraintLayout + splashView + .animate() + .alpha(0.0f) + .setDuration(SPLASHSCREEN_FINAL_ANIMATION_ALPHA_ANIMATION_DURATION) + } + + /** + * Handles the transition from the splash screen to the application. + */ + private fun onSplashScreenExit(splashScreenViewProvider: SplashScreenViewProvider) { + val accelerateInterpolator = FastOutLinearInInterpolator() + val splashScreenView = splashScreenViewProvider.view + val iconView = splashScreenViewProvider.iconView + + // Change the alpha of the main view. + val alpha = ValueAnimator.ofInt(255, 0) + alpha.duration = SPLASHSCREEN_ALPHA_ANIMATION_DURATION + alpha.interpolator = accelerateInterpolator + + // And translate the icon down. + val translationY = ObjectAnimator.ofFloat( + iconView, + View.TRANSLATION_Y, + iconView.translationY, + splashScreenView.height.toFloat() + ) + translationY.duration = SPLASHSCREEN_TY_ANIMATION_DURATION + translationY.interpolator = accelerateInterpolator + + // And play all of the animation together. + val animatorSet = AnimatorSet() + animatorSet.playTogether(alpha) + + // Apply layout constraints of starting frame of animation to + // FrameLayout's container for the TransitionManager to know + // where to start the transition. + val root = findViewById(R.id.container) + val set1 = ConstraintSet().apply { + clone(this@MainActivity, R.layout.main_activity) + } + set1.applyTo(root) + + // Retrieve layout constraints of final frame of animation + // for TransitionManager to know where to end the transition. + val set2 = ConstraintSet().apply { + clone(this@MainActivity, R.layout.main_activity_2) + } + + var transitionStarted = false + val autoTransition = AutoTransition().apply { + interpolator = AccelerateDecelerateInterpolator() + } + autoTransition.addListener(object: Transition.TransitionListener { + override fun onTransitionEnd(transition: Transition) { + initialAnimationFinished = true + + if (flutterUIReady) { + hideSplashScreenAnimation() + } + } + override fun onTransitionCancel(transition: Transition){} + override fun onTransitionPause(transition: Transition) {} + override fun onTransitionResume(transition: Transition) {} + override fun onTransitionStart(transition: Transition) {} + }) + + val alphaUpdateListener: (ValueAnimator) -> Unit = { valueAnimator -> + if (!transitionStarted && valueAnimator.animatedFraction > 0.5) { + transitionStarted = true + + TransitionManager.beginDelayedTransition(root, autoTransition) + iconView.visibility = View.GONE + + // Apply constraints of final frame of animation to + // FrameLayout's container once the transition is in progress. + set2.applyTo(root) + } + splashScreenView.background.alpha = valueAnimator.animatedValue as Int + } + alpha.addUpdateListener(alphaUpdateListener) + + // Once the application is finished, remove the splash screen from our view + // hierarchy. + animatorSet.doOnEnd { + splashScreenViewProvider.remove() + } + + waitForAnimatedIconToFinish(splashScreenViewProvider, splashScreenView) { + animatorSet.start() + } + } + + /** + * Wait until the AVD animation is finished before starting the splash screen dismiss animation. + */ + private fun SplashScreenViewProvider.remainingAnimationDuration() = iconAnimationStartMillis + + iconAnimationDurationMillis - System.currentTimeMillis() + + private fun waitForAnimatedIconToFinish( + splashScreenViewProvider: SplashScreenViewProvider, + view: View, + onAnimationFinished: () -> Unit + ) { + // If wanting to wait for our Animated Vector Drawable to finish animating, can compute + // the remaining time to delay the start of the exit animation. + val delayMillis: Long = + if (WAIT_FOR_AVD_TO_FINISH) splashScreenViewProvider.remainingAnimationDuration() else 0 + view.postDelayed(delayMillis, onAnimationFinished) + } + + private companion object { + const val SPLASHSCREEN_ALPHA_ANIMATION_DURATION = 500L + const val SPLASHSCREEN_TY_ANIMATION_DURATION = 500L + const val SPLASHSCREEN_FINAL_ANIMATION_ALPHA_ANIMATION_DURATION = 250L + const val WAIT_FOR_AVD_TO_FINISH = false + } +} diff --git a/android_splash_screen/android/app/src/main/res/drawable/android.xml b/android_splash_screen/android/app/src/main/res/drawable/android.xml new file mode 100644 index 0000000..cf1152b --- /dev/null +++ b/android_splash_screen/android/app/src/main/res/drawable/android.xml @@ -0,0 +1,24 @@ + + + + + + \ No newline at end of file diff --git a/android_splash_screen/android/app/src/main/res/drawable/android_background.xml b/android_splash_screen/android/app/src/main/res/drawable/android_background.xml new file mode 100644 index 0000000..091aab8 --- /dev/null +++ b/android_splash_screen/android/app/src/main/res/drawable/android_background.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/android_splash_screen/android/app/src/main/res/drawable/splashscreen_icon.xml b/android_splash_screen/android/app/src/main/res/drawable/splashscreen_icon.xml new file mode 100644 index 0000000..d718548 --- /dev/null +++ b/android_splash_screen/android/app/src/main/res/drawable/splashscreen_icon.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android_splash_screen/android/app/src/main/res/layout/main_activity.xml b/android_splash_screen/android/app/src/main/res/layout/main_activity.xml new file mode 100644 index 0000000..5e0f370 --- /dev/null +++ b/android_splash_screen/android/app/src/main/res/layout/main_activity.xml @@ -0,0 +1,58 @@ + + + + + + + + + + diff --git a/android_splash_screen/android/app/src/main/res/layout/main_activity_2.xml b/android_splash_screen/android/app/src/main/res/layout/main_activity_2.xml new file mode 100644 index 0000000..474a520 --- /dev/null +++ b/android_splash_screen/android/app/src/main/res/layout/main_activity_2.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + diff --git a/android_splash_screen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android_splash_screen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/android_splash_screen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android_splash_screen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android_splash_screen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/android_splash_screen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android_splash_screen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android_splash_screen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/android_splash_screen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android_splash_screen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android_splash_screen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/android_splash_screen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android_splash_screen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android_splash_screen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/android_splash_screen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android_splash_screen/android/app/src/main/res/values-night/styles.xml b/android_splash_screen/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..31895e3 --- /dev/null +++ b/android_splash_screen/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,33 @@ + + + + + + + + + diff --git a/android_splash_screen/android/app/src/main/res/values/color.xml b/android_splash_screen/android/app/src/main/res/values/color.xml new file mode 100644 index 0000000..21b791d --- /dev/null +++ b/android_splash_screen/android/app/src/main/res/values/color.xml @@ -0,0 +1,21 @@ + + + + #034565 + #034565 + #FFFFFF + diff --git a/android_splash_screen/android/app/src/main/res/values/dimens.xml b/android_splash_screen/android/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..f76be91 --- /dev/null +++ b/android_splash_screen/android/app/src/main/res/values/dimens.xml @@ -0,0 +1,17 @@ + + + + 30dp + 10dp + diff --git a/android_splash_screen/android/app/src/main/res/values/styles.xml b/android_splash_screen/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..9948130 --- /dev/null +++ b/android_splash_screen/android/app/src/main/res/values/styles.xml @@ -0,0 +1,33 @@ + + + + + + + + + diff --git a/android_splash_screen/android/app/src/profile/AndroidManifest.xml b/android_splash_screen/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..0dea87e --- /dev/null +++ b/android_splash_screen/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android_splash_screen/android/build.gradle b/android_splash_screen/android/build.gradle new file mode 100644 index 0000000..d2ffbff --- /dev/null +++ b/android_splash_screen/android/build.gradle @@ -0,0 +1,18 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = "../build" +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/android_splash_screen/android/gradle.properties b/android_splash_screen/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/android_splash_screen/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/android_splash_screen/android/gradle/wrapper/gradle-wrapper.properties b/android_splash_screen/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..7aeeb11 --- /dev/null +++ b/android_splash_screen/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip diff --git a/android_splash_screen/android/settings.gradle b/android_splash_screen/android/settings.gradle new file mode 100644 index 0000000..4f52071 --- /dev/null +++ b/android_splash_screen/android/settings.gradle @@ -0,0 +1,25 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.6.0" apply false + id "org.jetbrains.kotlin.android" version "2.1.0" apply false +} + +include ":app" diff --git a/android_splash_screen/images/androidIcon.png b/android_splash_screen/images/androidIcon.png new file mode 100644 index 0000000..72e87ff Binary files /dev/null and b/android_splash_screen/images/androidIcon.png differ diff --git a/android_splash_screen/lib/main.dart b/android_splash_screen/lib/main.dart new file mode 100644 index 0000000..59a0eb4 --- /dev/null +++ b/android_splash_screen/lib/main.dart @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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. + */ + +import 'package:flutter/material.dart'; + +Future main() async { + runApp(const MyApp()); +} + +/* Main widget that contains the Flutter starter app. */ +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData(primarySwatch: Colors.blue), + home: const MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + int _counter = 0; + + void _incrementCounter() { + setState(() { + _counter++; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Column( + children: [ + const Padding( + padding: EdgeInsets.only(top: 42, bottom: 250), + child: Align( + alignment: Alignment.topCenter, + child: CustomAppBar(), + ), + ), + const Text('You have pushed the button this many times:'), + Text( + '$_counter', + style: Theme.of(context).textTheme.headlineMedium, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: _incrementCounter, + tooltip: 'Increment', + child: const Icon(Icons.add), + ), + ); + } +} + +/* A Flutter implementation of the last frame of the splashscreen animation */ +class CustomAppBar extends StatelessWidget { + const CustomAppBar({super.key}); + + @override + Widget build(BuildContext context) { + Widget titleSection = Row( + children: [ + Padding( + padding: const EdgeInsets.only(left: 12, right: 4), + child: ClipRRect( + borderRadius: BorderRadius.circular(36.0), + child: Image.asset( + 'images/androidIcon.png', + width: 72.0, + height: 72.0, + fit: BoxFit.fill, + ), + ), + ), + const Padding( + padding: EdgeInsets.only(top: 3), + child: Text( + "Super Splash Screen Demo", + style: TextStyle(color: Colors.black54, fontSize: 24), + ), + ), + ], + ); + return titleSection; + } +} diff --git a/android_splash_screen/pubspec.yaml b/android_splash_screen/pubspec.yaml new file mode 100644 index 0000000..7c33475 --- /dev/null +++ b/android_splash_screen/pubspec.yaml @@ -0,0 +1,21 @@ +name: splash_screen_sample +description: A sample Flutter app with animated splash screen on Android 12. +publish_to: "none" +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + flutter: + sdk: flutter +dev_dependencies: + analysis_defaults: + path: ../analysis_defaults + flutter_test: + sdk: flutter +flutter: + uses-material-design: true + assets: + - images/androidIcon.png diff --git a/android_splash_screen/test/widget_test.dart b/android_splash_screen/test/widget_test.dart new file mode 100644 index 0000000..99fe910 --- /dev/null +++ b/android_splash_screen/test/widget_test.dart @@ -0,0 +1,9 @@ +// Copyright 2020 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. + +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('This test always passes', (tester) async {}); +} diff --git a/animation_gallery/ios/Flutter/ephemeral/flutter_lldb_helper.py b/animation_gallery/ios/Flutter/ephemeral/flutter_lldb_helper.py new file mode 100644 index 0000000..a88caf9 --- /dev/null +++ b/animation_gallery/ios/Flutter/ephemeral/flutter_lldb_helper.py @@ -0,0 +1,32 @@ +# +# Generated file, do not edit. +# + +import lldb + +def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): + """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" + base = frame.register["x0"].GetValueAsAddress() + page_len = frame.register["x1"].GetValueAsUnsigned() + + # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the + # first page to see if handled it correctly. This makes diagnosing + # misconfiguration (e.g. missing breakpoint) easier. + data = bytearray(page_len) + data[0:8] = b'IHELPED!' + + error = lldb.SBError() + frame.GetThread().GetProcess().WriteMemory(base, data, error) + if not error.Success(): + print(f'Failed to write into {base}[+{page_len}]', error) + return + +def __lldb_init_module(debugger: lldb.SBDebugger, _): + target = debugger.GetDummyTarget() + # Caveat: must use BreakpointCreateByRegEx here and not + # BreakpointCreateByName. For some reasons callback function does not + # get carried over from dummy target for the later. + bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") + bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) + bp.SetAutoContinue(True) + print("-- LLDB integration loaded --") diff --git a/animation_gallery/ios/Flutter/ephemeral/flutter_lldbinit b/animation_gallery/ios/Flutter/ephemeral/flutter_lldbinit new file mode 100644 index 0000000..e3ba6fb --- /dev/null +++ b/animation_gallery/ios/Flutter/ephemeral/flutter_lldbinit @@ -0,0 +1,5 @@ +# +# Generated file, do not edit. +# + +command script import --relative-to-command-file flutter_lldb_helper.py diff --git a/animation_gallery/ios/Flutter/ephemeral/flutter_native_integration.env b/animation_gallery/ios/Flutter/ephemeral/flutter_native_integration.env new file mode 100644 index 0000000..b5e017d --- /dev/null +++ b/animation_gallery/ios/Flutter/ephemeral/flutter_native_integration.env @@ -0,0 +1,12 @@ +FLUTTER_ROOT=/Users/ewindmill/development/flutter +FLUTTER_APPLICATION_PATH=/Users/ewindmill/development/samples/animation_gallery +FLUTTER_FRAMEWORK_SWIFT_PACKAGE_PATH=/Users/ewindmill/development/samples/animation_gallery/ios/Flutter/ephemeral/Packages/.packages/FlutterFramework +COCOAPODS_PARALLEL_CODE_SIGN=true +FLUTTER_TARGET=lib/main.dart +FLUTTER_BUILD_DIR=build +FLUTTER_BUILD_NAME=1.0.0 +FLUTTER_BUILD_NUMBER=1 +DART_OBFUSCATION=false +TRACK_WIDGET_CREATION=true +TREE_SHAKE_ICONS=false +PACKAGE_CONFIG=.dart_tool/package_config.json diff --git a/animation_gallery/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/animation_gallery/macos/Flutter/ephemeral/Flutter-Generated.xcconfig new file mode 100644 index 0000000..9379bfe --- /dev/null +++ b/animation_gallery/macos/Flutter/ephemeral/Flutter-Generated.xcconfig @@ -0,0 +1,12 @@ +// This is a generated file; do not edit or check into version control. +FLUTTER_ROOT=/Users/ewindmill/development/flutter +FLUTTER_APPLICATION_PATH=/Users/ewindmill/development/samples/animation_gallery +FLUTTER_FRAMEWORK_SWIFT_PACKAGE_PATH=/Users/ewindmill/development/samples/animation_gallery/macos/Flutter/ephemeral/Packages/.packages/FlutterFramework +COCOAPODS_PARALLEL_CODE_SIGN=true +FLUTTER_BUILD_DIR=build +FLUTTER_BUILD_NAME=1.0.0 +FLUTTER_BUILD_NUMBER=1 +DART_OBFUSCATION=false +TRACK_WIDGET_CREATION=true +TREE_SHAKE_ICONS=false +PACKAGE_CONFIG=.dart_tool/package_config.json diff --git a/animation_gallery/macos/Flutter/ephemeral/flutter_export_environment.sh b/animation_gallery/macos/Flutter/ephemeral/flutter_export_environment.sh new file mode 100755 index 0000000..76ce3ca --- /dev/null +++ b/animation_gallery/macos/Flutter/ephemeral/flutter_export_environment.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# This is a generated file; do not edit or check into version control. +export "FLUTTER_ROOT=/Users/ewindmill/development/flutter" +export "FLUTTER_APPLICATION_PATH=/Users/ewindmill/development/samples/animation_gallery" +export "FLUTTER_FRAMEWORK_SWIFT_PACKAGE_PATH=/Users/ewindmill/development/samples/animation_gallery/macos/Flutter/ephemeral/Packages/.packages/FlutterFramework" +export "COCOAPODS_PARALLEL_CODE_SIGN=true" +export "FLUTTER_BUILD_DIR=build" +export "FLUTTER_BUILD_NAME=1.0.0" +export "FLUTTER_BUILD_NUMBER=1" +export "DART_OBFUSCATION=false" +export "TRACK_WIDGET_CREATION=true" +export "TREE_SHAKE_ICONS=false" +export "PACKAGE_CONFIG=.dart_tool/package_config.json" diff --git a/animation_gallery/macos/Flutter/ephemeral/flutter_native_integration.env b/animation_gallery/macos/Flutter/ephemeral/flutter_native_integration.env new file mode 100644 index 0000000..d9c02a5 --- /dev/null +++ b/animation_gallery/macos/Flutter/ephemeral/flutter_native_integration.env @@ -0,0 +1,11 @@ +FLUTTER_ROOT=/Users/ewindmill/development/flutter +FLUTTER_APPLICATION_PATH=/Users/ewindmill/development/samples/animation_gallery +FLUTTER_FRAMEWORK_SWIFT_PACKAGE_PATH=/Users/ewindmill/development/samples/animation_gallery/macos/Flutter/ephemeral/Packages/.packages/FlutterFramework +COCOAPODS_PARALLEL_CODE_SIGN=true +FLUTTER_BUILD_DIR=build +FLUTTER_BUILD_NAME=1.0.0 +FLUTTER_BUILD_NUMBER=1 +DART_OBFUSCATION=false +TRACK_WIDGET_CREATION=true +TREE_SHAKE_ICONS=false +PACKAGE_CONFIG=.dart_tool/package_config.json diff --git a/animations/.gitignore b/animations/.gitignore new file mode 100644 index 0000000..2ddde2a --- /dev/null +++ b/animations/.gitignore @@ -0,0 +1,73 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.packages +.pub-cache/ +.pub/ +/build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# 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.* + +# 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 diff --git a/animations/.metadata b/animations/.metadata new file mode 100644 index 0000000..d22992e --- /dev/null +++ b/animations/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "db7ef5bf9f59442b0e200a90587e8fa5e0c6336a" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: android + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: ios + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: linux + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: macos + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: web + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: windows + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/animations/README.md b/animations/README.md new file mode 100644 index 0000000..4a13eee --- /dev/null +++ b/animations/README.md @@ -0,0 +1,56 @@ +# Animation Samples +Sample apps that showcase Flutter's animation features + +## Goals + +- Demonstrate the building blocks for animations and how they work together. +- Provide samples for common patterns and use-cases. + +## Samples + +### Basics + +Building blocks and patterns + +1. **AnimatedContainerDemo**: Demonstrates how to use `AnimatedContainer`. +2. **PageRouteBuilderDemo**: Demonstrates how to use `Tween` and `Animation` to + build a custom page route transition. +3. **AnimationControllerDemo**: Demonstrates how to use an + `AnimationController`. +4. **TweenDemo**: Demonstrates how to use a `Tween` with an + `AnimationController`. +5. **AnimatedBuilderDemo**: Demonstrates how to use an `AnimatedBuilder` with an + `AnimationController`. +6. **CustomTweenDemo**: Demonstrates how to extend `Tween`. +7. **TweenSequenceDemo**: Demonstrates how to use `TweenSequence` to build a + button that changes between different colors. +8. **FadeTransitionDemo**: Demonstrates how to use `FadeTransition`. + +### Misc + +Other uses-cases and examples + +- **RepeatingAnimationDemo**: Demonstrates how to repeat an animation. +- **ExpandCardDemo**: Demonstrates how to use `AnimatedCrossFade` to fade + between two widgets and change the size. +- **CarouselDemo**: Demonstrates how to use `PageView` with a custom animation. +- **FocusImageDemo**: Demonstrates how to measure the size of a widget and + expand it using a `PageRouteBuilder`. +- **PhysicsCardDragDemo**: Demonstrates how to run an AnimationController with a + spring simulation. +- **CardSwipeDemo**: A swipeable card that demonstrates how to use gesture + detection to drive an animation. +- **AnimatedList**: Demonstrates how to use `AnimatedList`. +- **AnimatedPositionedDemo**: Demonstrates how to use `AnimatedPositioned`. +- **AnimatedSwitcherDemo**: Demonstrates how to use `AnimatedSwitcher`. +- **HeroAnimationDemo**: Demonstrates how to use `Hero` animation. +- **CurvedAnimationDemo**: Demonstrates how to use different curves in + `CurvedAnimation`. + +## Other Resources + +- [Introduction to animations](https://flutter.dev/docs/development/ui/animations) +- [Animation widgets](https://flutter.dev/docs/development/ui/widgets/animation) +- [Flutter cookbook - Animations](https://flutter.dev/docs/cookbook/animation) +- [Animations tutorial](https://flutter.dev/docs/development/ui/animations/tutorial) +- [Implicit animation codelab](https://flutter.dev/docs/codelabs/implicit-animations) diff --git a/animations/analysis_options.yaml b/animations/analysis_options.yaml new file mode 100644 index 0000000..5e2133e --- /dev/null +++ b/animations/analysis_options.yaml @@ -0,0 +1 @@ +include: ../analysis_options.yaml diff --git a/animations/android/.gitignore b/animations/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/animations/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/animations/android/app/build.gradle b/animations/android/app/build.gradle new file mode 100644 index 0000000..7d2eca0 --- /dev/null +++ b/animations/android/app/build.gradle @@ -0,0 +1,67 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +android { + namespace "dev.flutter.animations" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "dev.flutter.animations" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies {} diff --git a/animations/android/app/src/debug/AndroidManifest.xml b/animations/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/animations/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/animations/android/app/src/main/AndroidManifest.xml b/animations/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..c9ce36f --- /dev/null +++ b/animations/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/animations/android/app/src/main/kotlin/dev/flutter/animations/MainActivity.kt b/animations/android/app/src/main/kotlin/dev/flutter/animations/MainActivity.kt new file mode 100644 index 0000000..022abe6 --- /dev/null +++ b/animations/android/app/src/main/kotlin/dev/flutter/animations/MainActivity.kt @@ -0,0 +1,6 @@ +package dev.flutter.animations + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/animations/android/app/src/main/res/drawable-v21/launch_background.xml b/animations/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/animations/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/animations/android/app/src/main/res/drawable/launch_background.xml b/animations/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/animations/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/animations/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/animations/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/animations/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/animations/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/animations/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/animations/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/animations/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/animations/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/animations/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/animations/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/animations/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/animations/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/animations/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/animations/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/animations/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/animations/android/app/src/main/res/values-night/styles.xml b/animations/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/animations/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/animations/android/app/src/main/res/values/styles.xml b/animations/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/animations/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/animations/android/app/src/profile/AndroidManifest.xml b/animations/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/animations/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/animations/android/build.gradle b/animations/android/build.gradle new file mode 100644 index 0000000..e83fb5d --- /dev/null +++ b/animations/android/build.gradle @@ -0,0 +1,30 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/animations/android/gradle.properties b/animations/android/gradle.properties new file mode 100644 index 0000000..598d13f --- /dev/null +++ b/animations/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G +android.useAndroidX=true +android.enableJetifier=true diff --git a/animations/android/gradle/wrapper/gradle-wrapper.properties b/animations/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/animations/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/animations/android/settings.gradle b/animations/android/settings.gradle new file mode 100644 index 0000000..7cd7128 --- /dev/null +++ b/animations/android/settings.gradle @@ -0,0 +1,29 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + + plugins { + id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false +} + +include ":app" diff --git a/animations/assets/eat_cape_town_sm.jpg b/animations/assets/eat_cape_town_sm.jpg new file mode 100644 index 0000000..5901be3 Binary files /dev/null and b/animations/assets/eat_cape_town_sm.jpg differ diff --git a/animations/assets/eat_new_orleans_sm.jpg b/animations/assets/eat_new_orleans_sm.jpg new file mode 100644 index 0000000..517759e Binary files /dev/null and b/animations/assets/eat_new_orleans_sm.jpg differ diff --git a/animations/assets/eat_sydney_sm.jpg b/animations/assets/eat_sydney_sm.jpg new file mode 100644 index 0000000..63372e8 Binary files /dev/null and b/animations/assets/eat_sydney_sm.jpg differ diff --git a/animations/codelab_rebuild.yaml b/animations/codelab_rebuild.yaml new file mode 100644 index 0000000..8a843ba --- /dev/null +++ b/animations/codelab_rebuild.yaml @@ -0,0 +1,13 @@ +# Run with tooling from https://github.com/flutter/codelabs/tree/main/tooling/codelab_rebuild +name: Animations rebuild script +steps: + - name: Remove runners + rmdirs: + - android + - ios + - linux + - macos + - web + - windows + - name: Flutter recreate + flutter: create --org dev.flutter . diff --git a/animations/fonts/LICENSE.txt b/animations/fonts/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/animations/fonts/LICENSE.txt @@ -0,0 +1,202 @@ + + 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. diff --git a/animations/ios/.gitignore b/animations/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/animations/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/animations/ios/Flutter/AppFrameworkInfo.plist b/animations/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/animations/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/animations/ios/Flutter/Debug.xcconfig b/animations/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/animations/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/animations/ios/Flutter/Release.xcconfig b/animations/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/animations/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/animations/ios/Podfile b/animations/ios/Podfile new file mode 100644 index 0000000..fdcc671 --- /dev/null +++ b/animations/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '11.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/animations/ios/Runner.xcodeproj/project.pbxproj b/animations/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..7af0983 --- /dev/null +++ b/animations/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,614 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807E294A63A400263BE5 /* Frameworks */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.animations; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.animations.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.animations.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.animations.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.animations; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.animations; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/animations/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/animations/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/animations/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/animations/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/animations/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/animations/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/animations/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/animations/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/animations/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/animations/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/animations/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..87131a0 --- /dev/null +++ b/animations/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/animations/ios/Runner.xcworkspace/contents.xcworkspacedata b/animations/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/animations/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/animations/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/animations/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/animations/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/animations/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/animations/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/animations/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/animations/ios/Runner/AppDelegate.swift b/animations/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/animations/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/animations/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/animations/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/animations/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/animations/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/animations/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/animations/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/animations/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/animations/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/animations/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/animations/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/animations/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/animations/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/animations/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/animations/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/animations/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/animations/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/animations/ios/Runner/Base.lproj/LaunchScreen.storyboard b/animations/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/animations/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/animations/ios/Runner/Base.lproj/Main.storyboard b/animations/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/animations/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/animations/ios/Runner/Info.plist b/animations/ios/Runner/Info.plist new file mode 100644 index 0000000..a6d2ff8 --- /dev/null +++ b/animations/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Animations + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + animations + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/animations/ios/Runner/Runner-Bridging-Header.h b/animations/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/animations/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/animations/ios/RunnerTests/RunnerTests.swift b/animations/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/animations/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/animations/lib/main.dart b/animations/lib/main.dart new file mode 100644 index 0000000..21150f4 --- /dev/null +++ b/animations/lib/main.dart @@ -0,0 +1,201 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import 'src/basics/basics.dart'; +import 'src/misc/misc.dart'; + +void main() { + runApp(const AnimationSamples()); +} + +class Demo { + final String name; + final String route; + final WidgetBuilder builder; + + const Demo({ + required this.name, + required this.route, + required this.builder, + }); +} + +final basicDemos = [ + Demo( + name: 'AnimatedContainer', + route: AnimatedContainerDemo.routeName, + builder: (context) => const AnimatedContainerDemo(), + ), + Demo( + name: 'PageRouteBuilder', + route: PageRouteBuilderDemo.routeName, + builder: (context) => const PageRouteBuilderDemo(), + ), + Demo( + name: 'Animation Controller', + route: AnimationControllerDemo.routeName, + builder: (context) => const AnimationControllerDemo(), + ), + Demo( + name: 'Tweens', + route: TweenDemo.routeName, + builder: (context) => const TweenDemo(), + ), + Demo( + name: 'AnimatedBuilder', + route: AnimatedBuilderDemo.routeName, + builder: (context) => const AnimatedBuilderDemo(), + ), + Demo( + name: 'Custom Tween', + route: CustomTweenDemo.routeName, + builder: (context) => const CustomTweenDemo(), + ), + Demo( + name: 'Tween Sequences', + route: TweenSequenceDemo.routeName, + builder: (context) => const TweenSequenceDemo(), + ), + Demo( + name: 'Fade Transition', + route: FadeTransitionDemo.routeName, + builder: (context) => const FadeTransitionDemo(), + ), +]; + +final miscDemos = [ + Demo( + name: 'Expandable Card', + route: ExpandCardDemo.routeName, + builder: (context) => const ExpandCardDemo(), + ), + Demo( + name: 'Carousel', + route: CarouselDemo.routeName, + builder: (context) => CarouselDemo(), + ), + Demo( + name: 'Focus Image', + route: FocusImageDemo.routeName, + builder: (context) => const FocusImageDemo(), + ), + Demo( + name: 'Card Swipe', + route: CardSwipeDemo.routeName, + builder: (context) => const CardSwipeDemo(), + ), + Demo( + name: 'Flutter Animate', + route: FlutterAnimateDemo.routeName, + builder: (context) => const FlutterAnimateDemo(), + ), + Demo( + name: 'Repeating Animation', + route: RepeatingAnimationDemo.routeName, + builder: (context) => const RepeatingAnimationDemo(), + ), + Demo( + name: 'Spring Physics', + route: PhysicsCardDragDemo.routeName, + builder: (context) => const PhysicsCardDragDemo(), + ), + Demo( + name: 'AnimatedList', + route: AnimatedListDemo.routeName, + builder: (context) => const AnimatedListDemo(), + ), + Demo( + name: 'AnimatedPositioned', + route: AnimatedPositionedDemo.routeName, + builder: (context) => const AnimatedPositionedDemo(), + ), + Demo( + name: 'AnimatedSwitcher', + route: AnimatedSwitcherDemo.routeName, + builder: (context) => const AnimatedSwitcherDemo(), + ), + Demo( + name: 'Hero Animation', + route: HeroAnimationDemo.routeName, + builder: (context) => const HeroAnimationDemo(), + ), + Demo( + name: 'Curved Animation', + route: CurvedAnimationDemo.routeName, + builder: (context) => const CurvedAnimationDemo(), + ), +]; + +final router = GoRouter( + routes: [ + GoRoute( + path: '/', + builder: (context, state) => const HomePage(), + routes: [ + for (final demo in basicDemos) + GoRoute( + path: demo.route, + builder: (context, state) => demo.builder(context), + ), + for (final demo in miscDemos) + GoRoute( + path: demo.route, + builder: (context, state) => demo.builder(context), + ), + ], + ), + ], +); + +class AnimationSamples extends StatelessWidget { + const AnimationSamples({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp.router( + title: 'Animation Samples', + theme: ThemeData(colorSchemeSeed: Colors.deepPurple), + routerConfig: router, + ); + } +} + +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + final headerStyle = Theme.of(context).textTheme.titleLarge; + return Scaffold( + appBar: AppBar(title: const Text('Animation Samples')), + body: ListView( + children: [ + ListTile(title: Text('Basics', style: headerStyle)), + ...basicDemos.map((d) => DemoTile(demo: d)), + ListTile(title: Text('Misc', style: headerStyle)), + ...miscDemos.map((d) => DemoTile(demo: d)), + ], + ), + ); + } +} + +class DemoTile extends StatelessWidget { + final Demo demo; + + const DemoTile({required this.demo, super.key}); + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text(demo.name), + onTap: () { + context.go('/${demo.route}'); + }, + ); + } +} diff --git a/animations/lib/src/basics/animated_builder.dart b/animations/lib/src/basics/animated_builder.dart new file mode 100644 index 0000000..030a405 --- /dev/null +++ b/animations/lib/src/basics/animated_builder.dart @@ -0,0 +1,78 @@ +// 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. + +import 'package:flutter/material.dart'; + +class AnimatedBuilderDemo extends StatefulWidget { + const AnimatedBuilderDemo({super.key}); + static const String routeName = 'basics/animated_builder'; + + @override + State createState() => _AnimatedBuilderDemoState(); +} + +class _AnimatedBuilderDemoState extends State + with SingleTickerProviderStateMixin { + static const Color beginColor = Colors.deepPurple; + static const Color endColor = Colors.deepOrange; + Duration duration = const Duration(milliseconds: 800); + late AnimationController controller; + late Animation animation; + + @override + void initState() { + super.initState(); + controller = AnimationController(vsync: this, duration: duration); + animation = ColorTween( + begin: beginColor, + end: endColor, + ).animate(controller); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('AnimatedBuilder')), + body: Center( + // AnimatedBuilder handles listening to a given animation and calling the builder + // whenever the value of the animation change. This can be useful when a Widget + // tree contains some animated and non-animated elements, as only the subtree + // created by the builder needs to be re-built when the animation changes. + child: AnimatedBuilder( + animation: animation, + builder: (context, child) { + return ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: animation.value, + ), + child: child, + onPressed: () { + switch (controller.status) { + case AnimationStatus.completed: + controller.reverse(); + default: + controller.forward(); + } + }, + ); + }, + // AnimatedBuilder can also accept a pre-built child Widget which is useful + // if there is a non-animated Widget contained within the animated widget. + // This can improve performance since this widget doesn't need to be rebuilt + // when the animation changes. + child: const Text( + 'Change Color', + style: TextStyle(color: Colors.white), + ), + ), + ), + ); + } +} diff --git a/animations/lib/src/basics/animated_container.dart b/animations/lib/src/basics/animated_container.dart new file mode 100644 index 0000000..7a772a9 --- /dev/null +++ b/animations/lib/src/basics/animated_container.dart @@ -0,0 +1,80 @@ +// 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. + +import 'dart:math'; + +import 'package:flutter/material.dart'; + +double generateBorderRadius() => Random().nextDouble() * 64; +double generateMargin() => Random().nextDouble() * 64; +Color generateColor() => Color(0xFFFFFFFF & Random().nextInt(0xFFFFFFFF)); + +class AnimatedContainerDemo extends StatefulWidget { + const AnimatedContainerDemo({super.key}); + static String routeName = 'basics/animated_container'; + + @override + State createState() => _AnimatedContainerDemoState(); +} + +class _AnimatedContainerDemoState extends State { + late Color color; + late double borderRadius; + late double margin; + + @override + void initState() { + super.initState(); + color = Colors.deepPurple; + borderRadius = generateBorderRadius(); + margin = generateMargin(); + } + + void change() { + setState(() { + color = generateColor(); + borderRadius = generateBorderRadius(); + margin = generateMargin(); + }); + } + + @override + Widget build(BuildContext context) { + // This widget is built using an AnimatedContainer, one of the easiest to use + // animated Widgets. Whenever the AnimatedContainer's properties, such as decoration, + // change, it will handle animating from the previous value to the new value. You can + // specify both a Duration and a Curve for the animation. + // This Widget is useful for designing animated interfaces that just need to change + // the properties of a container. For example, you could use this to design expanding + // and shrinking cards. + return Scaffold( + appBar: AppBar(title: const Text('AnimatedContainer')), + body: Center( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: SizedBox( + width: 128, + height: 128, + child: AnimatedContainer( + margin: EdgeInsets.all(margin), + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(borderRadius), + ), + duration: const Duration(milliseconds: 400), + ), + ), + ), + ElevatedButton( + child: const Text('change'), + onPressed: () => change(), + ), + ], + ), + ), + ); + } +} diff --git a/animations/lib/src/basics/animation_controller.dart b/animations/lib/src/basics/animation_controller.dart new file mode 100644 index 0000000..c389ca9 --- /dev/null +++ b/animations/lib/src/basics/animation_controller.dart @@ -0,0 +1,86 @@ +// 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. + +import 'package:flutter/material.dart'; + +class AnimationControllerDemo extends StatefulWidget { + const AnimationControllerDemo({super.key}); + static const String routeName = 'basics/animation_controller'; + + @override + State createState() => + _AnimationControllerDemoState(); +} + +class _AnimationControllerDemoState extends State + with SingleTickerProviderStateMixin { + // Using the SingleTickerProviderStateMixin can ensure that our + // AnimationController only animates while the Widget is visible on the + // screen. This is a useful optimization that saves resources when the + // Widget is not visible. + + static const Duration _duration = Duration(seconds: 1); + late final AnimationController controller; + + @override + void initState() { + super.initState(); + + controller = AnimationController(vsync: this, duration: _duration) + // The Widget's build needs to be called every time the animation's + // value changes. So add a listener here that will call setState() + // and trigger the build() method to be called by the framework. + // If your Widget's build is relatively simple, this is a good option. + // However, if your build method returns a tree of child Widgets and + // most of them are not animated you should consider using + // AnimatedBuilder instead. + ..addListener(() { + setState(() {}); + }); + } + + @override + void dispose() { + // AnimationController is a stateful resource that needs to be disposed when + // this State gets disposed. + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + // When building the widget you can read the AnimationController's value property + // when building child widgets. You can also check the status to see if the animation + // has completed. + return Scaffold( + appBar: AppBar(title: const Text('Animation Controller')), + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 200), + child: Text( + controller.value.toStringAsFixed(2), + style: Theme.of(context).textTheme.displaySmall, + textScaler: TextScaler.linear(1 + controller.value), + ), + ), + ElevatedButton( + child: const Text('animate'), + onPressed: () { + switch (controller.status) { + case AnimationStatus.completed: + controller.reverse(); + default: + controller.forward(); + } + }, + ), + ], + ), + ), + ); + } +} diff --git a/animations/lib/src/basics/basics.dart b/animations/lib/src/basics/basics.dart new file mode 100644 index 0000000..7ac4e4a --- /dev/null +++ b/animations/lib/src/basics/basics.dart @@ -0,0 +1,8 @@ +export 'animated_builder.dart'; +export 'animated_container.dart'; +export 'animation_controller.dart'; +export 'custom_tween.dart'; +export 'fade_transition.dart'; +export 'page_route_builder.dart'; +export 'tween_sequence.dart'; +export 'tweens.dart'; diff --git a/animations/lib/src/basics/custom_tween.dart b/animations/lib/src/basics/custom_tween.dart new file mode 100644 index 0000000..a8e1930 --- /dev/null +++ b/animations/lib/src/basics/custom_tween.dart @@ -0,0 +1,123 @@ +// 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. + +import 'package:flutter/material.dart'; + +class TypewriterTween extends Tween { + TypewriterTween({String begin = '', String end = ''}) + : super(begin: begin, end: end); + + @override + String lerp(double t) { + var cutoff = (end!.length * t).round(); + return end!.substring(0, cutoff); + } +} + +class CustomTweenDemo extends StatefulWidget { + const CustomTweenDemo({super.key}); + static const String routeName = 'basics/custom_tweens'; + + @override + State createState() => _CustomTweenDemoState(); +} + +class _CustomTweenDemoState extends State + with SingleTickerProviderStateMixin { + static const Duration _duration = Duration(seconds: 3); + static const String message = loremIpsum; + late final AnimationController controller; + late final Animation animation; + + @override + void initState() { + super.initState(); + + controller = AnimationController(vsync: this, duration: _duration); + animation = TypewriterTween(end: message).animate(controller); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Custom Tween'), + actions: [ + TextButton( + onPressed: () { + if (controller.status == AnimationStatus.completed) { + controller.reverse().whenComplete(() { + setState(() {}); + }); + } else { + controller.forward().whenComplete(() { + setState(() {}); + }); + } + }, + child: Text( + controller.status == AnimationStatus.completed + ? 'Delete Essay' + : 'Write Essay', + ), + ), + ], + ), + body: SafeArea( + child: Container( + alignment: Alignment.bottomCenter, + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Card( + child: Container( + padding: const EdgeInsets.all(8.0), + child: AnimatedBuilder( + animation: animation, + builder: (context, child) { + return Text( + animation.value, + style: const TextStyle( + fontSize: 16, + fontFamily: 'SpecialElite', + ), + ); + }, + ), + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} + +const String loremIpsum = ''' +Sed ut perspiciatis, unde omnis iste natus error sit voluptatem accusantium +doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore +veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim +ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia +consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque +porro quisquam est, qui dolorem ipsum, quia dolor sit amet consectetur +adipisci[ng] velit, sed quia non-numquam [do] eius modi tempora inci[di]dunt, ut +labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, +quis nostrum[d] exercitationem ullam corporis suscipit laboriosam, nisi ut +aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit, qui in +ea voluptate velit esse, quam nihil molestiae consequatur, vel illum, qui +dolorem eum fugiat, quo voluptas nulla pariatur? +'''; diff --git a/animations/lib/src/basics/fade_transition.dart b/animations/lib/src/basics/fade_transition.dart new file mode 100644 index 0000000..8a80845 --- /dev/null +++ b/animations/lib/src/basics/fade_transition.dart @@ -0,0 +1,73 @@ +// 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. + +import 'package:flutter/material.dart'; + +// Refer to the AnimatedWidget docs here - https://api.flutter.dev/flutter/widgets/AnimatedWidget-class.html +// for examples of other common animated widgets. +class FadeTransitionDemo extends StatefulWidget { + const FadeTransitionDemo({super.key}); + static const String routeName = 'basics/fade_transition'; + + @override + State createState() => _FadeTransitionDemoState(); +} + +class _FadeTransitionDemoState extends State + with SingleTickerProviderStateMixin { + late final AnimationController _controller; + late final Animation _animation; + late final CurvedAnimation _curve; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 500), + ); + + _curve = CurvedAnimation(parent: _controller, curve: Curves.easeIn); + + _animation = Tween(begin: 1.0, end: 0.0).animate(_curve); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Fade Transition')), + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + FadeTransition( + opacity: _animation, + child: const Icon( + Icons.star, + color: Colors.amber, + size: 300, + ), + ), + ElevatedButton( + child: const Text('animate'), + onPressed: () => setState(() { + _controller + .animateTo(1.0) + .then( + (value) => _controller.animateBack(0.0), + ); + }), + ), + ], + ), + ), + ); + } +} diff --git a/animations/lib/src/basics/page_route_builder.dart b/animations/lib/src/basics/page_route_builder.dart new file mode 100644 index 0000000..f9b474a --- /dev/null +++ b/animations/lib/src/basics/page_route_builder.dart @@ -0,0 +1,58 @@ +// 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. + +import 'package:flutter/material.dart'; + +class PageRouteBuilderDemo extends StatelessWidget { + const PageRouteBuilderDemo({super.key}); + static const String routeName = 'basics/page_route_builder'; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Page 1')), + body: Center( + child: ElevatedButton( + child: const Text('Go!'), + onPressed: () { + Navigator.of(context).push(_createRoute()); + }, + ), + ), + ); + } +} + +Route _createRoute() { + return PageRouteBuilder( + pageBuilder: (context, animation, secondaryAnimation) => _Page2(), + transitionsBuilder: (context, animation, secondaryAnimation, child) { + var tween = Tween( + begin: const Offset(0.0, 1.0), + end: Offset.zero, + ); + var curveTween = CurveTween(curve: Curves.ease); + + return SlideTransition( + position: animation.drive(curveTween).drive(tween), + child: child, + ); + }, + ); +} + +class _Page2 extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Page 2')), + body: Center( + child: Text( + 'Page 2!', + style: Theme.of(context).textTheme.headlineMedium, + ), + ), + ); + } +} diff --git a/animations/lib/src/basics/tween_sequence.dart b/animations/lib/src/basics/tween_sequence.dart new file mode 100644 index 0000000..05b7b58 --- /dev/null +++ b/animations/lib/src/basics/tween_sequence.dart @@ -0,0 +1,85 @@ +// 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. + +import 'package:flutter/material.dart'; + +class TweenSequenceDemo extends StatefulWidget { + const TweenSequenceDemo({super.key}); + static const String routeName = 'basics/chaining_tweens'; + + @override + State createState() => _TweenSequenceDemoState(); +} + +class _TweenSequenceDemoState extends State + with SingleTickerProviderStateMixin { + static const Duration duration = Duration(seconds: 3); + late final AnimationController controller; + late final Animation animation; + + static final colors = [ + Colors.red, + Colors.orange, + Colors.yellow, + Colors.green, + Colors.blue, + Colors.indigo, + Colors.purple, + ]; + + @override + void initState() { + super.initState(); + + final sequenceItems = >[]; + + for (var i = 0; i < colors.length; i++) { + final beginColor = colors[i]; + final endColor = colors[(i + 1) % colors.length]; + final weight = 1 / colors.length; + + sequenceItems.add( + TweenSequenceItem( + tween: ColorTween(begin: beginColor, end: endColor), + weight: weight, + ), + ); + } + + controller = AnimationController(duration: duration, vsync: this); + animation = TweenSequence(sequenceItems).animate(controller); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Tween Sequences')), + body: Center( + child: AnimatedBuilder( + animation: animation, + builder: (context, child) { + return MaterialButton( + color: animation.value, + onPressed: () async { + await controller.forward(); + controller.reset(); + }, + child: child, + ); + }, + child: const Text( + 'Animate', + style: TextStyle(color: Colors.white), + ), + ), + ), + ); + } +} diff --git a/animations/lib/src/basics/tweens.dart b/animations/lib/src/basics/tweens.dart new file mode 100644 index 0000000..c6120c8 --- /dev/null +++ b/animations/lib/src/basics/tweens.dart @@ -0,0 +1,76 @@ +// 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. + +import 'package:flutter/material.dart'; + +class TweenDemo extends StatefulWidget { + const TweenDemo({super.key}); + static const String routeName = 'basics/tweens'; + + @override + State createState() => _TweenDemoState(); +} + +class _TweenDemoState extends State + with SingleTickerProviderStateMixin { + static const Duration _duration = Duration(seconds: 1); + static const double accountBalance = 1000000; + late final AnimationController controller; + late final Animation animation; + + @override + void initState() { + super.initState(); + + controller = AnimationController(vsync: this, duration: _duration) + ..addListener(() { + // Marks the widget tree as dirty + setState(() {}); + }); + animation = Tween(begin: 0.0, end: accountBalance).animate(controller); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Tweens')), + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 200), + child: Text( + '\$${animation.value.toStringAsFixed(2)}', + style: const TextStyle(fontSize: 24), + ), + ), + ElevatedButton( + child: Text(switch (controller.status) { + AnimationStatus.completed => 'Buy a Mansion', + AnimationStatus.forward => 'Accruing...', + AnimationStatus.reverse => 'Spending...', + _ => 'Win the lottery', + }), + onPressed: () { + switch (controller.status) { + case AnimationStatus.completed: + controller.reverse(); + default: + controller.forward(); + } + }, + ), + ], + ), + ), + ); + } +} diff --git a/animations/lib/src/misc/animated_list.dart b/animations/lib/src/misc/animated_list.dart new file mode 100644 index 0000000..0144e6f --- /dev/null +++ b/animations/lib/src/misc/animated_list.dart @@ -0,0 +1,107 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; + +class AnimatedListDemo extends StatefulWidget { + const AnimatedListDemo({super.key}); + static String routeName = 'misc/animated_list'; + + @override + State createState() => _AnimatedListDemoState(); +} + +class _AnimatedListDemoState extends State { + final GlobalKey _listKey = GlobalKey(); + final listData = [ + UserModel(0, 'Govind', 'Dixit'), + UserModel(1, 'Greta', 'Stoll'), + UserModel(2, 'Monty', 'Carlo'), + UserModel(3, 'Petey', 'Cruiser'), + UserModel(4, 'Barry', 'Cade'), + ]; + final initialListSize = 5; + + void addUser() { + setState(() { + var index = listData.length; + listData.add(UserModel(++_maxIdValue, 'New', 'Person')); + _listKey.currentState!.insertItem( + index, + duration: const Duration(milliseconds: 300), + ); + }); + } + + void deleteUser(int id) { + setState(() { + final index = listData.indexWhere((u) => u.id == id); + var user = listData.removeAt(index); + _listKey.currentState!.removeItem(index, (context, animation) { + return FadeTransition( + opacity: CurvedAnimation( + parent: animation, + curve: const Interval(0.5, 1.0), + ), + child: SizeTransition( + sizeFactor: CurvedAnimation( + parent: animation, + curve: const Interval(0.0, 1.0), + ), + // ignore: deprecated_member_use + axisAlignment: 0.0, + child: _buildItem(user), + ), + ); + }, duration: const Duration(milliseconds: 600)); + }); + } + + Widget _buildItem(UserModel user) { + return ListTile( + key: ValueKey(user), + title: Text(user.firstName), + subtitle: Text(user.lastName), + leading: const CircleAvatar(child: Icon(Icons.person)), + trailing: IconButton( + icon: const Icon(Icons.delete), + onPressed: () => deleteUser(user.id), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('AnimatedList'), + actions: [ + IconButton(icon: const Icon(Icons.add), onPressed: addUser), + ], + ), + body: SafeArea( + child: AnimatedList( + key: _listKey, + initialItemCount: 5, + itemBuilder: (context, index, animation) { + return FadeTransition( + opacity: animation, + child: _buildItem(listData[index]), + ); + }, + ), + ), + ); + } +} + +class UserModel { + UserModel(this.id, this.firstName, this.lastName); + + final int id; + final String firstName; + final String lastName; +} + +int _maxIdValue = 4; diff --git a/animations/lib/src/misc/animated_positioned.dart b/animations/lib/src/misc/animated_positioned.dart new file mode 100644 index 0000000..cc703be --- /dev/null +++ b/animations/lib/src/misc/animated_positioned.dart @@ -0,0 +1,84 @@ +// Copyright 2020 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. + +import 'dart:math'; + +import 'package:flutter/material.dart'; + +class AnimatedPositionedDemo extends StatefulWidget { + const AnimatedPositionedDemo({super.key}); + static String routeName = 'misc/animated_positioned'; + + @override + State createState() => + _AnimatedPositionedDemoState(); +} + +class _AnimatedPositionedDemoState extends State { + late double topPosition; + late double leftPosition; + + double generateTopPosition(double top) => Random().nextDouble() * top; + + double generateLeftPosition(double left) => Random().nextDouble() * left; + + @override + void initState() { + super.initState(); + topPosition = generateTopPosition(30); + leftPosition = generateLeftPosition(30); + } + + void changePosition(double top, double left) { + setState(() { + topPosition = generateTopPosition(top); + leftPosition = generateLeftPosition(left); + }); + } + + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + final appBar = AppBar(title: const Text('AnimatedPositioned')); + final topPadding = MediaQuery.of(context).padding.top; + // AnimatedPositioned animates changes to a widget's position within a Stack + return Scaffold( + appBar: appBar, + body: SizedBox( + height: size.height, + width: size.width, + child: Stack( + children: [ + AnimatedPositioned( + top: topPosition, + left: leftPosition, + duration: const Duration(seconds: 1), + child: InkWell( + onTap: () => changePosition( + size.height - + (appBar.preferredSize.height + topPadding + 50), + size.width - 150, + ), + child: Container( + alignment: Alignment.center, + width: 150, + height: 50, + color: Theme.of(context).primaryColor, + child: Text( + 'Click Me', + style: TextStyle( + color: Theme.of( + context, + ).buttonTheme.colorScheme!.onPrimary, + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/animations/lib/src/misc/animated_switcher.dart b/animations/lib/src/misc/animated_switcher.dart new file mode 100644 index 0000000..96427f8 --- /dev/null +++ b/animations/lib/src/misc/animated_switcher.dart @@ -0,0 +1,70 @@ +// Copyright 2020 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. + +import 'dart:math'; + +import 'package:flutter/material.dart'; + +Color generateColor() => Color(0xFFFFFFFF & Random().nextInt(0xFFFFFFFF)); + +Widget generateContainer(int keyCount) => Container( + key: ValueKey(keyCount), + height: Random().nextDouble() * 200, + width: Random().nextDouble() * 200, + decoration: BoxDecoration( + color: generateColor(), + borderRadius: BorderRadius.circular(Random().nextDouble() * 100), + border: Border.all( + color: generateColor(), + width: Random().nextDouble() * 5, + ), + ), +); + +class AnimatedSwitcherDemo extends StatefulWidget { + const AnimatedSwitcherDemo({super.key}); + static String routeName = 'misc/animated_switcher'; + + @override + State createState() => _AnimatedSwitcherDemoState(); +} + +class _AnimatedSwitcherDemoState extends State { + late Widget container; + late int keyCount; + + @override + void initState() { + super.initState(); + keyCount = 0; + container = generateContainer(keyCount); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('AnimatedSwitcher'), + actions: [ + TextButton( + onPressed: () => + setState(() => container = generateContainer(++keyCount)), + child: const Text('Change Widget'), + ), + ], + ), + body: Center( + // AnimatedSwitcher Widget is used to switch between different widgets + // with a given transition. You can change the transitions by using + // transitionBuilder property. + child: AnimatedSwitcher( + duration: const Duration(seconds: 1), + child: container, + transitionBuilder: (child, animation) => + ScaleTransition(scale: animation, child: child), + ), + ), + ); + } +} diff --git a/animations/lib/src/misc/card_swipe.dart b/animations/lib/src/misc/card_swipe.dart new file mode 100644 index 0000000..7458c98 --- /dev/null +++ b/animations/lib/src/misc/card_swipe.dart @@ -0,0 +1,212 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:flutter/physics.dart'; + +class CardSwipeDemo extends StatefulWidget { + const CardSwipeDemo({super.key}); + static String routeName = 'misc/card_swipe'; + + @override + State createState() => _CardSwipeDemoState(); +} + +class _CardSwipeDemoState extends State { + late List fileNames; + + @override + void initState() { + super.initState(); + _resetCards(); + } + + void _resetCards() { + fileNames = [ + 'assets/eat_cape_town_sm.jpg', + 'assets/eat_new_orleans_sm.jpg', + 'assets/eat_sydney_sm.jpg', + ]; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Card Swipe')), + body: Padding( + padding: const EdgeInsets.all(12.0), + child: Center( + child: Column( + children: [ + Expanded( + child: ClipRect( + child: Stack( + children: [ + for (final fileName in fileNames) + SwipeableCard( + imageAssetName: fileName, + onSwiped: () { + setState(() { + fileNames.remove(fileName); + }); + }, + ), + ], + ), + ), + ), + ElevatedButton( + child: const Text('Refill'), + onPressed: () { + setState(() { + _resetCards(); + }); + }, + ), + ], + ), + ), + ), + ); + } +} + +class Card extends StatelessWidget { + final String imageAssetName; + + const Card({required this.imageAssetName, super.key}); + + @override + Widget build(BuildContext context) { + return AspectRatio( + aspectRatio: 3 / 5, + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20.0), + image: DecorationImage( + image: AssetImage(imageAssetName), + fit: BoxFit.cover, + ), + ), + ), + ); + } +} + +class SwipeableCard extends StatefulWidget { + final String imageAssetName; + final VoidCallback onSwiped; + + const SwipeableCard({ + required this.onSwiped, + required this.imageAssetName, + super.key, + }); + + @override + State createState() => _SwipeableCardState(); +} + +class _SwipeableCardState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _animation; + late double _dragStartX; + bool _isSwipingLeft = false; + + @override + void initState() { + super.initState(); + _controller = AnimationController.unbounded(vsync: this); + _animation = _controller.drive( + Tween(begin: Offset.zero, end: const Offset(1, 0)), + ); + } + + @override + Widget build(BuildContext context) { + return SlideTransition( + position: _animation, + child: GestureDetector( + onHorizontalDragStart: _dragStart, + onHorizontalDragUpdate: _dragUpdate, + onHorizontalDragEnd: _dragEnd, + child: Card(imageAssetName: widget.imageAssetName), + ), + ); + } + + /// Sets the starting position the user dragged from. + void _dragStart(DragStartDetails details) { + _dragStartX = details.localPosition.dx; + } + + /// Changes the animation to animate to the left or right depending on the + /// swipe, and sets the AnimationController's value to the swiped amount. + void _dragUpdate(DragUpdateDetails details) { + var isSwipingLeft = (details.localPosition.dx - _dragStartX) < 0; + if (isSwipingLeft != _isSwipingLeft) { + _isSwipingLeft = isSwipingLeft; + _updateAnimation(details.localPosition.dx); + } + + setState(() { + final size = context.size; + + if (size == null) { + return; + } + + // Calculate the amount dragged in unit coordinates (between 0 and 1) + // using this widgets width. + _controller.value = + (details.localPosition.dx - _dragStartX).abs() / size.width; + }); + } + + /// Runs the fling / spring animation using the final velocity of the drag + /// gesture. + void _dragEnd(DragEndDetails details) { + final size = context.size; + + if (size == null) { + return; + } + + var velocity = (details.velocity.pixelsPerSecond.dx / size.width).abs(); + _animate(velocity: velocity); + } + + void _updateAnimation(double dragPosition) { + _animation = _controller.drive( + Tween( + begin: Offset.zero, + end: _isSwipingLeft ? const Offset(-1, 0) : const Offset(1, 0), + ), + ); + } + + void _animate({double velocity = 0}) { + var description = const SpringDescription( + mass: 50, + stiffness: 1, + damping: 1, + ); + var simulation = SpringSimulation( + description, + _controller.value, + 1, + velocity, + ); + _controller.animateWith(simulation).then((_) { + widget.onSwiped(); + }); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } +} diff --git a/animations/lib/src/misc/carousel.dart b/animations/lib/src/misc/carousel.dart new file mode 100644 index 0000000..67c9c82 --- /dev/null +++ b/animations/lib/src/misc/carousel.dart @@ -0,0 +1,115 @@ +// 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. + +import 'dart:ui' as ui; +import 'package:flutter/material.dart'; + +class CarouselDemo extends StatelessWidget { + CarouselDemo({super.key}); + static String routeName = 'misc/carousel'; + + static const List fileNames = [ + 'assets/eat_cape_town_sm.jpg', + 'assets/eat_new_orleans_sm.jpg', + 'assets/eat_sydney_sm.jpg', + ]; + + final List images = fileNames + .map((file) => Image.asset(file, fit: BoxFit.cover)) + .toList(); + + @override + Widget build(context) { + return Scaffold( + appBar: AppBar(title: const Text('Carousel Demo')), + body: Center( + child: Padding( + padding: const EdgeInsets.all(16), + child: AspectRatio( + aspectRatio: 1, + child: Carousel(itemBuilder: widgetBuilder), + ), + ), + ), + ); + } + + Widget widgetBuilder(BuildContext context, int index) { + return images[index % images.length]; + } +} + +typedef OnCurrentItemChangedCallback = void Function(int currentItem); + +class Carousel extends StatefulWidget { + final IndexedWidgetBuilder itemBuilder; + + const Carousel({super.key, required this.itemBuilder}); + + @override + State createState() => _CarouselState(); +} + +class _CarouselState extends State { + late final PageController _controller; + late int _currentPage; + bool _pageHasChanged = false; + + @override + void initState() { + super.initState(); + _currentPage = 0; + _controller = PageController( + viewportFraction: .85, + initialPage: _currentPage, + ); + } + + @override + Widget build(context) { + var size = MediaQuery.of(context).size; + return PageView.builder( + onPageChanged: (value) { + setState(() { + _pageHasChanged = true; + _currentPage = value; + }); + }, + controller: _controller, + scrollBehavior: ScrollConfiguration.of(context).copyWith( + dragDevices: { + ui.PointerDeviceKind.touch, + ui.PointerDeviceKind.mouse, + }, + ), + itemBuilder: (context, index) => AnimatedBuilder( + animation: _controller, + builder: (context, child) { + var result = _pageHasChanged + ? _controller.page! + : _currentPage * 1.0; + + // The horizontal position of the page between a 1 and 0 + var value = result - index; + value = (1 - (value.abs() * .5)).clamp(0.0, 1.0); + + return Center( + child: SizedBox( + height: Curves.easeOut.transform(value) * size.height, + width: Curves.easeOut.transform(value) * size.width, + child: child, + ), + ); + }, + child: widget.itemBuilder(context, index), + ), + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } +} diff --git a/animations/lib/src/misc/curved_animation.dart b/animations/lib/src/misc/curved_animation.dart new file mode 100644 index 0000000..e460653 --- /dev/null +++ b/animations/lib/src/misc/curved_animation.dart @@ -0,0 +1,159 @@ +// Copyright 2020 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. + +import 'dart:math' as math; +import 'package:flutter/material.dart'; + +class CurvedAnimationDemo extends StatefulWidget { + const CurvedAnimationDemo({super.key}); + static const String routeName = 'misc/curved_animation'; + + @override + State createState() => _CurvedAnimationDemoState(); +} + +class CurveChoice { + final Curve curve; + final String name; + + const CurveChoice({required this.curve, required this.name}); +} + +class _CurvedAnimationDemoState extends State + with SingleTickerProviderStateMixin { + late final AnimationController controller; + late final Animation animationRotation; + late final Animation animationTranslation; + static const _duration = Duration(seconds: 4); + List curves = const [ + CurveChoice(curve: Curves.bounceIn, name: 'Bounce In'), + CurveChoice(curve: Curves.bounceOut, name: 'Bounce Out'), + CurveChoice(curve: Curves.easeInCubic, name: 'Ease In Cubic'), + CurveChoice(curve: Curves.easeOutCubic, name: 'Ease Out Cubic'), + CurveChoice(curve: Curves.easeInExpo, name: 'Ease In Expo'), + CurveChoice(curve: Curves.easeOutExpo, name: 'Ease Out Expo'), + CurveChoice(curve: Curves.elasticIn, name: 'Elastic In'), + CurveChoice(curve: Curves.elasticOut, name: 'Elastic Out'), + CurveChoice(curve: Curves.easeInQuart, name: 'Ease In Quart'), + CurveChoice(curve: Curves.easeOutQuart, name: 'Ease Out Quart'), + CurveChoice(curve: Curves.easeInCirc, name: 'Ease In Circle'), + CurveChoice(curve: Curves.easeOutCirc, name: 'Ease Out Circle'), + ]; + late CurveChoice selectedForwardCurve, selectedReverseCurve; + late final CurvedAnimation curvedAnimation; + + @override + void initState() { + super.initState(); + controller = AnimationController(duration: _duration, vsync: this); + selectedForwardCurve = curves[0]; + selectedReverseCurve = curves[0]; + curvedAnimation = CurvedAnimation( + parent: controller, + curve: selectedForwardCurve.curve, + reverseCurve: selectedReverseCurve.curve, + ); + animationRotation = + Tween(begin: 0, end: 2 * math.pi).animate(curvedAnimation) + ..addListener(() { + setState(() {}); + }) + ..addStatusListener((status) { + if (status == AnimationStatus.completed) { + controller.reverse(); + } + }); + animationTranslation = + Tween( + begin: const Offset(-1, 0), + end: const Offset(1, 0), + ).animate(curvedAnimation) + ..addListener(() { + setState(() {}); + }) + ..addStatusListener((status) { + if (status == AnimationStatus.completed) { + controller.reverse(); + } + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Curved Animation')), + body: Column( + children: [ + const SizedBox(height: 20.0), + Text( + 'Select Curve for forward motion', + style: Theme.of(context).textTheme.titleLarge, + ), + DropdownButton( + items: curves.map((curve) { + return DropdownMenuItem( + value: curve, + child: Text(curve.name), + ); + }).toList(), + onChanged: (newCurve) { + if (newCurve != null) { + setState(() { + selectedForwardCurve = newCurve; + curvedAnimation.curve = selectedForwardCurve.curve; + }); + } + }, + value: selectedForwardCurve, + ), + const SizedBox(height: 15.0), + Text( + 'Select Curve for reverse motion', + style: Theme.of(context).textTheme.titleLarge, + ), + DropdownButton( + items: curves.map((curve) { + return DropdownMenuItem( + value: curve, + child: Text(curve.name), + ); + }).toList(), + onChanged: (newCurve) { + if (newCurve != null) { + setState(() { + selectedReverseCurve = newCurve; + curvedAnimation.reverseCurve = selectedReverseCurve.curve; + }); + } + }, + value: selectedReverseCurve, + ), + const SizedBox(height: 35.0), + Transform.rotate( + angle: animationRotation.value, + child: const Center(child: FlutterLogo(size: 100)), + ), + const SizedBox(height: 35.0), + FractionalTranslation( + translation: animationTranslation.value, + child: const FlutterLogo(size: 100), + ), + const SizedBox(height: 25.0), + ElevatedButton( + onPressed: () { + controller.forward(); + }, + child: const Text('Animate'), + ), + ], + ), + ); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } +} diff --git a/animations/lib/src/misc/expand_card.dart b/animations/lib/src/misc/expand_card.dart new file mode 100644 index 0000000..d537d15 --- /dev/null +++ b/animations/lib/src/misc/expand_card.dart @@ -0,0 +1,87 @@ +// 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. + +import 'package:flutter/material.dart'; + +class ExpandCardDemo extends StatelessWidget { + const ExpandCardDemo({super.key}); + static const String routeName = 'misc/expand_card'; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Expandable Card')), + body: const Center(child: ExpandCard()), + ); + } +} + +class ExpandCard extends StatefulWidget { + const ExpandCard({super.key}); + @override + State createState() => _ExpandCardState(); +} + +class _ExpandCardState extends State + with SingleTickerProviderStateMixin { + static const Duration duration = Duration(milliseconds: 300); + bool selected = false; + + double get size => selected ? 256 : 128; + + void toggleExpanded() { + setState(() { + selected = !selected; + }); + } + + @override + Widget build(context) { + return GestureDetector( + onTap: () => toggleExpanded(), + child: Card( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: AnimatedContainer( + duration: duration, + width: size, + height: size, + curve: Curves.ease, + child: AnimatedCrossFade( + duration: duration, + firstCurve: Curves.easeInOutCubic, + secondCurve: Curves.easeInOutCubic, + crossFadeState: selected + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, + // Use Positioned.fill() to pass the constraints to its children. + // This allows the Images to use BoxFit.cover to cover the correct + // size + layoutBuilder: + (topChild, topChildKey, bottomChild, bottomChildKey) { + return Stack( + children: [ + Positioned.fill( + key: bottomChildKey, + child: bottomChild, + ), + Positioned.fill(key: topChildKey, child: topChild), + ], + ); + }, + firstChild: Image.asset( + 'assets/eat_cape_town_sm.jpg', + fit: BoxFit.cover, + ), + secondChild: Image.asset( + 'assets/eat_new_orleans_sm.jpg', + fit: BoxFit.cover, + ), + ), + ), + ), + ), + ); + } +} diff --git a/animations/lib/src/misc/flutter_animate.dart b/animations/lib/src/misc/flutter_animate.dart new file mode 100644 index 0000000..c31dde4 --- /dev/null +++ b/animations/lib/src/misc/flutter_animate.dart @@ -0,0 +1,40 @@ +// Copyright 2023 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. + +import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; + +// Demonstrating the `flutter_animate` package +class FlutterAnimateDemo extends StatelessWidget { + static const String routeName = 'misc/flutter_animate'; + + const FlutterAnimateDemo({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Flutter Animate Demo')), + body: Center( + child: Padding( + padding: const EdgeInsets.all(16), + child: + Text( + "Hello Flutter Animate", + style: Theme.of(context).textTheme.headlineLarge, + ) + .animate(onPlay: (controller) => controller.repeat()) + .then(delay: 250.ms) + .fadeIn(duration: 500.ms) + .then(delay: 250.ms) + .shimmer(duration: 400.ms) + .then(delay: 250.ms) + .slide() + .then(delay: 250.ms) + .blur(duration: 500.ms) + .then(delay: 100.ms), + ), + ), + ); + } +} diff --git a/animations/lib/src/misc/focus_image.dart b/animations/lib/src/misc/focus_image.dart new file mode 100644 index 0000000..542fbb0 --- /dev/null +++ b/animations/lib/src/misc/focus_image.dart @@ -0,0 +1,114 @@ +// 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. + +import 'package:flutter/material.dart'; + +class FocusImageDemo extends StatelessWidget { + const FocusImageDemo({super.key}); + static String routeName = 'misc/focus_image'; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Focus Image')), + body: const Grid(), + ); + } +} + +class Grid extends StatelessWidget { + const Grid({super.key}); + @override + Widget build(BuildContext context) { + return Scaffold( + body: GridView.builder( + itemCount: 40, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, + ), + itemBuilder: (context, index) { + return (index >= 20) + ? const SmallCard( + imageAssetName: 'assets/eat_cape_town_sm.jpg', + ) + : const SmallCard( + imageAssetName: 'assets/eat_new_orleans_sm.jpg', + ); + }, + ), + ); + } +} + +Route _createRoute(BuildContext parentContext, String image) { + return PageRouteBuilder( + pageBuilder: (context, animation, secondaryAnimation) { + return _SecondPage(image); + }, + transitionsBuilder: (context, animation, secondaryAnimation, child) { + var rectAnimation = _createTween( + parentContext, + ).chain(CurveTween(curve: Curves.ease)).animate(animation); + + return Stack( + children: [ + PositionedTransition(rect: rectAnimation, child: child), + ], + ); + }, + ); +} + +Tween _createTween(BuildContext context) { + var windowSize = MediaQuery.of(context).size; + var box = context.findRenderObject() as RenderBox; + var rect = box.localToGlobal(Offset.zero) & box.size; + var relativeRect = RelativeRect.fromSize(rect, windowSize); + + return RelativeRectTween(begin: relativeRect, end: RelativeRect.fill); +} + +class SmallCard extends StatelessWidget { + const SmallCard({required this.imageAssetName, super.key}); + final String imageAssetName; + + @override + Widget build(BuildContext context) { + return Card( + child: Material( + child: InkWell( + onTap: () { + var nav = Navigator.of(context); + nav.push(_createRoute(context, imageAssetName)); + }, + child: Image.asset(imageAssetName, fit: BoxFit.cover), + ), + ), + ); + } +} + +class _SecondPage extends StatelessWidget { + final String imageAssetName; + + const _SecondPage(this.imageAssetName); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + body: Center( + child: Material( + child: InkWell( + onTap: () => Navigator.of(context).pop(), + child: AspectRatio( + aspectRatio: 1, + child: Image.asset(imageAssetName, fit: BoxFit.cover), + ), + ), + ), + ), + ); + } +} diff --git a/animations/lib/src/misc/hero_animation.dart b/animations/lib/src/misc/hero_animation.dart new file mode 100644 index 0000000..372a416 --- /dev/null +++ b/animations/lib/src/misc/hero_animation.dart @@ -0,0 +1,66 @@ +// 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. + +import 'package:flutter/material.dart'; + +class HeroAnimationDemo extends StatelessWidget { + const HeroAnimationDemo({super.key}); + static const String routeName = 'misc/hero_animation'; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Hero Animation')), + body: GestureDetector( + child: Hero( + tag: 'hero-page-child', + child: _createHeroContainer( + size: 50.0, + color: Colors.grey.shade300, + ), + ), + onTap: () => + Navigator.of( + context, + ).push( + MaterialPageRoute(builder: (context) => const HeroPage()), + ), + ), + ); + } +} + +class HeroPage extends StatelessWidget { + const HeroPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.lightBlue, + appBar: AppBar(), + body: Center( + child: Hero( + tag: 'hero-page-child', + child: _createHeroContainer(size: 100.0, color: Colors.white), + ), + ), + ); + } +} + +StatelessWidget _createHeroContainer({ + required double size, + required Color color, +}) { + return Container( + height: size, + width: size, + padding: const EdgeInsets.all(10.0), + margin: size < 100.0 + ? const EdgeInsets.all(10.0) + : const EdgeInsets.all(0), + decoration: BoxDecoration(shape: BoxShape.circle, color: color), + child: const FlutterLogo(), + ); +} diff --git a/animations/lib/src/misc/misc.dart b/animations/lib/src/misc/misc.dart new file mode 100644 index 0000000..6344744 --- /dev/null +++ b/animations/lib/src/misc/misc.dart @@ -0,0 +1,12 @@ +export 'animated_list.dart'; +export 'animated_positioned.dart'; +export 'animated_switcher.dart'; +export 'card_swipe.dart'; +export 'carousel.dart'; +export 'curved_animation.dart'; +export 'expand_card.dart'; +export 'flutter_animate.dart'; +export 'focus_image.dart'; +export 'hero_animation.dart'; +export 'physics_card_drag.dart'; +export 'repeating_animation.dart'; diff --git a/animations/lib/src/misc/physics_card_drag.dart b/animations/lib/src/misc/physics_card_drag.dart new file mode 100644 index 0000000..e401c6e --- /dev/null +++ b/animations/lib/src/misc/physics_card_drag.dart @@ -0,0 +1,111 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:flutter/physics.dart'; + +class PhysicsCardDragDemo extends StatelessWidget { + const PhysicsCardDragDemo({super.key}); + static const String routeName = 'misc/physics_card'; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Spring Physics')), + body: const DraggableCard(child: FlutterLogo(size: 128)), + ); + } +} + +/// A draggable card that moves back to [Alignment.center] when it's +/// released. +class DraggableCard extends StatefulWidget { + const DraggableCard({required this.child, super.key}); + final Widget child; + + @override + State createState() => _DraggableCardState(); +} + +class _DraggableCardState extends State + with SingleTickerProviderStateMixin { + late final AnimationController _controller; + + /// The alignment of the card as it is dragged or being animated. + /// + /// While the card is being dragged, this value is set to the values computed + /// in the GestureDetector onPanUpdate callback. If the animation is running, + /// this value is set to the value of the [_animation]. + var _dragAlignment = Alignment.center; + + late Animation _animation; + + final _spring = const SpringDescription( + mass: 10, + stiffness: 1000, + damping: 0.7, + ); + + /// Calculate the velocity relative to the unit interval, [0,1], + /// used by the animation controller. + double _normalizeVelocity(Offset velocity, Size size) { + final normalizedVelocity = Offset( + velocity.dx / size.width, + velocity.dy / size.height, + ); + // Returning negative implies dragging away from center + return -normalizedVelocity.distance; + } + + /// Calculates and runs a [SpringSimulation] + void _runAnimation(Offset velocity, Size size) { + _animation = _controller.drive( + AlignmentTween(begin: _dragAlignment, end: Alignment.center), + ); + + final simulation = SpringSimulation( + _spring, + 0, + 1, + _normalizeVelocity(velocity, size), + ); + + _controller.animateWith(simulation); + } + + @override + void initState() { + super.initState(); + _controller = AnimationController.unbounded(vsync: this) + ..addListener( + () => setState(() => _dragAlignment = _animation.value), + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + return GestureDetector( + onPanStart: (details) => _controller.stop(canceled: true), + onPanUpdate: (details) => setState( + () => _dragAlignment += Alignment( + details.delta.dx / (size.width / 2), + details.delta.dy / (size.height / 2), + ), + ), + onPanEnd: (details) => + _runAnimation(details.velocity.pixelsPerSecond, size), + child: Align( + alignment: _dragAlignment, + child: Card(child: widget.child), + ), + ); + } +} diff --git a/animations/lib/src/misc/repeating_animation.dart b/animations/lib/src/misc/repeating_animation.dart new file mode 100644 index 0000000..2f1a215 --- /dev/null +++ b/animations/lib/src/misc/repeating_animation.dart @@ -0,0 +1,63 @@ +// 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. + +import 'package:flutter/material.dart'; + +class RepeatingAnimationDemo extends StatefulWidget { + const RepeatingAnimationDemo({super.key}); + static String routeName = 'misc/repeating_animation'; + + @override + State createState() => + _RepeatingAnimationDemoState(); +} + +class _RepeatingAnimationDemoState extends State + with SingleTickerProviderStateMixin { + late final AnimationController _controller; + late final Animation _borderRadius; + + @override + void initState() { + super.initState(); + + _controller = AnimationController( + duration: const Duration(seconds: 2), + vsync: this, + )..repeat(reverse: true); + + _borderRadius = BorderRadiusTween( + begin: BorderRadius.circular(100.0), + end: BorderRadius.circular(0.0), + ).animate(_controller); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Repeating Animation')), + body: Center( + child: AnimatedBuilder( + animation: _borderRadius, + builder: (context, child) { + return Container( + width: 200, + height: 200, + decoration: BoxDecoration( + color: Colors.deepPurple, + borderRadius: _borderRadius.value, + ), + ); + }, + ), + ), + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } +} diff --git a/animations/linux/.gitignore b/animations/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/animations/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/animations/linux/CMakeLists.txt b/animations/linux/CMakeLists.txt new file mode 100644 index 0000000..efa55d8 --- /dev/null +++ b/animations/linux/CMakeLists.txt @@ -0,0 +1,145 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "animations") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "dev.flutter.animations") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/animations/linux/flutter/CMakeLists.txt b/animations/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/animations/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/animations/linux/flutter/generated_plugin_registrant.cc b/animations/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..e71a16d --- /dev/null +++ b/animations/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/animations/linux/flutter/generated_plugin_registrant.h b/animations/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/animations/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/animations/linux/flutter/generated_plugins.cmake b/animations/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..2e1de87 --- /dev/null +++ b/animations/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/animations/linux/main.cc b/animations/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/animations/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/animations/linux/my_application.cc b/animations/linux/my_application.cc new file mode 100644 index 0000000..7f0d8b4 --- /dev/null +++ b/animations/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "animations"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "animations"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/animations/linux/my_application.h b/animations/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/animations/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/animations/macos/.gitignore b/animations/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/animations/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/animations/macos/Flutter/Flutter-Debug.xcconfig b/animations/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/animations/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/animations/macos/Flutter/Flutter-Release.xcconfig b/animations/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/animations/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/animations/macos/Flutter/GeneratedPluginRegistrant.swift b/animations/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..cccf817 --- /dev/null +++ b/animations/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,10 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { +} diff --git a/animations/macos/Podfile b/animations/macos/Podfile new file mode 100644 index 0000000..c795730 --- /dev/null +++ b/animations/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/animations/macos/Runner.xcodeproj/project.pbxproj b/animations/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..581f568 --- /dev/null +++ b/animations/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,695 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* animations.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "animations.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* animations.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* animations.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.animations.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/animations.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/animations"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.animations.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/animations.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/animations"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.animations.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/animations.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/animations"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/animations/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/animations/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/animations/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/animations/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/animations/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..ca47b8b --- /dev/null +++ b/animations/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/animations/macos/Runner.xcworkspace/contents.xcworkspacedata b/animations/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/animations/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/animations/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/animations/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/animations/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/animations/macos/Runner/AppDelegate.swift b/animations/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..d53ef64 --- /dev/null +++ b/animations/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/animations/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/animations/macos/Runner/Base.lproj/MainMenu.xib b/animations/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/animations/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/animations/macos/Runner/Configs/AppInfo.xcconfig b/animations/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..20d004f --- /dev/null +++ b/animations/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = animations + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.animations + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright Β© 2023 dev.flutter. All rights reserved. diff --git a/animations/macos/Runner/Configs/Debug.xcconfig b/animations/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/animations/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/animations/macos/Runner/Configs/Release.xcconfig b/animations/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/animations/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/animations/macos/Runner/Configs/Warnings.xcconfig b/animations/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/animations/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/animations/macos/Runner/DebugProfile.entitlements b/animations/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/animations/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/animations/macos/Runner/Info.plist b/animations/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/animations/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/animations/macos/Runner/MainFlutterWindow.swift b/animations/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/animations/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/animations/macos/Runner/Release.entitlements b/animations/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/animations/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/animations/macos/RunnerTests/RunnerTests.swift b/animations/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..5418c9f --- /dev/null +++ b/animations/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import FlutterMacOS +import Cocoa +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/animations/pubspec.yaml b/animations/pubspec.yaml new file mode 100644 index 0000000..35403b5 --- /dev/null +++ b/animations/pubspec.yaml @@ -0,0 +1,29 @@ +name: animations +description: A new Flutter project. +version: 1.0.0+1 +publish_to: none +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + flutter: + sdk: flutter + flutter_animate: ^4.1.0 + go_router: ^16.0.0 + +dev_dependencies: + analysis_defaults: + path: ../analysis_defaults + flutter_test: + sdk: flutter + +flutter: + uses-material-design: true + assets: + - assets/ + fonts: + - family: SpecialElite + fonts: + - asset: fonts/SpecialElite-Regular.ttf diff --git a/animations/test/basics/animated_builder_test.dart b/animations/test/basics/animated_builder_test.dart new file mode 100644 index 0000000..e60f924 --- /dev/null +++ b/animations/test/basics/animated_builder_test.dart @@ -0,0 +1,63 @@ +// Copyright 2023 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. + +import 'package:animations/src/basics/basics.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +Widget createAnimatedBuilderDemoScreen() => + const MaterialApp(home: AnimatedBuilderDemo()); + +void main() { + group('AnimatedBuilder Tests', () { + testWidgets('AnimatedBuilder changes button color', (tester) async { + await tester.pumpWidget(createAnimatedBuilderDemoScreen()); + + // Get the initial color of the button. + ElevatedButton button = tester.widget(find.byType(ElevatedButton)); + WidgetStateProperty? initialColor = + button.style!.backgroundColor; + + // Tap the button. + await tester.tap(find.byType(ElevatedButton)); + await tester.pumpAndSettle(); + + // Get the updated color of the button. + button = tester.widget(find.byType(ElevatedButton)); + WidgetStateProperty? updatedColor = + button.style!.backgroundColor; + + // Check if the color has changed. + expect(initialColor, isNot(updatedColor)); + }); + + testWidgets('AnimatedBuilder animates button color', (tester) async { + await tester.pumpWidget(createAnimatedBuilderDemoScreen()); + + // Get the initial color of the button. + ElevatedButton button = tester.widget(find.byType(ElevatedButton)); + WidgetStateProperty? initialColor = + button.style!.backgroundColor; + + // Tap the button to trigger the animation but don't wait for it to finish. + await tester.tap(find.byType(ElevatedButton)); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 400)); + + // Check that the color has changed but not to the final color. + button = tester.widget(find.byType(ElevatedButton)); + WidgetStateProperty? changedColor = + button.style!.backgroundColor; + expect(initialColor, isNot(changedColor)); + + // Wait for the animation to finish. + await tester.pump(const Duration(milliseconds: 400)); + + // Check that the color has changed to the final color. + button = tester.widget(find.byType(ElevatedButton)); + WidgetStateProperty? finalColor = button.style!.backgroundColor; + expect(changedColor, isNot(finalColor)); + }); + }); +} diff --git a/animations/test/misc/animated_list_test.dart b/animations/test/misc/animated_list_test.dart new file mode 100644 index 0000000..bc03fca --- /dev/null +++ b/animations/test/misc/animated_list_test.dart @@ -0,0 +1,123 @@ +// Copyright 2020 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. + +import 'package:animations/src/misc/animated_list.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +Widget createAnimatedListDemoScreen() => + const MaterialApp(home: AnimatedListDemo()); + +void main() { + group('Animated List Tests', () { + testWidgets('Initial length of list', (tester) async { + await tester.pumpWidget(createAnimatedListDemoScreen()); + + // Get the initial length of list. + var initialLength = tester.widgetList(find.byType(ListTile)).length; + + // Initial length of list should be equal to 5. + expect(initialLength, equals(5)); + }); + + testWidgets('Length of list increases on Add Icon Button tap', ( + tester, + ) async { + await tester.pumpWidget(createAnimatedListDemoScreen()); + + // Get the initial length of list. + var initialLength = tester.widgetList(find.byType(ListTile)).length; + + // Tap on the Add Icon Button. + await tester.tap(find.byIcon(Icons.add)); + await tester.pumpAndSettle(); + + // Get the new length of list. + var newLength = tester.widgetList(find.byType(ListTile)).length; + + // New length should be greater than initial length by one. + expect(newLength, equals(initialLength + 1)); + }); + + testWidgets( + 'Length of list decreases on Delete Icon Button tap at middle index', + (tester) async { + await tester.pumpWidget(createAnimatedListDemoScreen()); + + // Get the initial length of list. + var initialLength = tester.widgetList(find.byType(ListTile)).length; + + // Tap on the Delete Icon Button at middle index. + await tester.tap(find.byIcon(Icons.delete).at(initialLength ~/ 2)); + await tester.pumpAndSettle(); + + // Get the new length of list. + var newLength = tester.widgetList(find.byType(ListTile)).length; + + // New length should be less than initial length by one. + expect(newLength, equals(initialLength - 1)); + }, + ); + + testWidgets( + 'Length of list decreases on Delete Icon Button tap at start index', + (tester) async { + await tester.pumpWidget(createAnimatedListDemoScreen()); + + // Get the initial length of list. + var initialLength = tester.widgetList(find.byType(ListTile)).length; + + // Tap on the Delete Icon Button at start index. + await tester.tap(find.byIcon(Icons.delete).at(0)); + await tester.pumpAndSettle(); + + // Get the new length of list. + var newLength = tester.widgetList(find.byType(ListTile)).length; + + // New length should be less than initial length by one. + expect(newLength, equals(initialLength - 1)); + }, + ); + + testWidgets( + 'Length of list decreases on Delete Icon Button tap at end index', + (tester) async { + await tester.pumpWidget(createAnimatedListDemoScreen()); + + // Get the initial length of list. + var initialLength = tester.widgetList(find.byType(ListTile)).length; + + // Tap on the Delete Icon Button at end index. + await tester.tap(find.byIcon(Icons.delete).at(initialLength - 1)); + await tester.pumpAndSettle(); + + // Get the new length of list. + var newLength = tester.widgetList(find.byType(ListTile)).length; + + // New Length should be less than initial length by one. + expect(newLength, equals(initialLength - 1)); + }, + ); + + testWidgets('All ListTiles deleted', (tester) async { + await tester.pumpWidget(createAnimatedListDemoScreen()); + + // Get the initial length of list. + var initialLength = tester.widgetList(find.byType(ListTile)).length; + + // Iterating over all the Delete Icon Buttons. + for (var i = 0; i < initialLength; i++) { + // Tap on the Delete Icon + await tester.tap(find.byIcon(Icons.delete).first); + await tester.pumpAndSettle(); + } + + // Get the final length of list. + var finalLength = tester.widgetList(find.byType(ListTile)).length; + + // New length should be zero. + expect(finalLength, equals(0)); + }); + }); +} diff --git a/animations/test/misc/animated_positioned_test.dart b/animations/test/misc/animated_positioned_test.dart new file mode 100644 index 0000000..69bbc30 --- /dev/null +++ b/animations/test/misc/animated_positioned_test.dart @@ -0,0 +1,32 @@ +// Copyright 2020 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. + +import 'package:animations/src/misc/animated_positioned.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +Widget createAnimatedPositionedDemoScreen() => + const MaterialApp(home: AnimatedPositionedDemo()); + +void main() { + group('AnimatedPositioned Tests', () { + testWidgets('Position of Button Changes on Tap', (tester) async { + await tester.pumpWidget(createAnimatedPositionedDemoScreen()); + + var button = find.byType(InkWell); + + // Get initial position of the widget. + var initialPosition = tester.getTopLeft(button); + expect(initialPosition, isNotNull); + + // Tap on the widget. + await tester.tap(button); + await tester.pumpAndSettle(); + + // The new position should not be equal to initial position. + var newPosition = tester.getTopLeft(button); + expect(newPosition, isNot(offsetMoreOrLessEquals(initialPosition))); + }); + }); +} diff --git a/animations/test/misc/card_swipe_test.dart b/animations/test/misc/card_swipe_test.dart new file mode 100644 index 0000000..0a75bb7 --- /dev/null +++ b/animations/test/misc/card_swipe_test.dart @@ -0,0 +1,71 @@ +// Copyright 2020 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. + +import 'package:animations/src/misc/card_swipe.dart'; +import 'package:flutter/material.dart' hide Card; +import 'package:flutter_test/flutter_test.dart'; + +Widget createCardSwipeScreen() => const MaterialApp(home: CardSwipeDemo()); + +void main() { + group('Card Swipe Tests', () { + testWidgets('One card swiped out', (tester) async { + await tester.pumpWidget(createCardSwipeScreen()); + + // Get the total number of cards available. + var totalCards = tester.widgetList(find.byType(Card)).length; + + // Ensure card is visible. + await tester.ensureVisible(find.byType(Card).last); + + // Swipe out one card. + await tester.drag(find.byType(Card).last, const Offset(100.0, 0.0)); + await tester.pumpAndSettle(); + + // Check if removed properly. + expect( + tester.widgetList(find.byType(Card)).length, + lessThan(totalCards), + ); + }); + + testWidgets('All cards swiped out', (tester) async { + await tester.pumpWidget(createCardSwipeScreen()); + + // Get the total number of cards availabe. + var totalCards = tester.widgetList(find.byType(Card)).length; + + // Swipe out all cards. + for (var i = 0; i < totalCards; i++) { + // Swipe out one by one. + await tester.drag( + find.byType(Card).last, + const Offset(100.0, 0.0), + ); + await tester.pumpAndSettle(); + } + + // Check if any card is remaining. + expect(find.byType(Card), findsNothing); + }); + + testWidgets('Stack refilled with cards', (tester) async { + await tester.pumpWidget(createCardSwipeScreen()); + + // Get the total number of cards availabe. + var totalCards = tester.widgetList(find.byType(Card)).length; + + // Swipe out one card. + await tester.drag(find.byType(Card).last, const Offset(100.0, 0.0)); + await tester.pumpAndSettle(); + + // Tap the Refill button. + await tester.tap(find.byType(ElevatedButton)); + await tester.pumpAndSettle(); + + // Check if the entire stack is refilled. + expect(find.byType(Card), findsNWidgets(totalCards)); + }); + }); +} diff --git a/animations/test/misc/carousel_test.dart b/animations/test/misc/carousel_test.dart new file mode 100644 index 0000000..2a0ea44 --- /dev/null +++ b/animations/test/misc/carousel_test.dart @@ -0,0 +1,33 @@ +// Copyright 2020 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. + +import 'package:animations/src/misc/carousel.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +Widget createCarouselDemoScreen() => MaterialApp(home: CarouselDemo()); + +void main() { + group('CarouselDemo tests', () { + testWidgets('Swipe left moves carousel', (tester) async { + await tester.pumpWidget(createCarouselDemoScreen()); + + // Get the images available on the screen during initial state. + var imageList = tester.widgetList(find.byType(Image)).toList(); + expect(imageList.length, 2); + + // Swipe the Carousel. + await tester.fling( + find.byType(CarouselDemo), + const Offset(-400, 0), + 800, + ); + await tester.pumpAndSettle(); + + // Get the images available on the screen after swipe. + imageList = tester.widgetList(find.byType(Image)).toList(); + expect(imageList.length, 3); + }); + }); +} diff --git a/animations/test/misc/expand_card_test.dart b/animations/test/misc/expand_card_test.dart new file mode 100644 index 0000000..b955d27 --- /dev/null +++ b/animations/test/misc/expand_card_test.dart @@ -0,0 +1,47 @@ +// Copyright 2020 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. + +import 'package:animations/src/misc/expand_card.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +Widget createExpandCardScreen() => const MaterialApp(home: ExpandCardDemo()); + +void main() { + group('ExpandCard Tests', () { + testWidgets('ExpandCard changes size on tap', (tester) async { + await tester.pumpWidget(createExpandCardScreen()); + + // Get the initial size of ExpandCard. + var initialSize = tester.getSize(find.byType(ExpandCard)); + + // Tap on the ExpandCard. + await tester.tap(find.byType(ExpandCard)); + await tester.pumpAndSettle(); + + // The size of ExpandCard must change once tapped. + // The initialSize should be less than current ExpandCard size. + expect( + initialSize, + lessThan(tester.getSize(find.byType(ExpandCard))), + ); + }); + + testWidgets('ExpandCard changes image on tap', (tester) async { + await tester.pumpWidget(createExpandCardScreen()); + + var initialImage = tester.widget(find.byType(Image).last); + + // Tap on ExpandCard. + await tester.tap(find.byType(ExpandCard)); + await tester.pumpAndSettle(); + + // Once tapped, the image should change. + expect( + initialImage, + isNot(equals(tester.widget(find.byType(Image).last))), + ); + }); + }); +} diff --git a/animations/test/misc/focus_image_test.dart b/animations/test/misc/focus_image_test.dart new file mode 100644 index 0000000..7b3f8e0 --- /dev/null +++ b/animations/test/misc/focus_image_test.dart @@ -0,0 +1,53 @@ +// Copyright 2021 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. + +import 'package:animations/src/misc/focus_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +Widget createFocusImageScreen() => const MaterialApp(home: FocusImageDemo()); + +void main() { + group('FocusImage Tests', () { + testWidgets('Size of ink well changes on tap', (tester) async { + await tester.pumpWidget(createFocusImageScreen()); + + // Get the initial inkwell. + final initialInkwell = tester.widget(find.byType(InkWell).at(0)); + + // Get the size of initial inkwell. + var initialSize = tester.getSize(find.byWidget(initialInkwell)); + + // Tap on the ink well at index 0. + await tester.tap(find.byType(InkWell).at(0)); + await tester.pumpAndSettle(); + + // Get the final inkwell. + final finalInkwell = tester.widget(find.byType(InkWell).at(0)); + + // Get the size of final inkwell. + var finalSize = tester.getSize(find.byWidget(finalInkwell)); + + // Final size should be greater than initial size. + expect(finalSize, greaterThan(initialSize)); + }); + + testWidgets('Final inkwell on tap goes back to the grid', ( + tester, + ) async { + await tester.pumpWidget(createFocusImageScreen()); + + // Tap on the ink well at index 0. + await tester.tap(find.byType(InkWell).at(0)); + await tester.pumpAndSettle(); + + // Tap on the final inkwell. + await tester.tap(find.byType(InkWell)); + await tester.pumpAndSettle(); + + // The grid view should appear again. + expect(find.byType(Grid), findsOneWidget); + }); + }); +} diff --git a/animations/test/misc/hero_animation_test.dart b/animations/test/misc/hero_animation_test.dart new file mode 100644 index 0000000..48ca60a --- /dev/null +++ b/animations/test/misc/hero_animation_test.dart @@ -0,0 +1,86 @@ +// Copyright 2020 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. + +import 'package:animations/src/misc/hero_animation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +Widget createHeroAnimationDemoScreen() => + const MaterialApp(home: HeroAnimationDemo()); + +void main() { + group('Hero Animation Tests', () { + testWidgets('Size of Container changes on Tap', (tester) async { + await tester.pumpWidget(createHeroAnimationDemoScreen()); + + // Get the initial Container. + var initialContainer = tester.firstWidget(find.byType(Container)); + + // Get the size of initial Container. + var initialSize = tester.getSize(find.byWidget(initialContainer)); + + // Tap on the Gesture Detector. + await tester.tap(find.byType(GestureDetector)); + await tester.pumpAndSettle(); + + // Get the final Container. + var finalContainer = tester.firstWidget(find.byType(Container)); + + // Get the size of final Container. + var finalSize = tester.getSize(find.byWidget(finalContainer)); + + // initialSize should be less than finalSize. + expect(initialSize, lessThan(finalSize)); + }); + + testWidgets('Color of Container changes on Tap', (tester) async { + await tester.pumpWidget(createHeroAnimationDemoScreen()); + + // Get the initial Container. + final initialContainer = + tester.firstWidget(find.byType(Container)) as Container; + + // Initial color should be Color.grey[300]. + expect( + (initialContainer.decoration as BoxDecoration).color, + Colors.grey[300], + ); + + // Tap on the GestureDetector. + await tester.tap(find.byType(GestureDetector)); + await tester.pumpAndSettle(); + + // Get the final Container. + final finalContainer = + tester.firstWidget(find.byType(Container)) as Container; + + // Final color should not be same as initial color. + expect( + (finalContainer.decoration as BoxDecoration).color, + isNot( + equals((initialContainer.decoration as BoxDecoration).color), + ), + ); + }); + + testWidgets('Screen Changes on Tap', (tester) async { + await tester.pumpWidget(createHeroAnimationDemoScreen()); + + // Get the initial Screen. + final initialScreen = tester.firstWidget( + find.byType(HeroAnimationDemo), + ); + + // Tap on the GestureDetector. + await tester.tap(find.byType(GestureDetector)); + await tester.pumpAndSettle(); + + // Get the screen after navigation. + final finalScreen = tester.firstWidget(find.byType(HeroPage)); + + // initialScreen should not be same as finalScreen. + expect(initialScreen, isNot(equals(finalScreen))); + }); + }); +} diff --git a/animations/test/widget_test.dart b/animations/test/widget_test.dart new file mode 100644 index 0000000..be5639d --- /dev/null +++ b/animations/test/widget_test.dart @@ -0,0 +1,19 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:animations/main.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('smoke test', (tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const AnimationSamples()); + + // Verify that a least one of our demos is showing up in the list + expect(find.text('AnimatedContainer'), findsOneWidget); + }); +} diff --git a/animations/web/favicon.png b/animations/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/animations/web/favicon.png differ diff --git a/animations/web/icons/Icon-192.png b/animations/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/animations/web/icons/Icon-192.png differ diff --git a/animations/web/icons/Icon-512.png b/animations/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/animations/web/icons/Icon-512.png differ diff --git a/animations/web/icons/Icon-maskable-192.png b/animations/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/animations/web/icons/Icon-maskable-192.png differ diff --git a/animations/web/icons/Icon-maskable-512.png b/animations/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/animations/web/icons/Icon-maskable-512.png differ diff --git a/animations/web/index.html b/animations/web/index.html new file mode 100644 index 0000000..f3b38c9 --- /dev/null +++ b/animations/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + animations + + + + + + diff --git a/animations/web/manifest.json b/animations/web/manifest.json new file mode 100644 index 0000000..00bcc86 --- /dev/null +++ b/animations/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "animations", + "short_name": "animations", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/animations/windows/.gitignore b/animations/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/animations/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/animations/windows/CMakeLists.txt b/animations/windows/CMakeLists.txt new file mode 100644 index 0000000..457f200 --- /dev/null +++ b/animations/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(animations LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "animations") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/animations/windows/flutter/CMakeLists.txt b/animations/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/animations/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/animations/windows/flutter/generated_plugin_registrant.cc b/animations/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..8b6d468 --- /dev/null +++ b/animations/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/animations/windows/flutter/generated_plugin_registrant.h b/animations/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/animations/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/animations/windows/flutter/generated_plugins.cmake b/animations/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..b93c4c3 --- /dev/null +++ b/animations/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/animations/windows/runner/CMakeLists.txt b/animations/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/animations/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/animations/windows/runner/Runner.rc b/animations/windows/runner/Runner.rc new file mode 100644 index 0000000..31697ad --- /dev/null +++ b/animations/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "dev.flutter" "\0" + VALUE "FileDescription", "animations" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "animations" "\0" + VALUE "LegalCopyright", "Copyright (C) 2023 dev.flutter. All rights reserved." "\0" + VALUE "OriginalFilename", "animations.exe" "\0" + VALUE "ProductName", "animations" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/animations/windows/runner/flutter_window.cpp b/animations/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/animations/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/animations/windows/runner/flutter_window.h b/animations/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/animations/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/animations/windows/runner/main.cpp b/animations/windows/runner/main.cpp new file mode 100644 index 0000000..ea6c4c2 --- /dev/null +++ b/animations/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"animations", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/animations/windows/runner/resource.h b/animations/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/animations/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/animations/windows/runner/resources/app_icon.ico b/animations/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/animations/windows/runner/resources/app_icon.ico differ diff --git a/animations/windows/runner/runner.exe.manifest b/animations/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..a42ea76 --- /dev/null +++ b/animations/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/animations/windows/runner/utils.cpp b/animations/windows/runner/utils.cpp new file mode 100644 index 0000000..b2b0873 --- /dev/null +++ b/animations/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/animations/windows/runner/utils.h b/animations/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/animations/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/animations/windows/runner/win32_window.cpp b/animations/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/animations/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/animations/windows/runner/win32_window.h b/animations/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/animations/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/asset_transformation/.gitignore b/asset_transformation/.gitignore new file mode 100644 index 0000000..29a3a50 --- /dev/null +++ b/asset_transformation/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/asset_transformation/.metadata b/asset_transformation/.metadata new file mode 100644 index 0000000..03cd4e2 --- /dev/null +++ b/asset_transformation/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "db8c475cac117074a38ad50da810214cd7af0d46" + channel: "master" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: db8c475cac117074a38ad50da810214cd7af0d46 + base_revision: db8c475cac117074a38ad50da810214cd7af0d46 + - platform: android + create_revision: db8c475cac117074a38ad50da810214cd7af0d46 + base_revision: db8c475cac117074a38ad50da810214cd7af0d46 + - platform: ios + create_revision: db8c475cac117074a38ad50da810214cd7af0d46 + base_revision: db8c475cac117074a38ad50da810214cd7af0d46 + - platform: linux + create_revision: db8c475cac117074a38ad50da810214cd7af0d46 + base_revision: db8c475cac117074a38ad50da810214cd7af0d46 + - platform: macos + create_revision: db8c475cac117074a38ad50da810214cd7af0d46 + base_revision: db8c475cac117074a38ad50da810214cd7af0d46 + - platform: web + create_revision: db8c475cac117074a38ad50da810214cd7af0d46 + base_revision: db8c475cac117074a38ad50da810214cd7af0d46 + - platform: windows + create_revision: db8c475cac117074a38ad50da810214cd7af0d46 + base_revision: db8c475cac117074a38ad50da810214cd7af0d46 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/asset_transformation/README.md b/asset_transformation/README.md new file mode 100644 index 0000000..20aec29 --- /dev/null +++ b/asset_transformation/README.md @@ -0,0 +1,8 @@ +# asset_transformation + +This project demonstrates the asset transformation feature. It includes an example +of a custom Dart package that can be used as an asset transformer. It also shows +how to transform an asset using a pub package that is compatible with the asset +transformation feature. + +See the `assets` section in pubspec.yaml to get started. diff --git a/asset_transformation/android/.gitignore b/asset_transformation/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/asset_transformation/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/asset_transformation/android/app/build.gradle b/asset_transformation/android/app/build.gradle new file mode 100644 index 0000000..97b64b4 --- /dev/null +++ b/asset_transformation/android/app/build.gradle @@ -0,0 +1,40 @@ +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + +android { + namespace = "com.example.asset_transformation" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.asset_transformation" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.debug + } + } +} + +flutter { + source = "../.." +} diff --git a/asset_transformation/android/app/src/debug/AndroidManifest.xml b/asset_transformation/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/asset_transformation/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/asset_transformation/android/app/src/main/AndroidManifest.xml b/asset_transformation/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..360110a --- /dev/null +++ b/asset_transformation/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/asset_transformation/android/app/src/main/kotlin/com/example/asset_transformation/MainActivity.kt b/asset_transformation/android/app/src/main/kotlin/com/example/asset_transformation/MainActivity.kt new file mode 100644 index 0000000..64371b0 --- /dev/null +++ b/asset_transformation/android/app/src/main/kotlin/com/example/asset_transformation/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.asset_transformation + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/asset_transformation/android/app/src/main/res/drawable-v21/launch_background.xml b/asset_transformation/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/asset_transformation/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/asset_transformation/android/app/src/main/res/drawable/launch_background.xml b/asset_transformation/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/asset_transformation/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/asset_transformation/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/asset_transformation/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/asset_transformation/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/asset_transformation/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/asset_transformation/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/asset_transformation/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/asset_transformation/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/asset_transformation/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/asset_transformation/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/asset_transformation/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/asset_transformation/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/asset_transformation/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/asset_transformation/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/asset_transformation/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/asset_transformation/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/asset_transformation/android/app/src/main/res/values-night/styles.xml b/asset_transformation/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/asset_transformation/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/asset_transformation/android/app/src/main/res/values/styles.xml b/asset_transformation/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/asset_transformation/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/asset_transformation/android/app/src/profile/AndroidManifest.xml b/asset_transformation/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/asset_transformation/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/asset_transformation/android/build.gradle b/asset_transformation/android/build.gradle new file mode 100644 index 0000000..d2ffbff --- /dev/null +++ b/asset_transformation/android/build.gradle @@ -0,0 +1,18 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = "../build" +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/asset_transformation/android/gradle.properties b/asset_transformation/android/gradle.properties new file mode 100644 index 0000000..2597170 --- /dev/null +++ b/asset_transformation/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/asset_transformation/android/gradle/wrapper/gradle-wrapper.properties b/asset_transformation/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e1ca574 --- /dev/null +++ b/asset_transformation/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip diff --git a/asset_transformation/android/settings.gradle b/asset_transformation/android/settings.gradle new file mode 100644 index 0000000..536165d --- /dev/null +++ b/asset_transformation/android/settings.gradle @@ -0,0 +1,25 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false + id "org.jetbrains.kotlin.android" version "1.7.10" apply false +} + +include ":app" diff --git a/asset_transformation/assets/colorful.jpg b/asset_transformation/assets/colorful.jpg new file mode 100644 index 0000000..3e58357 Binary files /dev/null and b/asset_transformation/assets/colorful.jpg differ diff --git a/asset_transformation/assets/svg.svg b/asset_transformation/assets/svg.svg new file mode 100644 index 0000000..207f139 --- /dev/null +++ b/asset_transformation/assets/svg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/asset_transformation/grayscale_transformer/.gitignore b/asset_transformation/grayscale_transformer/.gitignore new file mode 100644 index 0000000..3a85790 --- /dev/null +++ b/asset_transformation/grayscale_transformer/.gitignore @@ -0,0 +1,3 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ diff --git a/asset_transformation/grayscale_transformer/CHANGELOG.md b/asset_transformation/grayscale_transformer/CHANGELOG.md new file mode 100644 index 0000000..effe43c --- /dev/null +++ b/asset_transformation/grayscale_transformer/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/asset_transformation/grayscale_transformer/analysis_options.yaml b/asset_transformation/grayscale_transformer/analysis_options.yaml new file mode 100644 index 0000000..a960295 --- /dev/null +++ b/asset_transformation/grayscale_transformer/analysis_options.yaml @@ -0,0 +1,10 @@ +analyzer: + exclude: + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** +include: ../../analysis_options.yaml diff --git a/asset_transformation/grayscale_transformer/bin/grayscale_transformer.dart b/asset_transformation/grayscale_transformer/bin/grayscale_transformer.dart new file mode 100644 index 0000000..b80fe71 --- /dev/null +++ b/asset_transformation/grayscale_transformer/bin/grayscale_transformer.dart @@ -0,0 +1,38 @@ +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:image/image.dart'; + +const inputOptionName = 'input'; +const outputOptionName = 'output'; + +int main(List arguments) { + // The flutter tool will invoke this program with two arguments, one for + // the `--input` option and one for the `--output` option. + // `--input` is the original asset file that this program should transform. + // `--output` is where flutter expects the transformation output to be written to. + final parser = ArgParser() + ..addOption(inputOptionName, mandatory: true, abbr: 'i') + ..addOption(outputOptionName, mandatory: true, abbr: 'o'); + + ArgResults argResults = parser.parse(arguments); + final String inputFilePath = argResults[inputOptionName] as String; + final String outputFilePath = argResults[outputOptionName] as String; + + try { + final Image image = decodeImage(File(inputFilePath).readAsBytesSync())!; + final Image imageInGrayscale = grayscale(image); + File(outputFilePath).writeAsBytesSync(encodeJpg(imageInGrayscale)); + + return 0; + } catch (e) { + // The flutter command line tool will see a non-zero exit code (1 in this case) + // and fail the build. Anything written to stderr by the asset transformer + // will be surfaced by flutter. + stderr.writeln( + 'Unexpected exception when producing grayscale image.\n' + 'Details: $e', + ); + return 1; + } +} diff --git a/asset_transformation/grayscale_transformer/pubspec.yaml b/asset_transformation/grayscale_transformer/pubspec.yaml new file mode 100644 index 0000000..ed59b38 --- /dev/null +++ b/asset_transformation/grayscale_transformer/pubspec.yaml @@ -0,0 +1,15 @@ +name: grayscale_transformer +description: A sample command-line application. +version: 1.0.0 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + args: ^2.4.2 + image: ^4.1.7 + +dev_dependencies: + lints: ^6.0.0 + test: ^1.24.0 diff --git a/asset_transformation/ios/.gitignore b/asset_transformation/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/asset_transformation/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/asset_transformation/ios/Flutter/AppFrameworkInfo.plist b/asset_transformation/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/asset_transformation/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/asset_transformation/ios/Flutter/Debug.xcconfig b/asset_transformation/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/asset_transformation/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/asset_transformation/ios/Flutter/Release.xcconfig b/asset_transformation/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/asset_transformation/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/asset_transformation/ios/Runner.xcodeproj/project.pbxproj b/asset_transformation/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..348ddce --- /dev/null +++ b/asset_transformation/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,619 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = KW457W2YCN; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.assetTransformation; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.assetTransformation.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.assetTransformation.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.assetTransformation.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = KW457W2YCN; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.assetTransformation; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = KW457W2YCN; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.assetTransformation; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/asset_transformation/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/asset_transformation/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/asset_transformation/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/asset_transformation/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/asset_transformation/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/asset_transformation/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/asset_transformation/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/asset_transformation/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/asset_transformation/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/asset_transformation/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/asset_transformation/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..8e3ca5d --- /dev/null +++ b/asset_transformation/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/asset_transformation/ios/Runner.xcworkspace/contents.xcworkspacedata b/asset_transformation/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/asset_transformation/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/asset_transformation/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/asset_transformation/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/asset_transformation/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/asset_transformation/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/asset_transformation/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/asset_transformation/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/asset_transformation/ios/Runner/AppDelegate.swift b/asset_transformation/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..6266644 --- /dev/null +++ b/asset_transformation/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/asset_transformation/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/asset_transformation/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/asset_transformation/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/asset_transformation/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/asset_transformation/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/asset_transformation/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/asset_transformation/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/asset_transformation/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/asset_transformation/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/asset_transformation/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/asset_transformation/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/asset_transformation/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/asset_transformation/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/asset_transformation/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/asset_transformation/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/asset_transformation/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/asset_transformation/ios/Runner/Base.lproj/LaunchScreen.storyboard b/asset_transformation/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/asset_transformation/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/asset_transformation/ios/Runner/Base.lproj/Main.storyboard b/asset_transformation/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/asset_transformation/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/asset_transformation/ios/Runner/Info.plist b/asset_transformation/ios/Runner/Info.plist new file mode 100644 index 0000000..10bfdec --- /dev/null +++ b/asset_transformation/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Asset Transformation + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + asset_transformation + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/asset_transformation/ios/Runner/Runner-Bridging-Header.h b/asset_transformation/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/asset_transformation/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/asset_transformation/ios/RunnerTests/RunnerTests.swift b/asset_transformation/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/asset_transformation/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/asset_transformation/lib/main.dart b/asset_transformation/lib/main.dart new file mode 100644 index 0000000..7d48caa --- /dev/null +++ b/asset_transformation/lib/main.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:vector_graphics/vector_graphics.dart'; + +void main() { + runApp(const MainApp()); +} + +class MainApp extends StatelessWidget { + const MainApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Container( + padding: const EdgeInsets.all(50), + child: Column( + children: [ + const Text( + 'This is an image asset that was declared in pubspec.yaml. ' + 'If you open the file, you will notice that the image is ' + 'colorful, but in the app it appears in grayscale. This is ' + 'because the transformer it was declared with produced the ' + 'image in grayscale. Explore the grayscale_transformer directory ' + 'to see the source and learn how to write your own Dart package ' + 'that can be used as an asset transformer in Flutter.', + ), + Image.asset('assets/colorful.jpg'), + const Divider(height: 75), + const Text( + "This is an SVG image rendered using the vector_graphics " + "package. Normally, the package can only render images produced " + "by the vector_graphics_compiler package. However, since we " + "configured the SVG asset with vector_graphics_compiler as a " + "transformer, we can render it directly using the " + "vector_graphics API.", + ), + const VectorGraphic(loader: AssetBytesLoader('assets/svg.svg')), + ], + ), + ), + ), + ); + } +} diff --git a/asset_transformation/linux/.gitignore b/asset_transformation/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/asset_transformation/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/asset_transformation/linux/CMakeLists.txt b/asset_transformation/linux/CMakeLists.txt new file mode 100644 index 0000000..915d7ce --- /dev/null +++ b/asset_transformation/linux/CMakeLists.txt @@ -0,0 +1,145 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "asset_transformation") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.asset_transformation") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/asset_transformation/linux/flutter/CMakeLists.txt b/asset_transformation/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/asset_transformation/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/asset_transformation/linux/flutter/generated_plugin_registrant.cc b/asset_transformation/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..e71a16d --- /dev/null +++ b/asset_transformation/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/asset_transformation/linux/flutter/generated_plugin_registrant.h b/asset_transformation/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/asset_transformation/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/asset_transformation/linux/flutter/generated_plugins.cmake b/asset_transformation/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..2e1de87 --- /dev/null +++ b/asset_transformation/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/asset_transformation/linux/main.cc b/asset_transformation/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/asset_transformation/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/asset_transformation/linux/my_application.cc b/asset_transformation/linux/my_application.cc new file mode 100644 index 0000000..5496ebc --- /dev/null +++ b/asset_transformation/linux/my_application.cc @@ -0,0 +1,124 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "asset_transformation"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "asset_transformation"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/asset_transformation/linux/my_application.h b/asset_transformation/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/asset_transformation/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/asset_transformation/macos/.gitignore b/asset_transformation/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/asset_transformation/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/asset_transformation/macos/Flutter/Flutter-Debug.xcconfig b/asset_transformation/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/asset_transformation/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/asset_transformation/macos/Flutter/Flutter-Release.xcconfig b/asset_transformation/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/asset_transformation/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/asset_transformation/macos/Flutter/GeneratedPluginRegistrant.swift b/asset_transformation/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..cccf817 --- /dev/null +++ b/asset_transformation/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,10 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { +} diff --git a/asset_transformation/macos/Runner.xcodeproj/project.pbxproj b/asset_transformation/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..b4ab2d1 --- /dev/null +++ b/asset_transformation/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,705 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* asset_transformation.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "asset_transformation.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* asset_transformation.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* asset_transformation.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.assetTransformation.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/asset_transformation.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/asset_transformation"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.assetTransformation.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/asset_transformation.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/asset_transformation"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.assetTransformation.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/asset_transformation.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/asset_transformation"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/asset_transformation/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/asset_transformation/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/asset_transformation/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/asset_transformation/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/asset_transformation/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..497fa68 --- /dev/null +++ b/asset_transformation/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/asset_transformation/macos/Runner.xcworkspace/contents.xcworkspacedata b/asset_transformation/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/asset_transformation/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/asset_transformation/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/asset_transformation/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/asset_transformation/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/asset_transformation/macos/Runner/AppDelegate.swift b/asset_transformation/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..8e02df2 --- /dev/null +++ b/asset_transformation/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/asset_transformation/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/asset_transformation/macos/Runner/Base.lproj/MainMenu.xib b/asset_transformation/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/asset_transformation/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/asset_transformation/macos/Runner/Configs/AppInfo.xcconfig b/asset_transformation/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..a1aa0cd --- /dev/null +++ b/asset_transformation/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = asset_transformation + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.assetTransformation + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright Β© 2024 com.example. All rights reserved. diff --git a/asset_transformation/macos/Runner/Configs/Debug.xcconfig b/asset_transformation/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/asset_transformation/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/asset_transformation/macos/Runner/Configs/Release.xcconfig b/asset_transformation/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/asset_transformation/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/asset_transformation/macos/Runner/Configs/Warnings.xcconfig b/asset_transformation/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/asset_transformation/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/asset_transformation/macos/Runner/DebugProfile.entitlements b/asset_transformation/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/asset_transformation/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/asset_transformation/macos/Runner/Info.plist b/asset_transformation/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/asset_transformation/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/asset_transformation/macos/Runner/MainFlutterWindow.swift b/asset_transformation/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/asset_transformation/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/asset_transformation/macos/Runner/Release.entitlements b/asset_transformation/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/asset_transformation/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/asset_transformation/macos/RunnerTests/RunnerTests.swift b/asset_transformation/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/asset_transformation/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/asset_transformation/pubspec.yaml b/asset_transformation/pubspec.yaml new file mode 100644 index 0000000..7b1a23e --- /dev/null +++ b/asset_transformation/pubspec.yaml @@ -0,0 +1,44 @@ +name: asset_transformation +description: "A new Flutter project." +publish_to: 'none' +version: 0.1.0 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + flutter: + sdk: flutter + vector_graphics: ^1.1.11+1 + +dev_dependencies: + analysis_defaults: + path: ../analysis_defaults + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 + vector_graphics_compiler: ^1.1.11+1 + grayscale_transformer: + path: ./grayscale_transformer + +flutter: + uses-material-design: true + assets: + # Here, we specify that assets/colorful.jpg should be transformed using + # the grayscale_transformer package. If you look at the dev_dependencies + # section, you'll notice that grayscale_transformer is a local path dependency + # and thus the full code of the package can be found right here in this project. + - path: assets/colorful.jpg + transformers: + - package: grayscale_transformer + # Here, we have an SVG image that we want to display in our app. Flutter + # cannot render SVG images itself, but there are packages that can. + # The vector_graphics package is one such app that can quickly render SVGs, + # but only after they have been run through the vector_graphics_compiler + # package first. Instead of having to do this manually ourselves, we + # can simply specify vector_graphics_compiler as a transformer on this asset. + - path: assets/svg.svg + transformers: + - package: vector_graphics_compiler + # Run the application using `flutter run` to see how these assets get rendered. diff --git a/asset_transformation/test/main_test.dart b/asset_transformation/test/main_test.dart new file mode 100644 index 0000000..2c84aa5 --- /dev/null +++ b/asset_transformation/test/main_test.dart @@ -0,0 +1,8 @@ +import 'package:asset_transformation/main.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('app can render without exceptions', (tester) async { + await tester.pumpWidget(const MainApp()); + }); +} diff --git a/asset_transformation/web/favicon.png b/asset_transformation/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/asset_transformation/web/favicon.png differ diff --git a/asset_transformation/web/icons/Icon-192.png b/asset_transformation/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/asset_transformation/web/icons/Icon-192.png differ diff --git a/asset_transformation/web/icons/Icon-512.png b/asset_transformation/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/asset_transformation/web/icons/Icon-512.png differ diff --git a/asset_transformation/web/icons/Icon-maskable-192.png b/asset_transformation/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/asset_transformation/web/icons/Icon-maskable-192.png differ diff --git a/asset_transformation/web/icons/Icon-maskable-512.png b/asset_transformation/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/asset_transformation/web/icons/Icon-maskable-512.png differ diff --git a/asset_transformation/web/index.html b/asset_transformation/web/index.html new file mode 100644 index 0000000..b19587c --- /dev/null +++ b/asset_transformation/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + asset_transformation + + + + + + diff --git a/asset_transformation/web/manifest.json b/asset_transformation/web/manifest.json new file mode 100644 index 0000000..d421627 --- /dev/null +++ b/asset_transformation/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "asset_transformation", + "short_name": "asset_transformation", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/asset_transformation/windows/.gitignore b/asset_transformation/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/asset_transformation/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/asset_transformation/windows/CMakeLists.txt b/asset_transformation/windows/CMakeLists.txt new file mode 100644 index 0000000..6a6b39b --- /dev/null +++ b/asset_transformation/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(asset_transformation LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "asset_transformation") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/asset_transformation/windows/flutter/CMakeLists.txt b/asset_transformation/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/asset_transformation/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/asset_transformation/windows/flutter/generated_plugin_registrant.cc b/asset_transformation/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..8b6d468 --- /dev/null +++ b/asset_transformation/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/asset_transformation/windows/flutter/generated_plugin_registrant.h b/asset_transformation/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/asset_transformation/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/asset_transformation/windows/flutter/generated_plugins.cmake b/asset_transformation/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..b93c4c3 --- /dev/null +++ b/asset_transformation/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/asset_transformation/windows/runner/CMakeLists.txt b/asset_transformation/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/asset_transformation/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/asset_transformation/windows/runner/Runner.rc b/asset_transformation/windows/runner/Runner.rc new file mode 100644 index 0000000..807913b --- /dev/null +++ b/asset_transformation/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "asset_transformation" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "asset_transformation" "\0" + VALUE "LegalCopyright", "Copyright (C) 2024 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "asset_transformation.exe" "\0" + VALUE "ProductName", "asset_transformation" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/asset_transformation/windows/runner/flutter_window.cpp b/asset_transformation/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/asset_transformation/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/asset_transformation/windows/runner/flutter_window.h b/asset_transformation/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/asset_transformation/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/asset_transformation/windows/runner/main.cpp b/asset_transformation/windows/runner/main.cpp new file mode 100644 index 0000000..5bc210b --- /dev/null +++ b/asset_transformation/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"asset_transformation", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/asset_transformation/windows/runner/resource.h b/asset_transformation/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/asset_transformation/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/asset_transformation/windows/runner/resources/app_icon.ico b/asset_transformation/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/asset_transformation/windows/runner/resources/app_icon.ico differ diff --git a/asset_transformation/windows/runner/runner.exe.manifest b/asset_transformation/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/asset_transformation/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/asset_transformation/windows/runner/utils.cpp b/asset_transformation/windows/runner/utils.cpp new file mode 100644 index 0000000..3a0b465 --- /dev/null +++ b/asset_transformation/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/asset_transformation/windows/runner/utils.h b/asset_transformation/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/asset_transformation/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/asset_transformation/windows/runner/win32_window.cpp b/asset_transformation/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/asset_transformation/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/asset_transformation/windows/runner/win32_window.h b/asset_transformation/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/asset_transformation/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/background_isolate_channels/analysis_options.yaml b/background_isolate_channels/analysis_options.yaml new file mode 100644 index 0000000..5e2133e --- /dev/null +++ b/background_isolate_channels/analysis_options.yaml @@ -0,0 +1 @@ +include: ../analysis_options.yaml diff --git a/background_isolate_channels/lib/main.dart b/background_isolate_channels/lib/main.dart new file mode 100644 index 0000000..c929ee0 --- /dev/null +++ b/background_isolate_channels/lib/main.dart @@ -0,0 +1,156 @@ +// Copyright 2022 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. + +import 'dart:io' show Directory; + +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:path/path.dart' as path; +import 'package:path_provider/path_provider.dart' as path_provider; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:uuid/uuid.dart' as uuid; + +import 'simple_database.dart'; + +/////////////////////////////////////////////////////////////////////////////// +// This is the UI which will present the contents of the [SimpleDatabase]. To +// see where Background Isolate Channels are used see simple_database.dart. +/////////////////////////////////////////////////////////////////////////////// + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Background Isolate Channels', + theme: ThemeData(primarySwatch: Colors.blue), + home: const MyHomePage(title: 'Background Isolate Channels'), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + + final String title; + + @override + State createState() { + return _MyHomePageState(); + } +} + +class _MyHomePageState extends State { + /// The database that is running on a background [Isolate]. This is nullable + /// because acquiring a [SimpleDatabase] is an asynchronous operation. This + /// value is `null` until the database is initialized. + SimpleDatabase? _database; + + /// Local cache of the query results returned by the [SimpleDatabase] for the + /// UI to render from. It is nullable since querying the results is + /// asynchronous. The value is `null` before any result has been received. + List? _entries; + + /// What is searched for in the [SimpleDatabase]. + String _query = ''; + + @override + void initState() { + // Write the value to [SharedPreferences] which will get read on the + // [SimpleDatabase]'s isolate. For this example the value is always true + // just for demonstration purposes. + final SharedPreferencesAsync sharedPreferences = SharedPreferencesAsync(); + + final Future sharedPreferencesSet = sharedPreferences.setBool( + 'isDebug', + true, + ); + final Future tempDirFuture = path_provider + .getTemporaryDirectory(); + + // Wait until the [SharedPreferences] value is set and the temporary + // directory is received before opening the database. If + // [sharedPreferencesSet] does not happen before opening the + // [SimpleDatabase] there has to be a way to refresh + // [_SimpleDatabaseServer]'s [SharedPreferences] cached values. + Future.wait([sharedPreferencesSet, tempDirFuture]).then((values) { + final Directory? tempDir = values[1] as Directory?; + final String dbPath = path.join(tempDir!.path, 'database.db'); + SimpleDatabase.open(dbPath).then((database) { + setState(() { + _database = database; + }); + _refresh(); + }); + }); + super.initState(); + } + + @override + void dispose() { + _database?.stop(); + super.dispose(); + } + + /// Performs a find on [SimpleDatabase] with [query] and updates the listed + /// contents. + void _refresh({String? query}) { + if (query != null) { + _query = query; + } + _database!.find(_query).toList().then((entries) { + setState(() { + _entries = entries; + }); + }); + } + + /// Adds a UUID and a timestamp to the [SimpleDatabase]. + void _addDate() { + final DateTime now = DateTime.now(); + final DateFormat formatter = DateFormat( + 'EEEE MMMM d, HH:mm:ss\n${const uuid.Uuid().v4()}', + ); + final String formatted = formatter.format(now); + _database!.addEntry(formatted).then((_) => _refresh()); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.title), + bottom: PreferredSize( + preferredSize: const Size.fromHeight(kToolbarHeight), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: SearchBar( + hintText: 'Search', + onChanged: _database == null + ? null + : (query) => _refresh(query: query), + trailing: const [Icon(Icons.search), SizedBox(width: 8)], + ), + ), + ), + ), + body: ListView.builder( + itemBuilder: (context, index) { + return ListTile(title: Text(_entries![index])); + }, + itemCount: _entries?.length ?? 0, + ), + floatingActionButton: FloatingActionButton( + onPressed: _database == null ? null : _addDate, + tooltip: 'Add', + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/background_isolate_channels/lib/simple_database.dart b/background_isolate_channels/lib/simple_database.dart new file mode 100644 index 0000000..8424123 --- /dev/null +++ b/background_isolate_channels/lib/simple_database.dart @@ -0,0 +1,247 @@ +// Copyright 2022 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. + +import 'dart:async'; +import 'dart:collection'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:isolate'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +/////////////////////////////////////////////////////////////////////////////// +// **WARNING:** This is not production code and is only intended to be used for +// demonstration purposes. +// +// The following database works by spawning a background isolate and +// communicating with it over Dart's SendPort API. It is presented below as a +// demonstration of the feature "Background Isolate Channels" and shows using +// plugins from a background isolate. The [SimpleDatabase] operates on the root +// isolate and the [_SimpleDatabaseServer] operates on a background isolate. +// +// Here is an example of the protocol they use to communicate: +// +// _________________ ________________________ +// [:SimpleDatabase] [:_SimpleDatabaseServer] +// ----------------- ------------------------ +// | | +// |<---------------(init)------------------------| +// |----------------(init)----------------------->| +// |<---------------(ack)------------------------>| +// | | +// |----------------(add)------------------------>| +// |<---------------(ack)-------------------------| +// | | +// |----------------(query)---------------------->| +// |<---------------(result)----------------------| +// |<---------------(result)----------------------| +// |<---------------(done)------------------------| +// +/////////////////////////////////////////////////////////////////////////////// + +/// The size of the database entries in bytes. +const int _entrySize = 256; + +/// All the command codes that can be sent and received between [SimpleDatabase] and +/// [_SimpleDatabaseServer]. +enum _Codes { init, add, query, ack, result, done } + +/// A command sent between [SimpleDatabase] and [_SimpleDatabaseServer]. +class _Command { + const _Command(this.code, {this.arg0, this.arg1}); + + final _Codes code; + final Object? arg0; + final Object? arg1; +} + +/// A SimpleDatabase that stores entries of strings to disk where they can be +/// queried. +/// +/// All the disk operations and queries are executed in a background isolate +/// operating. This class just sends and receives messages to the isolate. +class SimpleDatabase { + SimpleDatabase._(this._isolate, this._path); + + final Isolate _isolate; + final String _path; + late final SendPort _sendPort; + // Completers are stored in a queue so multiple commands can be queued up and + // handled serially. + final Queue> _completers = Queue>(); + // Similarly, StreamControllers are stored in a queue so they can be handled + // asynchronously and serially. + final Queue> _resultsStream = + Queue>(); + + /// Open the database at [path] and launch the server on a background isolate.. + static Future open(String path) async { + final ReceivePort receivePort = ReceivePort(); + final Isolate isolate = await Isolate.spawn( + _SimpleDatabaseServer._run, + receivePort.sendPort, + ); + final SimpleDatabase result = SimpleDatabase._(isolate, path); + Completer completer = Completer(); + result._completers.addFirst(completer); + receivePort.listen((message) { + result._handleCommand(message as _Command); + }); + await completer.future; + return result; + } + + /// Writes [value] to the database. + Future addEntry(String value) { + // No processing happens on the calling isolate, it gets delegated to the + // background isolate, see [__SimpleDatabaseServer._doAddEntry]. + Completer completer = Completer(); + _completers.addFirst(completer); + _sendPort.send(_Command(_Codes.add, arg0: value)); + return completer.future; + } + + /// Returns all the strings in the database that contain [query]. + Stream find(String query) { + // No processing happens on the calling isolate, it gets delegated to the + // background isolate, see [__SimpleDatabaseServer._doFind]. + StreamController resultsStream = StreamController(); + _resultsStream.addFirst(resultsStream); + _sendPort.send(_Command(_Codes.query, arg0: query)); + return resultsStream.stream; + } + + /// Handler invoked when a message is received from the port communicating + /// with the database server. + void _handleCommand(_Command command) { + switch (command.code) { + case _Codes.init: + _sendPort = command.arg0 as SendPort; + // ---------------------------------------------------------------------- + // Before using platform channels and plugins from background isolates we + // need to register it with its root isolate. This is achieved by + // acquiring a [RootIsolateToken] which the background isolate uses to + // invoke [BackgroundIsolateBinaryMessenger.ensureInitialized]. + // ---------------------------------------------------------------------- + RootIsolateToken rootIsolateToken = RootIsolateToken.instance!; + _sendPort.send( + _Command(_Codes.init, arg0: _path, arg1: rootIsolateToken), + ); + case _Codes.ack: + _completers.removeLast().complete(); + case _Codes.result: + _resultsStream.last.add(command.arg0 as String); + case _Codes.done: + _resultsStream.removeLast().close(); + default: + debugPrint('SimpleDatabase unrecognized command: ${command.code}'); + } + } + + /// Kills the background isolate and its database server. + void stop() { + _isolate.kill(); + } +} + +/// The portion of the [SimpleDatabase] that runs on the background isolate. +/// +/// This is where we use the new feature Background Isolate Channels, which +/// allows us to use plugins from background isolates. +class _SimpleDatabaseServer { + _SimpleDatabaseServer(this._sendPort); + + final SendPort _sendPort; + late final String _path; + + // ---------------------------------------------------------------------- + // Here the plugin is used from the background isolate. + // ---------------------------------------------------------------------- + + /// The main entrypoint for the background isolate sent to [Isolate.spawn]. + static void _run(SendPort sendPort) { + ReceivePort receivePort = ReceivePort(); + sendPort.send(_Command(_Codes.init, arg0: receivePort.sendPort)); + final _SimpleDatabaseServer server = _SimpleDatabaseServer(sendPort); + receivePort.listen((message) async { + final _Command command = message as _Command; + await server._handleCommand(command); + }); + } + + /// Handle the [command] received from the [ReceivePort]. + Future _handleCommand(_Command command) async { + switch (command.code) { + case _Codes.init: + _path = command.arg0 as String; + // ---------------------------------------------------------------------- + // The [RootIsolateToken] is required for + // [BackgroundIsolateBinaryMessenger.ensureInitialized] and must be + // obtained on the root isolate and passed into the background isolate via + // a [SendPort]. + // ---------------------------------------------------------------------- + RootIsolateToken rootIsolateToken = command.arg1 as RootIsolateToken; + // ---------------------------------------------------------------------- + // [BackgroundIsolateBinaryMessenger.ensureInitialized] for each + // background isolate that will use plugins. This sets up the + // [BinaryMessenger] that the Platform Channels will communicate with on + // the background isolate. + // ---------------------------------------------------------------------- + BackgroundIsolateBinaryMessenger.ensureInitialized( + rootIsolateToken, + ); + _sendPort.send(const _Command(_Codes.ack, arg0: null)); + case _Codes.add: + _doAddEntry(command.arg0 as String); + case _Codes.query: + _doFind(command.arg0 as String); + default: + debugPrint( + '_SimpleDatabaseServer unrecognized command ${command.code}', + ); + } + } + + /// Perform the add entry operation. + void _doAddEntry(String value) { + debugPrint('Performing add: $value'); + File file = File(_path); + if (!file.existsSync()) { + file.createSync(); + } + RandomAccessFile writer = file.openSync(mode: FileMode.append); + List bytes = utf8.encode(value); + if (bytes.length > _entrySize) { + bytes = bytes.sublist(0, _entrySize); + } else if (bytes.length < _entrySize) { + List newBytes = List.filled(_entrySize, 0); + for (int i = 0; i < bytes.length; ++i) { + newBytes[i] = bytes[i]; + } + bytes = newBytes; + } + writer.writeFromSync(bytes); + writer.closeSync(); + _sendPort.send(const _Command(_Codes.ack, arg0: null)); + } + + /// Perform the find entry operation. + void _doFind(String query) { + debugPrint('Performing find: $query'); + File file = File(_path); + if (file.existsSync()) { + RandomAccessFile reader = file.openSync(); + List buffer = List.filled(_entrySize, 0); + while (reader.readIntoSync(buffer) == _entrySize) { + List foo = buffer.takeWhile((value) => value != 0).toList(); + String string = utf8.decode(foo); + if (string.contains(query)) { + _sendPort.send(_Command(_Codes.result, arg0: string)); + } + } + reader.closeSync(); + } + _sendPort.send(const _Command(_Codes.done, arg0: null)); + } +} diff --git a/background_isolate_channels/pubspec.yaml b/background_isolate_channels/pubspec.yaml new file mode 100644 index 0000000..b5b6bc2 --- /dev/null +++ b/background_isolate_channels/pubspec.yaml @@ -0,0 +1,28 @@ +name: background_isolate_channels +description: A new Flutter project. +resolution: workspace + +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +version: 1.0.0+1 + +environment: + sdk: ^3.9.0-0 + +dependencies: + cupertino_icons: ^1.0.2 + flutter: + sdk: flutter + intl: ^0.20.0 + path: ^1.8.2 + path_provider: ^2.0.11 + shared_preferences: ^2.0.15 + uuid: ^4.0.0 + +dev_dependencies: + analysis_defaults: + path: ../analysis_defaults + flutter_test: + sdk: flutter +flutter: + uses-material-design: true diff --git a/compass_app/README.md b/compass_app/README.md new file mode 100644 index 0000000..05ed767 --- /dev/null +++ b/compass_app/README.md @@ -0,0 +1,59 @@ +# compass_app + +The Compass sample application is an app that helps users build and book +itineraries for trips. It’s a robust sample application with many features, routes, and screens. The app communicates +with an HTTP server, has development and production environments, brand-specific styling, and high test coverage. In +these ways and more, it simulates a real-world, feature rich Flutter application. + +The code in this application is used for explaining architecture in a Flutter application. + +![compass app splash screen](./docs/Global%20β€”%20Splash.png) +![compass app activities screen](./docs/Legacy%20β€”%20Activities.png) +![compass app home screen](./docs/Legacy%20β€”%20Home.png) +![compass app booking screen](./docs/Legacy%20β€”%20Book.png) + +The Compass app was originally [designed and built by the Firebase GenKit team](https://developers.googleblog.com/en/how-firebase-genkit-helped-add-ai-to-our-compass-app/). + +## Running the app + +This app contains multiple environments. + +* Development environment - This environment uses data from a JSON file, which is stored in the `assets` directory, and simulates developing locally. + +```bash +$ cd app +$ flutter run --target lib/main_development.dart +``` + +* Staging environment - This environment uses an HTTP server to get data, simulating a real app experience. This is a "dummy" server, that has endpoints that simply return fake data. The server can be found in the `compass_app/server` directory. You need to run the server locally before running the Flutter application. + +```bash +$ cd server +$ dart run +# => Server listening on port 8080 + +$ cd ../compass_app/app +$ flutter run --target lib/main_staging.dart +``` + +## Integration Tests + +Integration tests must be run from the `app` directory. + +**Integration tests with local data** + +```bash +cd app +$ flutter test integration_test/app_local_data_test.dart +``` + +**Integration tests with background server and remote data** + +```bash +cd app +$ flutter test integration_test/app_server_data_test.dart +``` + +Running the tests together with `flutter test integration_test` will fail. +See: https://github.com/flutter/flutter/issues/101031 + diff --git a/compass_app/app/.gitignore b/compass_app/app/.gitignore new file mode 100644 index 0000000..d0809a4 --- /dev/null +++ b/compass_app/app/.gitignore @@ -0,0 +1,48 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# Coverage test report +coverage/ diff --git a/compass_app/app/.metadata b/compass_app/app/.metadata new file mode 100644 index 0000000..925b67f --- /dev/null +++ b/compass_app/app/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "ee624bc4fd41413cbb89099b0701a42287643d9a" + channel: "beta" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: ee624bc4fd41413cbb89099b0701a42287643d9a + base_revision: ee624bc4fd41413cbb89099b0701a42287643d9a + - platform: android + create_revision: ee624bc4fd41413cbb89099b0701a42287643d9a + base_revision: ee624bc4fd41413cbb89099b0701a42287643d9a + - platform: ios + create_revision: ee624bc4fd41413cbb89099b0701a42287643d9a + base_revision: ee624bc4fd41413cbb89099b0701a42287643d9a + - platform: linux + create_revision: ee624bc4fd41413cbb89099b0701a42287643d9a + base_revision: ee624bc4fd41413cbb89099b0701a42287643d9a + - platform: macos + create_revision: ee624bc4fd41413cbb89099b0701a42287643d9a + base_revision: ee624bc4fd41413cbb89099b0701a42287643d9a + - platform: web + create_revision: ee624bc4fd41413cbb89099b0701a42287643d9a + base_revision: ee624bc4fd41413cbb89099b0701a42287643d9a + - platform: windows + create_revision: ee624bc4fd41413cbb89099b0701a42287643d9a + base_revision: ee624bc4fd41413cbb89099b0701a42287643d9a + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/compass_app/app/analysis_options.yaml b/compass_app/app/analysis_options.yaml new file mode 100644 index 0000000..f9d5a44 --- /dev/null +++ b/compass_app/app/analysis_options.yaml @@ -0,0 +1,20 @@ +linter: + rules: + - combinators_ordering + - directives_ordering + - omit_local_variable_types + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_relative_imports + +analyzer: + exclude: + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** + \ No newline at end of file diff --git a/compass_app/app/android/.gitignore b/compass_app/app/android/.gitignore new file mode 100644 index 0000000..55afd91 --- /dev/null +++ b/compass_app/app/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/compass_app/app/android/app/build.gradle b/compass_app/app/android/app/build.gradle new file mode 100644 index 0000000..fb8c978 --- /dev/null +++ b/compass_app/app/android/app/build.gradle @@ -0,0 +1,44 @@ +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + +android { + namespace = "com.example.compass_app" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.compass_app" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.debug + } + } +} + +flutter { + source = "../.." +} diff --git a/compass_app/app/android/app/src/debug/AndroidManifest.xml b/compass_app/app/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/compass_app/app/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/compass_app/app/android/app/src/main/AndroidManifest.xml b/compass_app/app/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7d5151b --- /dev/null +++ b/compass_app/app/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/compass_app/app/android/app/src/main/kotlin/com/example/compass_app/MainActivity.kt b/compass_app/app/android/app/src/main/kotlin/com/example/compass_app/MainActivity.kt new file mode 100644 index 0000000..aab96a4 --- /dev/null +++ b/compass_app/app/android/app/src/main/kotlin/com/example/compass_app/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.compass_app + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/compass_app/app/android/app/src/main/res/drawable-v21/launch_background.xml b/compass_app/app/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/compass_app/app/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/compass_app/app/android/app/src/main/res/drawable/launch_background.xml b/compass_app/app/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/compass_app/app/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/compass_app/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/compass_app/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/compass_app/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/compass_app/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/compass_app/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/compass_app/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/compass_app/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/compass_app/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/compass_app/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/compass_app/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/compass_app/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/compass_app/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/compass_app/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/compass_app/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/compass_app/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/compass_app/app/android/app/src/main/res/values-night/styles.xml b/compass_app/app/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..4732e85 --- /dev/null +++ b/compass_app/app/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/compass_app/app/android/app/src/main/res/values/styles.xml b/compass_app/app/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..ce8badf --- /dev/null +++ b/compass_app/app/android/app/src/main/res/values/styles.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/compass_app/app/android/app/src/profile/AndroidManifest.xml b/compass_app/app/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/compass_app/app/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/compass_app/app/android/build.gradle b/compass_app/app/android/build.gradle new file mode 100644 index 0000000..d2ffbff --- /dev/null +++ b/compass_app/app/android/build.gradle @@ -0,0 +1,18 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = "../build" +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/compass_app/app/android/gradle.properties b/compass_app/app/android/gradle.properties new file mode 100644 index 0000000..2597170 --- /dev/null +++ b/compass_app/app/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/compass_app/app/android/gradle/wrapper/gradle-wrapper.properties b/compass_app/app/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c85cfe --- /dev/null +++ b/compass_app/app/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip diff --git a/compass_app/app/android/settings.gradle b/compass_app/app/android/settings.gradle new file mode 100644 index 0000000..4f52071 --- /dev/null +++ b/compass_app/app/android/settings.gradle @@ -0,0 +1,25 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.6.0" apply false + id "org.jetbrains.kotlin.android" version "2.1.0" apply false +} + +include ":app" diff --git a/compass_app/app/assets/destinations.json b/compass_app/app/assets/destinations.json new file mode 100644 index 0000000..be1f233 --- /dev/null +++ b/compass_app/app/assets/destinations.json @@ -0,0 +1,1235 @@ +[ + { + "ref": "alaska", + "name": "Alaska", + "country": "United States", + "continent": "North America", + "knownFor": "Alaska is a haven for outdoor enthusiasts and nature lovers. Visitors can experience glaciers, mountains, and wildlife, making it ideal for hiking, kayaking, and wildlife viewing. Alaska also offers a unique cultural experience with its rich Native American heritage and frontier spirit.", + "tags": ["Mountain", "Off-the-beaten-path", "Wildlife watching"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/alaska.jpg" + }, + { + "ref": "amalfi-coast", + "name": "Amalfi Coast", + "country": "Italy", + "continent": "Europe", + "knownFor": "Experience the breathtaking beauty of the Amalfi Coast, with its dramatic cliffs, colorful villages, and turquoise waters. Indulge in delicious Italian cuisine, explore charming towns like Positano and Amalfi, and soak up the sun on picturesque beaches.", + "tags": ["Beach", "Romantic", "Foodie"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/amalfi-coast.jpg" + }, + { + "ref": "amazon-rainforest", + "name": "Amazon Rainforest", + "country": "Brazil", + "continent": "South America", + "knownFor": "Immerse yourself in the biodiversity of the world's largest rainforest. Embark on jungle treks, spot exotic wildlife, and discover indigenous cultures. Take a boat trip down the Amazon River, explore the canopy on a zipline, and experience the unique ecosystem.", + "tags": ["Jungle", "Wildlife watching", "Adventure sports"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/amazon-rainforest.jpg" + }, + { + "ref": "andes-mountains", + "name": "The Andes Mountains", + "country": "South America", + "continent": "South America", + "knownFor": "The Andes Mountains, stretching along the western coast of South America, offer diverse landscapes and experiences. Visitors can trek to Machu Picchu in Peru, explore the salt flats of Salar de Uyuni in Bolivia, or visit the glaciers of Patagonia. The Andes also provide opportunities for skiing, mountaineering, and cultural encounters with indigenous communities.", + "tags": ["Mountain", "Hiking", "Cultural experiences"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/andes-mountains.jpg" + }, + { + "ref": "angkor-wat", + "name": "Angkor Wat", + "country": "Cambodia", + "continent": "Asia", + "knownFor": "Angkor Wat, a vast temple complex in Cambodia, is the largest religious monument in the world and a UNESCO World Heritage site. Visitors can explore the intricate carvings, towering spires, and vast courtyards, marveling at the architectural grandeur and rich history of the Khmer Empire.", + "tags": ["Historic", "Cultural experiences", "Sightseeing"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/angkor-wat.jpg" + }, + { + "ref": "antelope-canyon", + "name": "Antelope Canyon", + "country": "United States", + "continent": "North America", + "knownFor": "Experience the awe-inspiring beauty of Antelope Canyon, a slot canyon renowned for its flowing sandstone formations and mesmerizing light beams. Embark on guided tours to navigate the narrow passageways and capture stunning photographs. Learn about the Navajo Nation's history and culture, as the canyon is located on their land.", + "tags": ["Off-the-beaten-path", "Hiking", "Sightseeing"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/antelope-canyon.jpg" + }, + { + "ref": "aruba", + "name": "Aruba", + "country": "Aruba", + "continent": "South America", + "knownFor": "Indulge in the beauty of Aruba, a Caribbean paradise known for its pristine beaches, turquoise waters, and year-round sunshine. Relax on the white sands of Eagle Beach, explore the vibrant capital of Oranjestad, and discover hidden coves and natural wonders. Aruba offers a perfect escape for beach lovers and water sports enthusiasts.", + "tags": ["Beach", "Island", "Scuba diving"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/aruba.jpg" + }, + { + "ref": "asheville", + "name": "Asheville", + "country": "USA", + "continent": "North America", + "knownFor": "Asheville, nestled in the Blue Ridge Mountains of North Carolina, is a vibrant city known for its arts scene, craft breweries, and outdoor activities. Visitors can explore the historic Biltmore Estate, hike in the surrounding mountains, sample local beers, and enjoy the city's eclectic shops and restaurants.", + "tags": ["City", "Hiking", "Cultural experiences"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/asheville.jpg" + }, + { + "ref": "azores", + "name": "Azores", + "country": "Portugal", + "continent": "Europe", + "knownFor": "This archipelago in the mid-Atlantic boasts stunning volcanic landscapes, lush green hills, and dramatic coastlines. Hike to crater lakes, soak in natural hot springs, or go whale watching in the surrounding waters. With its relaxed atmosphere and unique culture, the Azores is a perfect destination for nature lovers and adventurers seeking an off-the-beaten-path experience.", + "tags": ["Island", "Off-the-beaten-path", "Hiking"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/azores.jpg" + }, + { + "ref": "bali", + "name": "Bali", + "country": "Indonesia", + "continent": "Asia", + "knownFor": "Discover the cultural heart of Indonesia on the island of Bali. Explore ancient temples, experience vibrant Hindu traditions, and relax on beautiful beaches. Practice yoga and meditation, indulge in spa treatments, and enjoy the island's lush natural beauty.", + "tags": ["Island", "Cultural experiences", "Wellness retreats"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/bali.jpg" + }, + { + "ref": "banff-national-park", + "name": "Banff National Park", + "country": "Canada", + "continent": "North America", + "knownFor": "Nestled in the Canadian Rockies, Banff National Park offers stunning mountain scenery, turquoise lakes, and abundant wildlife. Hike to Lake Louise, explore Johnston Canyon, and enjoy outdoor activities year-round.", + "tags": ["Mountain", "Hiking", "Wildlife watching"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/banff-national-park.jpg" + }, + { + "ref": "belize", + "name": "Belize", + "country": "Belize", + "continent": "North America", + "knownFor": "Embark on an unforgettable adventure in Belize, a Central American gem boasting lush rainforests, ancient Maya ruins, and the world's second-largest barrier reef. Explore the mysteries of Caracol and Xunantunich, dive into the Great Blue Hole, and discover diverse marine life. Belize offers a unique blend of cultural exploration and eco-tourism.", + "tags": ["Jungle", "Historic", "Scuba diving"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/belize.jpg" + }, + { + "ref": "bhutan", + "name": "Bhutan", + "country": "Bhutan", + "continent": "Asia", + "knownFor": "Discover the mystical kingdom of Bhutan, nestled in the Himalayas. Explore ancient monasteries, hike through pristine valleys, and experience the unique culture and traditions. Immerse yourself in the spiritual atmosphere and embrace the concept of Gross National Happiness.", + "tags": ["Mountain", "Cultural experiences", "Off-the-beaten-path"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/bhutan.jpg" + }, + { + "ref": "big-island-hawaii", + "name": "Big Island", + "country": "United States", + "continent": "North America", + "knownFor": "The Big Island of Hawaii offers diverse landscapes, from volcanic craters and black sand beaches to lush rainforests and snow-capped mountains. Visitors can witness the fiery glow of Kilauea volcano, snorkel with manta rays, and stargaze atop Mauna Kea.", + "tags": ["Island", "Hiking", "Snorkeling"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/big-island-hawaii.jpg" + }, + { + "ref": "big-sur", + "name": "Big Sur", + "country": "United States", + "continent": "North America", + "knownFor": "Experience the breathtaking beauty of California's rugged coastline along Big Sur. Drive the iconic Pacific Coast Highway, stopping at dramatic cliffs, hidden coves, and redwood forests. Enjoy hiking, camping, or simply soaking up the awe-inspiring views of this natural wonderland.", + "tags": ["Road trip destination", "Secluded", "Hiking"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/big-sur.jpg" + }, + { + "ref": "bora-bora", + "name": "Bora Bora", + "country": "French Polynesia", + "continent": "Oceania", + "knownFor": "Bora Bora is synonymous with luxury and tranquility. Overwater bungalows perched above turquoise lagoons offer a unique and indulgent experience. Visitors can enjoy snorkeling, diving, and swimming amidst vibrant coral reefs and diverse marine life. The island's lush interior provides opportunities for hiking and exploring, while Polynesian culture adds a touch of exotic charm.", + "tags": ["Island", "Luxury", "Secluded"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/bora-bora.jpg" + }, + { + "ref": "botswana", + "name": "Okavango Delta", + "country": "Botswana", + "continent": "Africa", + "knownFor": "The Okavango Delta, a unique inland delta in Botswana, is a haven for wildlife enthusiasts. Visitors can embark on safari adventures, encountering elephants, lions, hippos, and a diverse array of birds. The delta's waterways offer opportunities for mokoro (canoe) excursions and boat tours, providing a close-up view of the abundant wildlife and stunning landscapes.", + "tags": ["Wildlife watching", "Adventure sports", "Luxury"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/botswana.jpg" + }, + { + "ref": "bruges", + "name": "Bruges", + "country": "Belgium", + "continent": "Europe", + "knownFor": "Step back in time in this charming medieval city with its cobblestone streets, canals, and well-preserved architecture. Explore the historic Markt square, indulge in delicious Belgian chocolate and beer, or take a boat tour through the canals. Bruges offers a romantic and picturesque escape for history buffs and those seeking a quintessential European experience.", + "tags": ["City", "Historic", "Romantic"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/bruges.jpg" + }, + { + "ref": "brunei", + "name": "Brunei", + "country": "Brunei", + "continent": "Asia", + "knownFor": "This small sultanate on the island of Borneo offers a fascinating blend of culture and nature. Visitors can explore the opulent Sultan Omar Ali Saifuddin Mosque, delve into the lush rainforests, and discover the unique water village of Kampong Ayer.", + "tags": ["Secluded", "Cultural experiences", "Island"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/brunei.jpg" + }, + { + "ref": "budapest", + "name": "Budapest", + "country": "Hungary", + "continent": "Europe", + "knownFor": "Discover the charm of Budapest, a historic city divided by the Danube River. Explore Buda's Castle District with its medieval streets and Fisherman's Bastion, offering panoramic views. Relax in the thermal baths, a legacy of the Ottoman era, or visit the Hungarian Parliament Building, a stunning example of Gothic Revival architecture.", + "tags": ["City", "Historic", "Cultural experiences"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/budapest.jpg" + }, + { + "ref": "burgundy", + "name": "Burgundy", + "country": "France", + "continent": "Europe", + "knownFor": "Burgundy, a region in eastern France, is renowned for its world-class wines, charming villages, and rich history. Explore vineyards, indulge in wine tastings, and visit medieval castles and abbeys. Cycle through rolling hills, savor gourmet cuisine, and experience the art de vivre of this picturesque region.", + "tags": ["Rural", "Wine tasting", "Cultural experiences"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/burgundy.jpg" + }, + { + "ref": "cambodia", + "name": "Cambodia", + "country": "Cambodia", + "continent": "Asia", + "knownFor": "Cambodia, a Southeast Asian nation, is a captivating blend of ancient wonders, natural beauty, and vibrant culture. Explore the magnificent temples of Angkor, relax on pristine beaches, and cruise along the Mekong River. Experience the warmth of the Cambodian people, savor delicious cuisine, and discover the rich history of this fascinating country.", + "tags": ["Historic", "Cultural experiences", "Beach"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/cambodia.jpg" + }, + { + "ref": "canadian-rockies", + "name": "Canadian Rockies", + "country": "Canada", + "continent": "North America", + "knownFor": "Embark on an adventure through the majestic Canadian Rockies, where towering mountains, turquoise lakes, and glaciers create a breathtaking landscape. Hike through scenic trails, go skiing or snowboarding in world-class resorts, and encounter diverse wildlife. Experience the thrill of outdoor activities and the beauty of untouched nature.", + "tags": ["Mountain", "Hiking", "Adventure sports"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/canadian-rockies.jpg" + }, + { + "ref": "canary-islands", + "name": "Canary Islands", + "country": "Spain", + "continent": "Europe", + "knownFor": "Discover the volcanic beauty of the Canary Islands, a Spanish archipelago off the coast of Africa. Explore diverse landscapes, from the dramatic volcanic peaks of Tenerife and Mount Teide to the golden beaches of Gran Canaria and Fuerteventura. Enjoy water sports, hiking, and stargazing in this island paradise.", + "tags": ["Island", "Beach", "Hiking"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/canary-islands.jpg" + }, + { + "ref": "cappadocia", + "name": "Cappadocia", + "country": "Turkey", + "continent": "Asia", + "knownFor": "Embark on a magical journey through a surreal landscape of fairy chimneys, cave dwellings, and underground cities. Soar above the valleys in a hot air balloon, explore ancient rock-cut churches, and experience the unique culture and hospitality of the region.", + "tags": ["Historic", "Off-the-beaten-path", "Adventure sports"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/cappadocia.jpg" + }, + { + "ref": "chilean-lake-district", + "name": "Chilean Lake District", + "country": "Chile", + "continent": "South America", + "knownFor": "The Chilean Lake District is a paradise for nature lovers, with snow-capped volcanoes, turquoise lakes, and lush forests. Hike in national parks, go kayaking on the lakes, and enjoy the tranquility of the surroundings. The region's German heritage adds a unique cultural element.", + "tags": ["Lake", "Mountain", "Hiking"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/chilean-lake-district.jpg" + }, + { + "ref": "cinque-terre", + "name": "Cinque Terre", + "country": "Italy", + "continent": "Europe", + "knownFor": "Explore the picturesque villages of Cinque Terre, perched on the rugged Italian Riviera coastline. Hike the scenic trails connecting the five colorful towns, each with its unique character. Enjoy fresh seafood, local wines, and breathtaking views of the Mediterranean Sea.", + "tags": ["Hiking", "Coastal", "Foodie"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/cinque-terre.jpg" + }, + { + "ref": "colombia", + "name": "Colombia", + "country": "Colombia", + "continent": "South America", + "knownFor": "Colombia is a vibrant country with a diverse landscape, ranging from the Andes Mountains to the Caribbean coast. Explore the colonial city of Cartagena, hike in the Cocora Valley, or dance the night away in MedellΓ­n. Discover the coffee region, learn about the indigenous cultures, and experience the warmth of the Colombian people.", + "tags": ["City", "Mountain", "Cultural experiences"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/colombia.jpg" + }, + { + "ref": "corsica", + "name": "Corsica", + "country": "France", + "continent": "Europe", + "knownFor": "This mountainous Mediterranean island offers a diverse landscape of rugged mountains, pristine beaches, and charming villages. Visitors can enjoy hiking, water sports, exploring historical sites, and experiencing the unique Corsican culture.", + "tags": ["Mountain", "Beach", "Cultural experiences"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/corsica.jpg" + }, + { + "ref": "costa-rica", + "name": "Osa Peninsula", + "country": "Costa Rica", + "continent": "North America", + "knownFor": "A haven for eco-tourism, the Osa Peninsula boasts incredible biodiversity, lush rainforests, and pristine beaches. Adventure seekers can go zip-lining, kayaking, and hiking, while nature enthusiasts can spot monkeys, sloths, and exotic birds. The Corcovado National Park, known as one of the most biodiverse places on Earth, is a must-visit for wildlife watching.", + "tags": ["Jungle", "Secluded", "Wildlife watching"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/costa-rica.jpg" + }, + { + "ref": "dubai", + "name": "Dubai", + "country": "United Arab Emirates", + "continent": "Asia", + "knownFor": "Dubai is a modern metropolis known for its towering skyscrapers, luxurious shopping malls, and extravagant attractions. Visitors can experience the thrill of the Burj Khalifa, the world's tallest building, shop at the Dubai Mall, or enjoy a desert safari. With its vibrant nightlife and world-class dining scene, Dubai offers a truly cosmopolitan experience.", + "tags": ["City", "Luxury", "Shopping"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/dubai.jpg" + }, + { + "ref": "dubrovnik", + "name": "Dubrovnik", + "country": "Croatia", + "continent": "Europe", + "knownFor": "Dubrovnik, the \"Pearl of the Adriatic\", is a historic walled city renowned for its stunning architecture, ancient city walls, and breathtaking coastal views. Visitors can explore historical sites, enjoy boat trips, and experience the vibrant cultural scene.", + "tags": ["Historic", "Sightseeing", "City"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/dubrovnik.jpg" + }, + { + "ref": "ethiopia", + "name": "Ethiopia", + "country": "Ethiopia", + "continent": "Africa", + "knownFor": "Ethiopia, a landlocked country in the Horn of Africa, is a unique destination with ancient history, diverse landscapes, and rich culture. Explore the rock-hewn churches of Lalibela, trek through the Simien Mountains, and witness the vibrant tribal traditions. From bustling cities to remote villages, Ethiopia offers an unforgettable journey.", + "tags": ["Off-the-beaten-path", "Cultural experiences", "Hiking"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/ethiopia.jpg" + }, + { + "ref": "fiesole", + "name": "Fiesole", + "country": "Italy", + "continent": "Europe", + "knownFor": "Step back in time in Fiesole, a charming hilltop town overlooking Florence, Italy. Explore Etruscan and Roman ruins, visit the beautiful Fiesole Cathedral, and wander through quaint streets lined with historic buildings. Enjoy breathtaking views of the Tuscan countryside and escape the hustle and bustle of Florence.", + "tags": ["Historic", "Rural", "Romantic"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/fiesole.jpg" + }, + { + "ref": "fiji", + "name": "Fiji", + "country": "Fiji", + "continent": "Oceania", + "knownFor": "Fiji, an archipelago in the South Pacific, is renowned for its pristine beaches, crystal-clear waters, and lush rainforests. Visitors can indulge in water activities like snorkeling, scuba diving, and surfing, or explore the islands' rich cultural heritage and traditions. Fiji is also a popular destination for honeymoons and romantic getaways.", + "tags": ["Island", "Beach", "Scuba diving"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/fiji.jpg" + }, + { + "ref": "french-alps", + "name": "French Alps", + "country": "France", + "continent": "Europe", + "knownFor": "The French Alps offer stunning mountain scenery, charming villages, and world-class skiing. During the winter months, hit the slopes in renowned resorts like Chamonix and Val d'IsΓ¨re. In the summer, enjoy hiking, mountain biking, and paragliding.", + "tags": ["Mountain", "Skiing", "Hiking"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/french-alps.jpg" + }, + { + "ref": "french-polynesia", + "name": "French Polynesia", + "country": "France", + "continent": "Oceania", + "knownFor": "Escape to the paradise of French Polynesia, where overwater bungalows, turquoise lagoons, and pristine beaches await. Dive into the vibrant underwater world, explore volcanic landscapes, and experience Polynesian culture. Indulge in luxury resorts, romantic getaways, and unforgettable island hopping adventures.", + "tags": ["Tropical", "Island", "Luxury"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/french-polynesia.jpg" + }, + { + "ref": "french-riviera", + "name": "French Riviera", + "country": "France", + "continent": "Europe", + "knownFor": "Discover the glamour of the French Riviera, where glamorous beaches, luxury yachts, and charming coastal towns line the Mediterranean coast. Explore the vibrant city of Nice, visit the iconic Monte Carlo casino, and soak up the sun on the beaches of Cannes. Experience the region's luxurious lifestyle, stunning scenery, and vibrant nightlife.", + "tags": ["Beach", "Luxury", "Nightlife"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/french-riviera.jpg" + }, + { + "ref": "galapagos-islands", + "name": "Galapagos Islands", + "country": "Ecuador", + "continent": "South America", + "knownFor": "Embark on a once-in-a-lifetime adventure to the Galapagos Islands, a living laboratory of evolution. Observe unique wildlife, including giant tortoises, marine iguanas, and blue-footed boobies. Go snorkeling or diving among vibrant coral reefs and explore volcanic landscapes.", + "tags": ["Island", "Wildlife watching", "Scuba diving"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/galapagos-islands.jpg" + }, + { + "ref": "galicia", + "name": "Galicia", + "country": "Spain", + "continent": "Europe", + "knownFor": "This region in northwestern Spain boasts rugged coastlines, green landscapes, and a rich Celtic heritage. Visitors can enjoy fresh seafood, explore charming towns, embark on the Camino de Santiago pilgrimage, and discover the unique Galician culture.", + "tags": ["Hiking", "Cultural experiences", "Foodie"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/galicia.jpg" + }, + { + "ref": "grand-canyon", + "name": "Grand Canyon", + "country": "United States", + "continent": "North America", + "knownFor": "Witness the awe-inspiring vastness and natural beauty of the Grand Canyon, a UNESCO World Heritage Site. Hike along the rim for breathtaking panoramic views, descend into the canyon for a challenging adventure, or raft down the Colorado River. The Grand Canyon offers an unforgettable experience for nature enthusiasts and adventure seekers.", + "tags": ["Mountain", "Hiking", "Adventure sports"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/grand-canyon.jpg" + }, + { + "ref": "great-barrier-reef", + "name": "Great Barrier Reef", + "country": "Australia", + "continent": "Australia", + "knownFor": "The Great Barrier Reef is the world's largest coral reef system, teeming with marine life. Snorkel or scuba dive among colorful corals, spot tropical fish, and experience the underwater wonders of this natural treasure.", + "tags": ["Snorkeling", "Scuba diving", "Adventure sports"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/great-barrier-reef.jpg" + }, + { + "ref": "great-bear-rainforest", + "name": "Great Bear Rainforest", + "country": "Canada", + "continent": "North America", + "knownFor": "The Great Bear Rainforest is a vast and pristine wilderness area on the Pacific coast of British Columbia. It is home to a diverse array of wildlife, including grizzly bears, black bears, wolves, and whales. Visitors can explore the rainforest by boat, kayak, or on foot, and experience the magic of this untouched ecosystem.", + "tags": ["Wildlife watching", "Hiking", "Eco-conscious"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/great-bear-rainforest.jpg" + }, + { + "ref": "greek-islands", + "name": "Greek Islands", + "country": "Greece", + "continent": "Europe", + "knownFor": "Island hop through the Greek Islands, each with its own unique charm and allure. Explore ancient ruins, relax on pristine beaches, and indulge in delicious Greek cuisine. Experience the vibrant nightlife of Mykonos, the romantic sunsets of Santorini, and the historical treasures of Crete. Discover a world of crystal-clear waters, whitewashed villages, and endless sunshine.", + "tags": ["Island", "Beach", "Historic"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/greek-islands.jpg" + }, + { + "ref": "greenland", + "name": "Ilulissat Icefjord", + "country": "Greenland", + "continent": "North America", + "knownFor": "Ilulissat Icefjord is a UNESCO World Heritage site and a breathtaking natural wonder with massive icebergs calving from the Sermeq Kujalleq glacier. Visitors can take boat tours to witness the stunning ice formations, go hiking in the surrounding area, and experience the unique culture of Greenland.", + "tags": ["Off-the-beaten-path", "Adventure sports", "Wildlife watching"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/greenland.jpg" + }, + { + "ref": "guadeloupe", + "name": "Guadeloupe", + "country": "France", + "continent": "North America", + "knownFor": "Guadeloupe, a butterfly-shaped archipelago in the Caribbean, is a French overseas territory boasting stunning natural landscapes. With its lush rainforests, volcanic peaks, and white-sand beaches, it's a paradise for outdoor enthusiasts. Hike to cascading waterfalls, explore vibrant coral reefs, and savor the unique blend of French and Creole culture.", + "tags": ["Island", "Tropical", "Hiking"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/guadeloupe.jpg" + }, + { + "ref": "guatemala", + "name": "Lake AtitlΓ‘n", + "country": "Guatemala", + "continent": "North America", + "knownFor": "Lake AtitlΓ‘n, surrounded by volcanoes and traditional Mayan villages, offers a scenic and cultural experience in Guatemala. Visitors can explore the lakeside towns, hike to viewpoints, visit Mayan ruins, and learn about local traditions. The lake also provides opportunities for kayaking, swimming, and boat trips.", + "tags": ["Lake", "Cultural experiences", "Hiking"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/guatemala.jpg" + }, + { + "ref": "ha-long-bay", + "name": "Ha Long Bay", + "country": "Vietnam", + "continent": "Asia", + "knownFor": "Ha Long Bay is a breathtaking UNESCO World Heritage Site, featuring thousands of limestone islands and islets rising from emerald waters. Visitors can cruise through the bay, explore hidden caves, kayak among the karst formations, and experience the unique beauty of this natural wonder.", + "tags": ["Island", "Secluded", "Sightseeing"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/ha-long-bay.jpg" + }, + { + "ref": "harrisburg", + "name": "Harrisburg", + "country": "USA", + "continent": "North America", + "knownFor": "Discover the charm of Harrisburg, Pennsylvania's historic capital city. Explore the impressive Pennsylvania State Capitol Building, delve into history at the National Civil War Museum, and enjoy family fun at City Island. With its scenic riverfront location, Harrisburg offers a blend of cultural attractions and outdoor activities.", + "tags": ["City", "Historic", "Family-friendly"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/harrisburg.jpg" + }, + { + "ref": "havana", + "name": "Havana", + "country": "Cuba", + "continent": "North America", + "knownFor": "Step back in time in Havana, the vibrant capital of Cuba. Stroll along the MalecΓ³n, a seaside promenade, and admire the colorful vintage cars. Explore Old Havana, a UNESCO World Heritage Site, with its colonial architecture and lively squares. Immerse yourself in Cuban culture, music, and dance.", + "tags": ["City", "Historic", "Cultural experiences"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/havana.jpg" + }, + { + "ref": "ho-chi-minh-city", + "name": "Ho Chi Minh City", + "country": "Vietnam", + "continent": "Asia", + "knownFor": "Ho Chi Minh City is a vibrant metropolis with a rich history and delicious street food. Explore the bustling markets, visit historical landmarks like the Cu Chi Tunnels, and immerse yourself in the city's energetic atmosphere. The blend of French colonial architecture and modern skyscrapers creates a unique cityscape.", + "tags": ["City", "Cultural experiences", "Food tours"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/ho-chi-minh-city.jpg" + }, + { + "ref": "iceland", + "name": "Iceland", + "country": "Iceland", + "continent": "Europe", + "knownFor": "Iceland's dramatic landscapes include glaciers, volcanoes, geysers, and waterfalls. Explore the Golden Circle, relax in geothermal pools, and witness the Northern Lights in winter.", + "tags": ["Secluded", "Adventure sports", "Winter destination"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/iceland.jpg" + }, + { + "ref": "japan-alps", + "name": "Japanese Alps", + "country": "Japan", + "continent": "Asia", + "knownFor": "The Japanese Alps offer stunning mountain scenery, traditional villages, and outdoor adventures. Hike through the Kamikochi Valley, ski in Hakuba, or soak in the onsen hot springs. Visit Matsumoto Castle, a historic landmark, and experience the unique culture of the mountain communities.", + "tags": ["Mountain", "Hiking", "Skiing"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/japan-alps.jpg" + }, + { + "ref": "jeju-island", + "name": "Jeju Island", + "country": "South Korea", + "continent": "Asia", + "knownFor": "Escape to the volcanic paradise of Jeju Island, a popular destination off the coast of South Korea. Discover stunning natural landscapes, from volcanic craters and lava tubes to pristine beaches and waterfalls. Explore unique museums, hike Mount Hallasan, and relax in charming coastal towns.", + "tags": ["Island", "Hiking", "Secluded"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/jeju-island.jpg" + }, + { + "ref": "jordan", + "name": "Petra", + "country": "Jordan", + "continent": "Asia", + "knownFor": "Petra, an ancient city carved into rose-colored sandstone cliffs, is a UNESCO World Heritage site and one of the New Seven Wonders of the World. Visitors can marvel at the Treasury, explore the Siq, and discover hidden tombs and temples. Petra offers a glimpse into the fascinating history and culture of the Nabataean civilization.", + "tags": ["Historic", "Cultural experiences", "Off-the-beaten-path"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/jordan.jpg" + }, + { + "ref": "kauai", + "name": "Kauai", + "country": "United States", + "continent": "North America", + "knownFor": "Escape to the Garden Isle of Kauai, a paradise of lush rainforests, dramatic cliffs, and pristine beaches. Hike the challenging Kalalau Trail along the Na Pali Coast, kayak the Wailua River, or relax on Poipu Beach. Discover hidden waterfalls, explore Waimea Canyon, and experience the island's laid-back atmosphere.", + "tags": ["Island", "Hiking", "Beach"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/kauai.jpg" + }, + { + "ref": "kenya", + "name": "Kenya", + "country": "Kenya", + "continent": "Africa", + "knownFor": "Embark on an unforgettable safari adventure in Kenya, home to diverse wildlife and stunning landscapes. Witness the Great Migration, encounter lions, elephants, and rhinos, and experience the rich culture of the Maasai people.", + "tags": ["Wildlife watching", "Safari", "Cultural experiences"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/kenya.jpg" + }, + { + "ref": "kyoto", + "name": "Kyoto", + "country": "Japan", + "continent": "Asia", + "knownFor": "Kyoto, Japan's former capital, is a cultural treasure trove with numerous temples, shrines, and gardens. Experience traditional tea ceremonies, stroll through the Arashiyama Bamboo Grove, and immerse yourself in Japanese history and spirituality.", + "tags": ["Historic", "Cultural experiences", "City"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/kyoto.jpg" + }, + { + "ref": "lake-bled", + "name": "Lake Bled", + "country": "Slovenia", + "continent": "Europe", + "knownFor": "Nestled in the Julian Alps, Lake Bled is a fairytale-like destination with a stunning glacial lake, a charming island church, and a medieval castle perched on a cliff. Visitors can enjoy swimming, boating, hiking, and exploring the surrounding mountains. Bled is also known for its delicious cream cake and thermal springs.", + "tags": ["Lake", "Mountain", "Romantic"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/lake-bled.jpg" + }, + { + "ref": "lake-como", + "name": "Lake Como", + "country": "Italy", + "continent": "Europe", + "knownFor": "Escape to the picturesque shores of Lake Como, surrounded by charming villages, luxurious villas, and stunning mountain scenery. Take a boat tour on the lake, explore historic gardens, and indulge in fine dining and Italian wines. Enjoy hiking, biking, and water sports in the surrounding area.", + "tags": ["Lake", "Romantic", "Luxury"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/lake-como.jpg" + }, + { + "ref": "lake-district", + "name": "Lake District National Park", + "country": "England", + "continent": "Europe", + "knownFor": "Nestled in the heart of Cumbria, the Lake District offers breathtaking landscapes with rolling hills, shimmering lakes, and charming villages. Visitors can enjoy hiking, boating, and exploring the literary legacy of Beatrix Potter and William Wordsworth. The region is also known for its delicious local produce and cozy pubs.", + "tags": ["Lake", "Hiking", "Rural"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/lake-district.jpg" + }, + { + "ref": "lake-garda", + "name": "Lake Garda", + "country": "Italy", + "continent": "Europe", + "knownFor": "Lake Garda is the largest lake in Italy, known for its stunning scenery, charming towns, and historic sites. Visitors can enjoy swimming, sailing, windsurfing, and hiking in the surrounding mountains. The area is also famous for its lemon groves and olive oil production, offering delicious local cuisine and wine tasting experiences.", + "tags": ["Lake", "Mountain", "Food tours"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/lake-garda.jpg" + }, + { + "ref": "lake-tahoe", + "name": "Lake Tahoe", + "country": "USA", + "continent": "North America", + "knownFor": "Lake Tahoe offers a blend of outdoor adventure and stunning natural beauty. Visitors enjoy skiing in the winter and water sports like kayaking and paddleboarding in the summer. The crystal-clear lake, surrounded by mountains, provides breathtaking scenery and a relaxing atmosphere.", + "tags": ["Lake", "Mountain", "Hiking"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/lake-tahoe.jpg" + }, + { + "ref": "laos", + "name": "Laos", + "country": "Laos", + "continent": "Asia", + "knownFor": "Laos is a landlocked country in Southeast Asia, known for its laid-back atmosphere, stunning natural beauty, and ancient temples. Explore the UNESCO World Heritage city of Luang Prabang, kayak down the Mekong River, discover the Kuang Si Falls, and visit the Pak Ou Caves filled with thousands of Buddha statues.", + "tags": ["Off-the-beaten-path", "Cultural experiences", "River"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/laos.jpg" + }, + { + "ref": "lofoten-islands", + "name": "Lofoten Islands", + "country": "Norway", + "continent": "Europe", + "knownFor": "Experience the breathtaking beauty of the Arctic Circle with dramatic landscapes, towering mountains, and charming fishing villages. Hike scenic trails, kayak along pristine fjords, and marvel at the Northern Lights. Explore Viking history and indulge in fresh seafood delicacies.", + "tags": ["Island", "Secluded", "Hiking"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/lofoten-islands.jpg" + }, + { + "ref": "lombok", + "name": "Lombok", + "country": "Indonesia", + "continent": "Asia", + "knownFor": "Lombok, Bali's less-crowded neighbor, offers pristine beaches, lush rainforests, and the majestic Mount Rinjani volcano. Visitors can enjoy surfing, diving, hiking, and exploring the island's cultural attractions.", + "tags": ["Beach", "Mountain", "Adventure sports"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/lombok.jpg" + }, + { + "ref": "luang-prabang", + "name": "Luang Prabang", + "country": "Laos", + "continent": "Asia", + "knownFor": "Immerse yourself in the tranquility of Luang Prabang, a UNESCO World Heritage town in Laos. Visit ornate temples like Wat Xieng Thong, witness the alms-giving ceremony at dawn, and explore the night market. Cruise down the Mekong River, discover Kuang Si Falls, and experience the town's spiritual atmosphere.", + "tags": ["Off-the-beaten-path", "Cultural experiences", "River"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/luang-prabang.jpg" + }, + { + "ref": "madagascar", + "name": "Madagascar", + "country": "Madagascar", + "continent": "Africa", + "knownFor": "Madagascar, an island nation off the southeast coast of Africa, is a biodiversity hotspot with unique flora and fauna. Explore rainforests, baobab-lined avenues, and pristine beaches. Encounter lemurs, chameleons, and other endemic species, and discover the rich cultural heritage of the Malagasy people. Madagascar offers an unforgettable adventure for nature lovers.", + "tags": ["Island", "Wildlife watching", "Hiking"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/madagascar.jpg" + }, + { + "ref": "maldives", + "name": "Maldives", + "country": "Maldives", + "continent": "Asia", + "knownFor": "The Maldives, a tropical island nation, offers luxurious overwater bungalows, pristine beaches, and world-class diving. Relax on the white sand, swim in the crystal-clear waters, and explore the vibrant coral reefs. The Maldives is the perfect destination for a romantic getaway or a relaxing beach vacation.", + "tags": ["Island", "Beach", "Scuba diving"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/maldives.jpg" + }, + { + "ref": "mallorca", + "name": "Mallorca", + "country": "Spain", + "continent": "Europe", + "knownFor": "Mallorca offers a diverse experience, from stunning beaches and turquoise waters to charming villages and rugged mountains. Visitors can explore historic Palma, hike the Serra de Tramuntana, or simply relax on the beach and enjoy the Mediterranean sunshine.", + "tags": ["Beach", "Island", "Hiking"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/mallorca.jpg" + }, + { + "ref": "malta", + "name": "Malta", + "country": "Malta", + "continent": "Europe", + "knownFor": "Malta, an archipelago in the central Mediterranean, is a captivating blend of history, culture, and stunning natural beauty. Its ancient temples, fortified cities, and hidden coves attract history buffs and adventurers alike. From exploring the UNESCO-listed capital Valletta to diving in crystal-clear waters, Malta offers a diverse experience for every traveler.", + "tags": ["Island", "Historic", "Cultural experiences"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/malta.jpg" + }, + { + "ref": "marrakech", + "name": "Marrakech", + "country": "Morocco", + "continent": "Africa", + "knownFor": "Step into a world of vibrant colours and bustling souks in Marrakech. Explore the historic Medina, with its maze-like alleys and hidden treasures, or marvel at the intricate architecture of mosques and palaces. Indulge in the rich flavours of Moroccan cuisine and experience the unique culture of this magical city.", + "tags": ["Cultural experiences", "City", "Shopping"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/marrakech.jpg" + }, + { + "ref": "maui", + "name": "Maui", + "country": "United States", + "continent": "North America", + "knownFor": "Experience the diverse landscapes of Maui, from volcanic craters to lush rainforests and stunning beaches. Drive the scenic Road to Hana, watch the sunrise from Haleakala Crater, or snorkel in Molokini Crater. Enjoy water sports, whale watching, and the island's relaxed vibes.", + "tags": ["Island", "Road trip", "Beach"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/maui.jpg" + }, + { + "ref": "milford-sound", + "name": "Milford Sound", + "country": "New Zealand", + "continent": "Oceania", + "knownFor": "Milford Sound, nestled in Fiordland National Park, offers breathtaking landscapes with towering cliffs, cascading waterfalls, and pristine waters. Visitors can cruise the fiord, kayak among the peaks, or hike the Milford Track for a multi-day adventure. The area is also known for its rich biodiversity, including dolphins, seals, and penguins.", + "tags": ["Secluded", "Hiking", "Wildlife watching"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/milford-sound.jpg" + }, + { + "ref": "moab", + "name": "Moab", + "country": "United States", + "continent": "North America", + "knownFor": "Moab is an adventurer's paradise, renowned for its stunning red rock formations and world-class outdoor activities. Hiking, mountain biking, and off-roading are popular pursuits in Arches and Canyonlands National Parks. The Colorado River offers white-water rafting and kayaking, while the surrounding desert landscapes provide endless opportunities for exploration and discovery.", + "tags": ["Desert", "Adventure sports", "Off-the-beaten-path"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/moab.jpg" + }, + { + "ref": "montenegro", + "name": "Montenegro", + "country": "Montenegro", + "continent": "Europe", + "knownFor": "Montenegro is a small Balkan country with a dramatic coastline, soaring mountains, and charming towns. Explore the Bay of Kotor, a UNESCO World Heritage Site, hike in Durmitor National Park, or relax on the beaches of Budva. Discover the historic cities of Kotor and Cetinje and enjoy the local seafood specialties.", + "tags": ["Beach", "Mountain", "Historic"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/montenegro.jpg" + }, + { + "ref": "mosel-valley", + "name": "Mosel Valley", + "country": "Germany", + "continent": "Europe", + "knownFor": "Embark on a journey through picturesque vineyards and charming villages along the Mosel River. Explore medieval castles, indulge in world-renowned Riesling wines, and savor authentic German cuisine. Hike or bike along scenic trails, or take a leisurely river cruise to soak in the breathtaking landscapes.", + "tags": ["River", "Wine tasting", "Hiking"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/mosel-valley.jpg" + }, + { + "ref": "myanmar", + "name": "Myanmar", + "country": "Myanmar", + "continent": "Asia", + "knownFor": "Myanmar (formerly Burma) is a country rich in culture and history, with ancient temples, stunning landscapes, and friendly people. Explore the iconic Shwedagon Pagoda in Yangon, visit the temple complex of Bagan, and cruise along the Irrawaddy River.", + "tags": ["Secluded", "Cultural experiences", "Off-the-beaten-path"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/myanmar.jpg" + }, + { + "ref": "mykonos", + "name": "Mykonos", + "country": "Greece", + "continent": "Europe", + "knownFor": "Mykonos is a glamorous Greek island famous for its whitewashed houses, iconic windmills, and vibrant nightlife. Visitors can relax on beautiful beaches, explore charming towns, and indulge in delicious Greek cuisine. Mykonos is also known for its luxury hotels, designer boutiques, and lively beach clubs.", + "tags": ["Island", "Beach", "Nightlife"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/mykonos.jpg" + }, + { + "ref": "nairobi", + "name": "Nairobi", + "country": "Kenya", + "continent": "Africa", + "knownFor": "Nairobi, the bustling capital of Kenya, serves as a gateway to the country's renowned safari destinations. Visit the David Sheldrick Elephant Orphanage, explore the Karen Blixen Museum, and experience the vibrant nightlife and cultural scene.", + "tags": ["City", "Wildlife watching", "Cultural experiences"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/nairobi.jpg" + }, + { + "ref": "namibia", + "name": "Sossusvlei", + "country": "Namibia", + "continent": "Africa", + "knownFor": "Sossusvlei is a mesmerizing salt and clay pan surrounded by towering red sand dunes in the Namib Desert. Visitors can embark on scenic drives, climb the dunes for panoramic views, and capture breathtaking photos of the unique landscape. Sossusvlei is a photographer's paradise and a must-visit for desert enthusiasts.", + "tags": ["Desert", "Off-the-beaten-path", "Photography"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/namibia.jpg" + }, + { + "ref": "napa-valley", + "name": "Napa Valley", + "country": "United States", + "continent": "North America", + "knownFor": "Indulge in the world-renowned wine region of Napa Valley, California. Visit picturesque vineyards, sample exquisite wines, and savor gourmet cuisine at Michelin-starred restaurants. Explore charming towns like St. Helena and Yountville, or relax in luxurious spa resorts. Napa Valley offers a sophisticated and relaxing getaway for wine lovers and foodies.", + "tags": ["Wine tasting", "Food tours", "Luxury"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/napa-valley.jpg" + }, + { + "ref": "new-orleans", + "name": "New Orleans", + "country": "United States", + "continent": "North America", + "knownFor": "New Orleans is a vibrant city with a unique culture, known for its jazz music, Mardi Gras celebrations, and Creole cuisine. Explore the French Quarter, listen to live music on Frenchmen Street, or visit the historic cemeteries. Experience the nightlife, enjoy the local food, and immerse yourself in the spirit of New Orleans.", + "tags": ["City", "Cultural experiences", "Foodie"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/new-orleans.jpg" + }, + { + "ref": "new-zealand", + "name": "New Zealand", + "country": "New Zealand", + "continent": "Oceania", + "knownFor": "Embark on an adventure in New Zealand, a land of diverse landscapes, from snow-capped mountains and glaciers to geothermal wonders and lush rainforests. Hike through Fiordland National Park, explore the Waitomo Caves, and experience the thrill of bungee jumping and white-water rafting. Discover the Maori culture, indulge in delicious local cuisine, and marvel at the country's natural beauty.", + "tags": ["Adventure sports", "Hiking", "Cultural experiences"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/new-zealand.jpg" + }, + { + "ref": "newfoundland", + "name": "Newfoundland", + "country": "Canada", + "continent": "North America", + "knownFor": "Newfoundland boasts rugged coastlines, charming fishing villages, and abundant wildlife. Hike along scenic trails, go whale watching, and experience the unique local culture. The island's friendly people and lively music scene add to its appeal.", + "tags": ["Island", "Wildlife watching", "Hiking"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/newfoundland.jpg" + }, + { + "ref": "nicaragua", + "name": "Nicaragua", + "country": "Nicaragua", + "continent": "North America", + "knownFor": "Nicaragua offers a diverse landscape of volcanoes, lakes, beaches, and rainforests. Visitors can enjoy adventure activities like surfing, volcano boarding, and zip-lining, as well as exploring colonial cities and experiencing the rich Nicaraguan culture.", + "tags": ["Adventure sports", "Beach", "Cultural experiences"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/nicaragua.jpg" + }, + { + "ref": "northern-territory", + "name": "Kakadu National Park", + "country": "Australia", + "continent": "Oceania", + "knownFor": "Kakadu National Park is a UNESCO World Heritage site with diverse landscapes, including wetlands, sandstone escarpments, and ancient rock art sites. Visitors can explore Aboriginal culture, go bird watching, take boat tours through wetlands, and admire cascading waterfalls. Kakadu is a haven for wildlife, with crocodiles, wallabies, and numerous bird species.", + "tags": ["National Park", "Wildlife watching", "Cultural experiences"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/northern-territory.jpg" + }, + { + "ref": "oaxaca", + "name": "Oaxaca", + "country": "Mexico", + "continent": "North America", + "knownFor": "Immerse yourself in the vibrant culture and rich history of Oaxaca, Mexico. Explore ancient Zapotec ruins, visit colorful markets filled with handicrafts, and experience traditional festivals. Sample the region's renowned cuisine, including mole sauces and mezcal. Oaxaca offers a unique and authentic travel experience for culture enthusiasts and foodies.", + "tags": ["Cultural experiences", "Food tours", "Historic"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/oaxaca.jpg" + }, + { + "ref": "okavango-delta", + "name": "Okavango Delta", + "country": "Botswana", + "continent": "Africa", + "knownFor": "The Okavango Delta, a vast inland delta in Botswana, is a haven for wildlife. Explore the waterways by mokoro (traditional canoe) and witness elephants, lions, hippos, and an array of bird species in their natural habitat.", + "tags": ["River", "Wildlife watching", "Adventure sports"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/okavango-delta.jpg" + }, + { + "ref": "oman", + "name": "Oman", + "country": "Oman", + "continent": "Asia", + "knownFor": "Oman, a country on the Arabian Peninsula, is a hidden gem with dramatic landscapes, ancient forts, and warm hospitality. Explore the vast deserts, swim in turquoise wadis, and hike through rugged mountains. Visit traditional souks, experience Bedouin culture, and discover the unique blend of modernity and tradition in this captivating destination.", + "tags": ["Desert", "Secluded", "Cultural experiences"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/oman.jpg" + }, + { + "ref": "pagos-islands", + "name": "GalΓ‘pagos Islands", + "country": "Ecuador", + "continent": "South America", + "knownFor": "A unique archipelago off the coast of Ecuador, the GalΓ‘pagos Islands is a haven for wildlife enthusiasts. Encounter giant tortoises, marine iguanas, blue-footed boobies, and sea lions in their natural habitat. Snorkeling and diving opportunities reveal a vibrant underwater world.", + "tags": ["Island", "Wildlife watching", "Snorkeling"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/pagos-islands.jpg" + }, + { + "ref": "patagonia", + "name": "Patagonia", + "country": "Argentina and Chile", + "continent": "South America", + "knownFor": "Patagonia is a vast region at the southern tip of South America, known for its glaciers, mountains, and diverse wildlife. Visitors can embark on trekking adventures, witness the Perito Moreno Glacier, go kayaking, and spot penguins, whales, and other animals.", + "tags": ["Hiking", "Adventure sports", "Wildlife watching"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/patagonia.jpg" + }, + { + "ref": "peru", + "name": "Arequipa", + "country": "Peru", + "continent": "South America", + "knownFor": "Arequipa, known as the \"White City\" due to its buildings made of white volcanic stone, is a beautiful colonial city in southern Peru. Visitors can explore the historic center, visit the Santa Catalina Monastery, and enjoy stunning views of the surrounding volcanoes. Arequipa is also a gateway to the Colca Canyon, one of the deepest canyons in the world.", + "tags": ["City", "Historic", "Hiking"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/peru.jpg" + }, + { + "ref": "peruvian-amazon", + "name": "Peruvian Amazon", + "country": "Peru", + "continent": "South America", + "knownFor": "Venture into the heart of the Amazon rainforest, a biodiversity hotspot teeming with exotic wildlife and indigenous cultures. Embark on jungle treks, navigate the Amazon River, and spot unique flora and fauna. Experience the thrill of adventure travel and connect with nature in its purest form.", + "tags": ["Jungle", "Adventure sports", "Wildlife watching"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/peruvian-amazon.jpg" + }, + { + "ref": "phnom-penh", + "name": "Phnom Penh", + "country": "Cambodia", + "continent": "Asia", + "knownFor": "Immerse yourself in the vibrant culture and rich history of Phnom Penh, Cambodia's bustling capital. Visit the Royal Palace, explore ancient temples like Wat Phnom, and delve into the poignant history at the Tuol Sleng Genocide Museum. Experience the city's energetic nightlife and savor delicious Khmer cuisine.", + "tags": ["City", "Historic", "Cultural experiences"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/phnom-penh.jpg" + }, + { + "ref": "phuket", + "name": "Phuket", + "country": "Thailand", + "continent": "Asia", + "knownFor": "Relax on the stunning beaches of Phuket, Thailand's largest island. Explore the turquoise waters of the Andaman Sea, go snorkeling or scuba diving among coral reefs, or visit nearby islands like Phi Phi. Enjoy the vibrant nightlife, indulge in Thai massages, and experience the local culture. Phuket offers a perfect blend of relaxation and adventure for beach lovers and those seeking a tropical escape.", + "tags": ["Beach", "Island", "Snorkeling"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/phuket.jpg" + }, + { + "ref": "positano", + "name": "Positano", + "country": "Italy", + "continent": "Europe", + "knownFor": "Nestled along the Amalfi Coast, Positano enchants with its colorful cliffside houses, pebble beaches, and luxury boutiques. Explore the narrow streets, indulge in delicious Italian cuisine, and soak up the romantic atmosphere.", + "tags": ["Beach", "Romantic", "Luxury"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/positano.jpg" + }, + { + "ref": "prague", + "name": "Prague", + "country": "Czech Republic", + "continent": "Europe", + "knownFor": "Prague, with its stunning architecture and rich history, is a fairytale city. Explore the Prague Castle, wander through the Old Town Square, and enjoy a traditional Czech meal. The city's charming atmosphere and affordable prices make it a popular destination.", + "tags": ["City", "Historic", "Sightseeing"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/prague.jpg" + }, + { + "ref": "puerto-rico", + "name": "Puerto Rico", + "country": "United States", + "continent": "North America", + "knownFor": "Puerto Rico is a Caribbean island with a rich history, vibrant culture, and stunning natural beauty. Explore the historic forts of Old San Juan, relax on the beaches of Vieques, or hike in El Yunque National Forest. Experience the bioluminescent bays, go salsa dancing, and enjoy the local cuisine.", + "tags": ["Island", "Beach", "Historic"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/puerto-rico.jpg" + }, + { + "ref": "punta-cana", + "name": "Punta Cana", + "country": "Dominican Republic", + "continent": "North America", + "knownFor": "Punta Cana is a renowned beach destination with pristine white sand, crystal-clear waters, and luxurious resorts. Enjoy water sports, golf, or simply unwind by the ocean and experience the vibrant Dominican culture.", + "tags": ["Beach", "All-inclusive", "Family-friendly"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/punta-cana.jpg" + }, + { + "ref": "quebec-city", + "name": "Quebec City", + "country": "Canada", + "continent": "North America", + "knownFor": "Experience the European charm of Quebec City, a UNESCO World Heritage Site. Wander through the historic Old Town with its cobblestone streets and fortified walls, visit the iconic Chateau Frontenac, and admire the stunning views of the St. Lawrence River. Quebec City offers a unique blend of history, culture, and French Canadian charm for a memorable getaway.", + "tags": ["City", "Historic", "Cultural experiences"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/quebec-city.jpg" + }, + { + "ref": "queenstown", + "name": "Queenstown", + "country": "New Zealand", + "continent": "Oceania", + "knownFor": "Experience the adventure capital of the world in Queenstown, New Zealand. Surrounded by stunning mountains and Lake Wakatipu, this town offers everything from bungy jumping and skydiving to skiing and hiking.", + "tags": ["Adventure sports", "Lake", "Mountain"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/queenstown.jpg" + }, + { + "ref": "rajasthan", + "name": "Rajasthan", + "country": "India", + "continent": "Asia", + "knownFor": "Rajasthan, a state in northwestern India, is known for its opulent palaces, vibrant culture, and desert landscapes. Visitors can explore the cities of Jaipur, Jodhpur, and Udaipur, with their magnificent forts and palaces. The region also offers camel safaris, desert camping, and opportunities to experience traditional Rajasthani music and dance.", + "tags": ["Historic", "Desert", "Cultural experiences"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/rajasthan.jpg" + }, + { + "ref": "reunion-island", + "name": "RΓ©union Island", + "country": "France", + "continent": "Africa", + "knownFor": "This volcanic island boasts dramatic landscapes, including Piton de la Fournaise, one of the world's most active volcanoes. Hike through lush rainforests, relax on black sand beaches, and experience the unique Creole culture.", + "tags": ["Secluded", "Hiking", "Tropical"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/reunion-island.jpg" + }, + { + "ref": "rio-de-janeiro", + "name": "Rio de Janeiro", + "country": "Brazil", + "continent": "South America", + "knownFor": "Rio de Janeiro is a vibrant city known for its stunning beaches, iconic landmarks like Christ the Redeemer and Sugarloaf Mountain, and lively Carnival celebrations. Visitors can enjoy sunbathing, surfing, and exploring the city's diverse neighborhoods, experiencing the infectious energy and cultural richness of Brazil.", + "tags": ["City", "Beach", "Party"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/rio-de-janeiro.jpg" + }, + { + "ref": "salar-de-uyuni", + "name": "Salar de Uyuni", + "country": "Bolivia", + "continent": "South America", + "knownFor": "Witness the surreal beauty of the world's largest salt flats, a mesmerizing landscape that transforms into a giant mirror during the rainy season. Capture incredible photos, explore unique rock formations, and visit nearby lagoons teeming with flamingos.", + "tags": ["Desert", "Off-the-beaten-path", "Photography"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/salar-de-uyuni.jpg" + }, + { + "ref": "san-diego", + "name": "San Diego", + "country": "United States", + "continent": "North America", + "knownFor": "San Diego, a sunny coastal city in Southern California, boasts beautiful beaches, a world-famous zoo, and a vibrant cultural scene. Explore Balboa Park, visit the historic Gaslamp Quarter, and enjoy water sports along the Pacific coast.", + "tags": ["City", "Beach", "Family-friendly"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/san-diego.jpg" + }, + { + "ref": "san-miguel-de-allende", + "name": "San Miguel de Allende", + "country": "Mexico", + "continent": "North America", + "knownFor": "This colonial city in central Mexico is a UNESCO World Heritage site with stunning Spanish architecture, vibrant cultural events, and a thriving arts scene. Visitors can explore historic churches, wander through cobbled streets lined with colorful houses, and enjoy delicious Mexican cuisine. San Miguel de Allende is also a popular destination for art classes and workshops.", + "tags": ["City", "Historic", "Cultural experiences"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/san-miguel-de-allende.jpg" + }, + { + "ref": "san-sebastian", + "name": "San Sebastian", + "country": "Spain", + "continent": "Europe", + "knownFor": "Indulge in the culinary delights of this coastal paradise, renowned for its Michelin-starred restaurants and pintxos bars. Relax on the beautiful beaches, explore the charming Old Town, and hike or bike in the surrounding hills. Experience the vibrant culture and lively festivals of the Basque region.", + "tags": ["Beach", "City", "Foodie"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/san-sebastian.jpg" + }, + { + "ref": "santorini", + "name": "Santorini", + "country": "Greece", + "continent": "Europe", + "knownFor": "Santorini's iconic whitewashed villages perched on volcanic cliffs offer breathtaking views of the Aegean Sea. Explore charming Oia, visit ancient Akrotiri, and enjoy romantic sunsets with caldera views.", + "tags": ["Island", "Romantic", "Luxury"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/santorini.jpg" + }, + { + "ref": "sardinia", + "name": "Sardinia", + "country": "Italy", + "continent": "Europe", + "knownFor": "Experience the allure of Sardinia, a Mediterranean island boasting stunning coastlines, turquoise waters, and rugged mountains. Explore charming villages, ancient ruins, and secluded coves. Indulge in delicious Sardinian cuisine, hike scenic trails, and discover the island's rich history and culture.", + "tags": ["Island", "Beach", "Hiking"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/sardinia.jpg" + }, + { + "ref": "scotland", + "name": "Scotland", + "country": "United Kingdom", + "continent": "Europe", + "knownFor": "Scotland is known for its dramatic landscapes, historic castles, and vibrant cities. Explore the Scottish Highlands, visit Edinburgh Castle, and sample Scotch whisky. The country's rich history and culture, along with its friendly people, make it a captivating destination.", + "tags": ["Mountain", "Historic", "Cultural experiences"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/scotland.jpg" + }, + { + "ref": "scottish-highlands", + "name": "Scottish Highlands", + "country": "Scotland", + "continent": "Europe", + "knownFor": "The Scottish Highlands, a mountainous region in northern Scotland, is renowned for its rugged beauty, ancient castles, and rich history. Visitors can explore the dramatic landscapes through hiking, climbing, and scenic drives, or visit historic sites like Loch Ness and Eilean Donan Castle. The region is also famous for its whisky distilleries and traditional Highland culture.", + "tags": ["Mountain", "Historic", "Hiking"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/scottish-highlands.jpg" + }, + { + "ref": "seoul", + "name": "Seoul", + "country": "South Korea", + "continent": "Asia", + "knownFor": "Seoul is a vibrant metropolis blending modern skyscrapers with ancient palaces and temples. Visitors can explore historical landmarks, experience K-pop culture, indulge in delicious Korean cuisine, and enjoy the city's bustling nightlife.", + "tags": ["City", "Cultural experiences", "Nightlife"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/seoul.jpg" + }, + { + "ref": "serengeti-national-park", + "name": "Serengeti National Park", + "country": "Tanzania", + "continent": "Africa", + "knownFor": "Serengeti National Park is renowned for its incredible wildlife and the annual Great Migration, where millions of wildebeest, zebras, and other animals traverse the plains in search of fresh grazing. Visitors can embark on thrilling safari adventures, witness predator-prey interactions, and marvel at the diversity of the African savanna.", + "tags": ["Wildlife watching", "Safari", "Adventure"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/serengeti-national-park.jpg" + }, + { + "ref": "seville", + "name": "Seville", + "country": "Spain", + "continent": "Europe", + "knownFor": "Seville, the vibrant capital of Andalusia, is renowned for its flamenco dancing, Moorish architecture, and lively tapas bars. Explore the stunning AlcΓ‘zar palace, witness a passionate flamenco performance, and wander through the charming Santa Cruz district.", + "tags": ["City", "Cultural experiences", "Food tours"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/seville.jpg" + }, + { + "ref": "singapore", + "name": "Singapore", + "country": "Singapore", + "continent": "Asia", + "knownFor": "Experience a vibrant mix of cultures, cutting-edge architecture, and lush green spaces in this dynamic city-state. Discover futuristic Gardens by the Bay, indulge in diverse culinary delights, and explore world-class shopping and entertainment options.", + "tags": ["City", "Cultural experiences", "Foodie"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/singapore.jpg" + }, + { + "ref": "slovenia", + "name": "Slovenia", + "country": "Slovenia", + "continent": "Europe", + "knownFor": "Slovenia is a small country in Central Europe with stunning alpine scenery, charming towns, and a rich history. Visit Lake Bled, a picturesque lake with a church on an island, explore the Postojna Cave, or hike in Triglav National Park. Discover the capital city of Ljubljana, enjoy the local wines, and experience the Slovenian hospitality.", + "tags": ["Lake", "Mountain", "City"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/slovenia.jpg" + }, + { + "ref": "sri-lanka", + "name": "Sri Lanka", + "country": "Sri Lanka", + "continent": "Asia", + "knownFor": "Sri Lanka is an island nation off the southern coast of India, known for its ancient ruins, beautiful beaches, and diverse wildlife. Visit the Sigiriya rock fortress, relax on the beaches of Bentota, or go on a safari in Yala National Park. Explore the tea plantations, experience the local culture, and enjoy the delicious Sri Lankan cuisine.", + "tags": ["Island", "Beach", "Wildlife watching"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/sri-lanka.jpg" + }, + { + "ref": "svalbard", + "name": "Svalbard", + "country": "Norway", + "continent": "Europe", + "knownFor": "Svalbard, an Arctic archipelago under Norwegian sovereignty, is a remote and captivating destination for adventurers and nature enthusiasts. Witness glaciers, fjords, and ice-covered landscapes. Spot polar bears, walruses, and reindeer, and experience the midnight sun or the northern lights. Svalbard offers a unique opportunity to explore the Arctic wilderness.", + "tags": ["Off-the-beaten-path", "Wildlife watching", "Winter destination"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/svalbard.jpg" + }, + { + "ref": "swiss-alps", + "name": "Swiss Alps", + "country": "Switzerland", + "continent": "Europe", + "knownFor": "Discover the breathtaking beauty of the Swiss Alps, a paradise for outdoor enthusiasts. Hike through scenic mountain trails, go skiing or snowboarding in world-class resorts, or take a scenic train ride through the mountains. Enjoy the fresh air, charming villages, and stunning scenery. The Swiss Alps offer an unforgettable experience for nature lovers and adventure seekers.", + "tags": ["Mountain", "Hiking", "Skiing"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/swiss-alps.jpg" + }, + { + "ref": "tasmania", + "name": "Tasmania", + "country": "Australia", + "continent": "Oceania", + "knownFor": "Discover the wild beauty of Tasmania, an island state off the coast of Australia. Explore Cradle Mountain-Lake St Clair National Park, with its rugged mountains and pristine lakes. Visit Port Arthur Historic Site, a former penal colony, or encounter unique wildlife like Tasmanian devils and quolls.", + "tags": ["Island", "Hiking", "Wildlife watching"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/tasmania.jpg" + }, + { + "ref": "tel-aviv", + "name": "Tel Aviv", + "country": "Israel", + "continent": "Asia", + "knownFor": "Experience the vibrant and cosmopolitan city of Tel Aviv, known for its beaches, Bauhaus architecture, and thriving nightlife. Relax on the sandy shores of the Mediterranean Sea, explore the trendy neighborhoods of Neve Tzedek and Florentin, and enjoy the city's diverse culinary scene. Tel Aviv offers a perfect blend of beach relaxation, cultural experiences, and exciting nightlife.", + "tags": ["City", "Beach", "Nightlife"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/tel-aviv.jpg" + }, + { + "ref": "trans-siberian-railway", + "name": "Trans-Siberian Railway", + "country": "Russia", + "continent": "Asia", + "knownFor": "The Trans-Siberian Railway is the longest railway line in the world, stretching over 9,000 kilometers from Moscow to Vladivostok. This epic journey offers travelers a unique opportunity to experience the vastness and diversity of Russia, passing through bustling cities, remote villages, and stunning natural landscapes.", + "tags": ["Adventure sports", "Cultural experiences", "Long-haul vacation"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/trans-siberian-railway.jpg" + }, + { + "ref": "transylvania", + "name": "Transylvania", + "country": "Romania", + "continent": "Europe", + "knownFor": "Transylvania, a region in Romania, is famous for its medieval towns, fortified churches, and stunning Carpathian Mountain scenery. Visitors can explore Bran Castle, associated with the Dracula legend, visit historic cities like Brasov and Sibiu, and hike or ski in the mountains. The region also offers opportunities to experience traditional Romanian culture and cuisine.", + "tags": ["Historic", "Mountain", "Cultural experiences"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/transylvania.jpg" + }, + { + "ref": "tulum", + "name": "Tulum", + "country": "Mexico", + "continent": "North America", + "knownFor": "Tulum seamlessly blends ancient Mayan history with modern bohemian vibes. Visitors can explore the Tulum Archaeological Site, perched on cliffs overlooking the Caribbean Sea, and discover well-preserved ruins. Pristine beaches offer relaxation and water activities, while the town's eco-chic atmosphere provides yoga retreats, wellness centers, and sustainable dining options.", + "tags": ["Beach", "Cultural experiences", "Wellness retreats"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/tulum.jpg" + }, + { + "ref": "turkish-riviera", + "name": "Turkish Riviera", + "country": "Turkey", + "continent": "Asia", + "knownFor": "The Turkish Riviera offers a mix of ancient ruins, stunning beaches, and turquoise waters. Explore the historical sites of Ephesus and Antalya, relax on the sandy shores, and enjoy water sports like sailing and snorkeling. The region's delicious cuisine and affordable prices add to its appeal.", + "tags": ["Beach", "Historic", "Sightseeing"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/turkish-riviera.jpg" + }, + { + "ref": "tuscany", + "name": "Tuscany", + "country": "Italy", + "continent": "Europe", + "knownFor": "Explore the rolling hills and vineyards of Tuscany, indulging in wine tastings and farm-to-table cuisine. Discover charming medieval towns, Renaissance art, and historic cities like Florence and Siena. Immerse yourself in the region's rich culture and art scene, or simply relax and soak up the idyllic scenery.", + "tags": ["Cultural experiences", "Food tours", "Wine tasting"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/tuscany.jpg" + }, + { + "ref": "us-virgin-islands", + "name": "US Virgin Islands", + "country": "United States", + "continent": "North America", + "knownFor": "Escape to the Caribbean paradise of the US Virgin Islands, where you can relax on pristine beaches, explore coral reefs, and experience the laid-back island lifestyle. Visit the historic towns of Charlotte Amalie and Christiansted, go sailing or snorkeling in crystal-clear waters, or simply soak up the sun. The US Virgin Islands offer a perfect tropical getaway for beach lovers and those seeking a relaxing escape.", + "tags": ["Island", "Beach", "Relaxing"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/us-virgin-islands.jpg" + }, + { + "ref": "vancouver-island", + "name": "Vancouver Island", + "country": "Canada", + "continent": "North America", + "knownFor": "Vancouver Island, located off Canada's Pacific coast, is a haven for nature lovers and adventure seekers. Explore the rugged coastline, ancient rainforests, and snow-capped mountains. Go whale watching, kayaking, or surfing, and discover charming towns and vibrant cities like Victoria. Vancouver Island offers a perfect blend of wilderness and urban experiences.", + "tags": ["Island", "Adventure sports", "Wildlife watching"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/vancouver-island.jpg" + }, + { + "ref": "vienna", + "name": "Vienna", + "country": "Austria", + "continent": "Europe", + "knownFor": "Step into the imperial city of Vienna, where grand palaces, historical landmarks, and elegant cafes exude charm and sophistication. Explore museums, art galleries, and renowned opera houses, or visit SchΓΆnbrunn Palace and delve into Habsburg history. Enjoy classical music concerts, indulge in Viennese pastries, and experience the city's rich cultural heritage.", + "tags": ["City", "Historic", "Cultural experiences"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/vienna.jpg" + }, + { + "ref": "vietnam", + "name": "Vietnam", + "country": "Vietnam", + "continent": "Asia", + "knownFor": "Vietnam offers a rich tapestry of culture, history, and natural beauty. Explore the bustling streets of Hanoi, cruise through the scenic Ha Long Bay, and discover the ancient town of Hoi An. From delicious street food to stunning landscapes, Vietnam is a destination that will captivate your senses.", + "tags": ["Cultural experiences", "Food tours", "Sightseeing"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/vietnam.jpg" + }, + { + "ref": "western-australia", + "name": "Western Australia", + "country": "Australia", + "continent": "Australia", + "knownFor": "Western Australia, the largest state in Australia, is a land of vast landscapes, stunning coastlines, and unique wildlife. Explore the vibrant city of Perth, swim with whale sharks at Ningaloo Reef, and discover the ancient rock formations of the Kimberley region. From wineries to deserts, Western Australia offers a diverse and unforgettable experience.", + "tags": ["Beach", "Road trip destination", "Wildlife watching"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/western-australia.jpg" + }, + { + "ref": "yellowstone-national-park", + "name": "Yellowstone National Park", + "country": "United States", + "continent": "North America", + "knownFor": "Witness the geothermal wonders of Yellowstone, with its geysers, hot springs, and mudpots. Observe abundant wildlife, including bison, elk, and wolves. Explore the Grand Canyon of the Yellowstone, go hiking or camping, and enjoy winter sports.", + "tags": ["National Park", "Wildlife watching", "Hiking"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/yellowstone-national-park.jpg" + }, + { + "ref": "yosemite-national-park", + "name": "Yosemite National Park", + "country": "United States", + "continent": "North America", + "knownFor": "Yosemite National Park, located in California's Sierra Nevada mountains, is renowned for its towering granite cliffs, giant sequoia trees, and stunning waterfalls. Visitors can enjoy hiking, camping, rock climbing, and exploring the park's natural wonders, including Yosemite Valley, Half Dome, and Glacier Point.", + "tags": ["Mountain", "Hiking", "Sightseeing"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/yosemite-national-park.jpg" + }, + { + "ref": "yucatan-peninsula", + "name": "Yucatan Peninsula", + "country": "Mexico", + "continent": "North America", + "knownFor": "Discover ancient Mayan ruins, explore vibrant coral reefs, and relax on pristine beaches in the Yucatan Peninsula. Dive into cenotes, swim with whale sharks, and experience the rich culture and history of this captivating region.", + "tags": ["Beach", "Historic", "Scuba diving"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/yucatan-peninsula.jpg" + }, + { + "ref": "zanzibar", + "name": "Zanzibar", + "country": "Tanzania", + "continent": "Africa", + "knownFor": "Zanzibar is a Tanzanian archipelago off the coast of East Africa, known for its stunning beaches, turquoise waters, and historical Stone Town. Visitors can relax on the beach, explore the UNESCO-listed Stone Town, go diving or snorkeling, and experience the island's unique blend of African, Arab, and European influences.", + "tags": ["Beach", "Cultural experiences", "Scuba diving"], + "imageUrl": "https://storage.googleapis.com/tripedia-images/destinations/zanzibar.jpg" + } +] diff --git a/compass_app/app/assets/logo.svg b/compass_app/app/assets/logo.svg new file mode 100644 index 0000000..1bf798e --- /dev/null +++ b/compass_app/app/assets/logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/compass_app/app/assets/user.jpg b/compass_app/app/assets/user.jpg new file mode 100644 index 0000000..1e79068 Binary files /dev/null and b/compass_app/app/assets/user.jpg differ diff --git a/compass_app/app/devtools_options.yaml b/compass_app/app/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/compass_app/app/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/compass_app/app/integration_test/app_local_data_test.dart b/compass_app/app/integration_test/app_local_data_test.dart new file mode 100644 index 0000000..db7caf2 --- /dev/null +++ b/compass_app/app/integration_test/app_local_data_test.dart @@ -0,0 +1,138 @@ +import 'package:compass_app/config/dependencies.dart'; +import 'package:compass_app/main.dart'; +import 'package:compass_app/ui/activities/widgets/activities_screen.dart'; +import 'package:compass_app/ui/booking/widgets/booking_screen.dart'; +import 'package:compass_app/ui/core/ui/custom_checkbox.dart'; +import 'package:compass_app/ui/core/ui/home_button.dart'; +import 'package:compass_app/ui/home/widgets/home_screen.dart'; +import 'package:compass_app/ui/results/widgets/result_card.dart'; +import 'package:compass_app/ui/results/widgets/results_screen.dart'; +import 'package:compass_app/ui/search_form/widgets/search_form_guests.dart'; +import 'package:compass_app/ui/search_form/widgets/search_form_screen.dart'; +import 'package:compass_app/ui/search_form/widgets/search_form_submit.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:provider/provider.dart'; + +/// This Integration Test launches the Compass-App with the local configuration. +/// The app uses data from the assets folder to create a booking. +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('end-to-end test with local data', () { + testWidgets('should load app', (tester) async { + // Load app widget. + await tester.pumpWidget( + MultiProvider(providers: providersLocal, child: const MainApp()), + ); + }); + + testWidgets('Open a booking', (tester) async { + // Load app widget with local configuration + await tester.pumpWidget( + MultiProvider(providers: providersLocal, child: const MainApp()), + ); + + await tester.pumpAndSettle(); + + // Home screen + expect(find.byType(HomeScreen), findsOneWidget); + await tester.pumpAndSettle(); + + // Should show user name + expect(find.text('Sofie\'s Trips'), findsOneWidget); + + // Tap on booking (Alaska is created by default) + await tester.tap(find.text('Alaska, North America')); + await tester.pumpAndSettle(); + + // Should be at booking screen + expect(find.byType(BookingScreen), findsOneWidget); + expect(find.text('Alaska'), findsOneWidget); + }); + + testWidgets('Create booking', (tester) async { + // Load app widget with local configuration + await tester.pumpWidget( + MultiProvider(providers: providersLocal, child: const MainApp()), + ); + await tester.pumpAndSettle(); + + // Home screen + expect(find.byType(HomeScreen), findsOneWidget); + await tester.pumpAndSettle(); + + // Select create new booking + await tester.tap(find.byKey(const ValueKey(bookingButtonKey))); + await tester.pumpAndSettle(); + + // Search destinations screen + expect(find.byType(SearchFormScreen), findsOneWidget); + + // Select Europe because it is always the first result + await tester.tap(find.text('Europe'), warnIfMissed: false); + + // Select dates + await tester.tap(find.text('Add Dates')); + await tester.pumpAndSettle(); + final tomorrow = DateTime.now().add(const Duration(days: 1)).day; + final nextDay = DateTime.now().add(const Duration(days: 2)).day; + // Select first and last widget that matches today number + //and tomorrow number, sort of ensures a valid range + await tester.tap(find.text(tomorrow.toString()).first); + await tester.pumpAndSettle(); + await tester.tap(find.text(nextDay.toString()).first); + await tester.pumpAndSettle(); + await tester.tap(find.text('Save')); + await tester.pumpAndSettle(); + + // Select guests + await tester.tap( + find.byKey(const ValueKey(addGuestsKey)), + warnIfMissed: false, + ); + + // Refresh screen state + await tester.pumpAndSettle(); + + // Perform search and navigate to next screen + await tester.tap(find.byKey(const ValueKey(searchFormSubmitButtonKey))); + await tester.pumpAndSettle(); + + // Results Screen + expect(find.byType(ResultsScreen), findsOneWidget); + + // Amalfi Coast should be the first result for Europe + // Tap and navigate to activities screen + await tester.tap(find.byType(ResultCard).first); + await tester.pumpAndSettle(); + + // Activities Screen + expect(find.byType(ActivitiesScreen), findsOneWidget); + + // Select one activity + await tester.tap(find.byType(CustomCheckbox).first); + await tester.pumpAndSettle(); + expect(find.text('1 selected'), findsOneWidget); + + // Submit selection + await tester.tap(find.byKey(const ValueKey(confirmButtonKey))); + await tester.pumpAndSettle(); + + // Should be at booking screen + expect(find.byType(BookingScreen), findsOneWidget); + expect(find.text('Amalfi Coast'), findsOneWidget); + + // Navigate back home + await tester.tap(find.byType(HomeButton)); + await tester.pumpAndSettle(); + + // Home screen + expect(find.byType(HomeScreen), findsOneWidget); + + // New Booking should appear + expect(find.text('Amalfi Coast, Europe'), findsOneWidget); + }); + }); +} diff --git a/compass_app/app/integration_test/app_server_data_test.dart b/compass_app/app/integration_test/app_server_data_test.dart new file mode 100644 index 0000000..c406c72 --- /dev/null +++ b/compass_app/app/integration_test/app_server_data_test.dart @@ -0,0 +1,201 @@ +import 'dart:io'; + +import 'package:compass_app/config/dependencies.dart'; +import 'package:compass_app/main.dart'; +import 'package:compass_app/ui/activities/widgets/activities_screen.dart'; +import 'package:compass_app/ui/auth/login/widgets/login_screen.dart'; +import 'package:compass_app/ui/auth/logout/widgets/logout_button.dart'; +import 'package:compass_app/ui/booking/widgets/booking_screen.dart'; +import 'package:compass_app/ui/core/ui/custom_checkbox.dart'; +import 'package:compass_app/ui/core/ui/home_button.dart'; +import 'package:compass_app/ui/home/widgets/home_screen.dart'; +import 'package:compass_app/ui/results/widgets/result_card.dart'; +import 'package:compass_app/ui/results/widgets/results_screen.dart'; +import 'package:compass_app/ui/search_form/widgets/search_form_screen.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +/// This Integration Test starts the Dart server +/// before launching the Compass-App with the remote configuration. +/// The app connects to its endpoints to perform login and create a booking. +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('end-to-end test with remote data', () { + const port = '8080'; + late Process p; + + setUpAll(() async { + // Clear any stored shared preferences + final sharedPreferences = SharedPreferencesAsync(); + await sharedPreferences.clear(); + + // Start the dart server + p = await Process.start( + 'dart', + ['run', 'bin/compass_server.dart'], + environment: {'PORT': port}, + // Relative to the app/ folder + workingDirectory: '../server', + ); + // Wait for server to start and print to stdout. + await p.stdout.first; + }); + + tearDownAll(() => p.kill()); + + testWidgets('should load app', (tester) async { + // Load app widget. + await tester.pumpWidget( + MultiProvider(providers: providersRemote, child: const MainApp()), + ); + + await tester.pumpAndSettle(); + + // Login screen because logget out + expect(find.byType(LoginScreen), findsOneWidget); + }); + + testWidgets('Open a booking', (tester) async { + // Load app widget with local configuration + await tester.pumpWidget( + MultiProvider(providers: providersRemote, child: const MainApp()), + ); + + await tester.pumpAndSettle(); + + // Login screen because logget out + expect(find.byType(LoginScreen), findsOneWidget); + + // Perform login (credentials are prefilled) + await tester.tap(find.text('Login')); + await tester.pumpAndSettle(); + + // Home screen + expect(find.byType(HomeScreen), findsOneWidget); + await tester.pumpAndSettle(); + + // Should show user name + expect(find.text('Sofie\'s Trips'), findsOneWidget); + + // Tap on booking (Alaska is created by default) + await tester.tap(find.text('Alaska, North America')); + await tester.pumpAndSettle(); + + // Should be at booking screen + expect(find.byType(BookingScreen), findsOneWidget); + expect(find.text('Alaska'), findsOneWidget); + + // Navigate back to home + await tester.tap(find.byType(HomeButton).first); + await tester.pumpAndSettle(); + + // Home screen + expect(find.byType(HomeScreen), findsOneWidget); + + // Perform logout + await tester.tap(find.byType(LogoutButton).first); + await tester.pumpAndSettle(); + expect(find.byType(LoginScreen), findsOneWidget); + }); + + testWidgets('Create booking', (tester) async { + // Load app widget with local configuration + await tester.pumpWidget( + MultiProvider(providers: providersRemote, child: const MainApp()), + ); + + await tester.pumpAndSettle(); + + // Login screen because logget out + expect(find.byType(LoginScreen), findsOneWidget); + + // Perform login (credentials are prefilled) + await tester.tap(find.text('Login')); + await tester.pumpAndSettle(); + + // Home screen + expect(find.byType(HomeScreen), findsOneWidget); + await tester.pumpAndSettle(); + + // Select create new booking + await tester.tap(find.byKey(const ValueKey('booking-button'))); + await tester.pumpAndSettle(); + + // Search destinations screen + expect(find.byType(SearchFormScreen), findsOneWidget); + + // Select Europe because it is always the first result + await tester.tap(find.text('Europe'), warnIfMissed: false); + + // Select dates + await tester.tap(find.text('Add Dates')); + await tester.pumpAndSettle(); + final tomorrow = DateTime.now().add(const Duration(days: 1)).day; + final nextDay = DateTime.now().add(const Duration(days: 2)).day; + // Select first and last widget that matches today number + //and tomorrow number, sort of ensures a valid range + await tester.tap(find.text(tomorrow.toString()).first); + await tester.pumpAndSettle(); + await tester.tap(find.text(nextDay.toString()).first); + await tester.pumpAndSettle(); + await tester.tap(find.text('Save')); + await tester.pumpAndSettle(); + + // Select guests + await tester.tap( + find.byKey(const ValueKey('add_guests')), + warnIfMissed: false, + ); + + // Refresh screen state + await tester.pumpAndSettle(); + + // Perform search and navigate to next screen + await tester.tap(find.byKey(const ValueKey('submit_button'))); + await tester.pumpAndSettle(); + + // Results Screen + expect(find.byType(ResultsScreen), findsOneWidget); + + // Amalfi Coast should be the first result for Europe + // Tap and navigate to activities screen + await tester.tap(find.byType(ResultCard).first); + await tester.pumpAndSettle(); + + // Activities Screen + expect(find.byType(ActivitiesScreen), findsOneWidget); + + // Select one activity + await tester.tap(find.byType(CustomCheckbox).first); + await tester.pumpAndSettle(); + expect(find.text('1 selected'), findsOneWidget); + + // Submit selection + await tester.tap(find.byKey(const ValueKey('confirm-button'))); + await tester.pumpAndSettle(); + + // Should be at booking screen + expect(find.byType(BookingScreen), findsOneWidget); + expect(find.text('Amalfi Coast'), findsOneWidget); + + // Navigate back to home + await tester.tap(find.byType(HomeButton).first); + await tester.pumpAndSettle(); + + // Home screen + expect(find.byType(HomeScreen), findsOneWidget); + + // New Booking should appear + expect(find.text('Amalfi Coast, Europe'), findsOneWidget); + + // Perform logout + await tester.tap(find.byType(LogoutButton).first); + await tester.pumpAndSettle(); + expect(find.byType(LoginScreen), findsOneWidget); + }); + }); +} diff --git a/compass_app/app/ios/.gitignore b/compass_app/app/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/compass_app/app/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/compass_app/app/ios/Flutter/AppFrameworkInfo.plist b/compass_app/app/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/compass_app/app/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/compass_app/app/ios/Flutter/Debug.xcconfig b/compass_app/app/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/compass_app/app/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/compass_app/app/ios/Flutter/Release.xcconfig b/compass_app/app/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/compass_app/app/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/compass_app/app/ios/Podfile b/compass_app/app/ios/Podfile new file mode 100644 index 0000000..d97f17e --- /dev/null +++ b/compass_app/app/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/compass_app/app/ios/Runner.xcodeproj/project.pbxproj b/compass_app/app/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..9a72577 --- /dev/null +++ b/compass_app/app/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,616 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.compassApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.compassApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.compassApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.compassApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.compassApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.compassApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/compass_app/app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/compass_app/app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/compass_app/app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/compass_app/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/compass_app/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/compass_app/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/compass_app/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/compass_app/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/compass_app/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/compass_app/app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/compass_app/app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..8e3ca5d --- /dev/null +++ b/compass_app/app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/compass_app/app/ios/Runner.xcworkspace/contents.xcworkspacedata b/compass_app/app/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/compass_app/app/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/compass_app/app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/compass_app/app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/compass_app/app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/compass_app/app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/compass_app/app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/compass_app/app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/compass_app/app/ios/Runner/AppDelegate.swift b/compass_app/app/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..9074fee --- /dev/null +++ b/compass_app/app/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/compass_app/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/compass_app/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/compass_app/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/compass_app/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/compass_app/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/compass_app/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/compass_app/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/compass_app/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/compass_app/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/compass_app/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/compass_app/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/compass_app/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/compass_app/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/compass_app/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/compass_app/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/compass_app/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/compass_app/app/ios/Runner/Base.lproj/LaunchScreen.storyboard b/compass_app/app/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/compass_app/app/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/compass_app/app/ios/Runner/Base.lproj/Main.storyboard b/compass_app/app/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/compass_app/app/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/compass_app/app/ios/Runner/Info.plist b/compass_app/app/ios/Runner/Info.plist new file mode 100644 index 0000000..e8a8df1 --- /dev/null +++ b/compass_app/app/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Compass App + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + compass_app + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/compass_app/app/ios/Runner/Runner-Bridging-Header.h b/compass_app/app/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/compass_app/app/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/compass_app/app/ios/RunnerTests/RunnerTests.swift b/compass_app/app/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/compass_app/app/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/compass_app/app/lib/config/assets.dart b/compass_app/app/lib/config/assets.dart new file mode 100644 index 0000000..cbbbc4e --- /dev/null +++ b/compass_app/app/lib/config/assets.dart @@ -0,0 +1,8 @@ +// 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. + +abstract final class Assets { + static const activities = 'assets/activities.json'; + static const destinations = 'assets/destinations.json'; +} diff --git a/compass_app/app/lib/config/dependencies.dart b/compass_app/app/lib/config/dependencies.dart new file mode 100644 index 0000000..29e3543 --- /dev/null +++ b/compass_app/app/lib/config/dependencies.dart @@ -0,0 +1,135 @@ +// 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. + +import 'package:provider/provider.dart'; +import 'package:provider/single_child_widget.dart'; + +import '../data/repositories/activity/activity_repository.dart'; +import '../data/repositories/activity/activity_repository_local.dart'; +import '../data/repositories/activity/activity_repository_remote.dart'; +import '../data/repositories/auth/auth_repository.dart'; +import '../data/repositories/auth/auth_repository_dev.dart'; +import '../data/repositories/auth/auth_repository_remote.dart'; +import '../data/repositories/booking/booking_repository.dart'; +import '../data/repositories/booking/booking_repository_local.dart'; +import '../data/repositories/booking/booking_repository_remote.dart'; +import '../data/repositories/continent/continent_repository.dart'; +import '../data/repositories/continent/continent_repository_local.dart'; +import '../data/repositories/continent/continent_repository_remote.dart'; +import '../data/repositories/destination/destination_repository.dart'; +import '../data/repositories/destination/destination_repository_local.dart'; +import '../data/repositories/destination/destination_repository_remote.dart'; +import '../data/repositories/itinerary_config/itinerary_config_repository.dart'; +import '../data/repositories/itinerary_config/itinerary_config_repository_memory.dart'; +import '../data/repositories/user/user_repository.dart'; +import '../data/repositories/user/user_repository_local.dart'; +import '../data/repositories/user/user_repository_remote.dart'; +import '../data/services/api/api_client.dart'; +import '../data/services/api/auth_api_client.dart'; +import '../data/services/local/local_data_service.dart'; +import '../data/services/shared_preferences_service.dart'; +import '../domain/use_cases/booking/booking_create_use_case.dart'; +import '../domain/use_cases/booking/booking_share_use_case.dart'; + +/// Shared providers for all configurations. +List _sharedProviders = [ + Provider( + lazy: true, + create: (context) => BookingCreateUseCase( + destinationRepository: context.read(), + activityRepository: context.read(), + bookingRepository: context.read(), + ), + ), + Provider( + lazy: true, + create: (context) => BookingShareUseCase.withSharePlus(), + ), +]; + +/// Configure dependencies for remote data. +/// This dependency list uses repositories that connect to a remote server. +List get providersRemote { + return [ + Provider(create: (context) => AuthApiClient()), + Provider(create: (context) => ApiClient()), + Provider(create: (context) => SharedPreferencesService()), + ChangeNotifierProvider( + create: (context) => + AuthRepositoryRemote( + authApiClient: context.read(), + apiClient: context.read(), + sharedPreferencesService: context.read(), + ) + as AuthRepository, + ), + Provider( + create: (context) => + DestinationRepositoryRemote(apiClient: context.read()) + as DestinationRepository, + ), + Provider( + create: (context) => + ContinentRepositoryRemote(apiClient: context.read()) + as ContinentRepository, + ), + Provider( + create: (context) => + ActivityRepositoryRemote(apiClient: context.read()) + as ActivityRepository, + ), + Provider.value( + value: ItineraryConfigRepositoryMemory() as ItineraryConfigRepository, + ), + Provider( + create: (context) => + BookingRepositoryRemote(apiClient: context.read()) + as BookingRepository, + ), + Provider( + create: (context) => + UserRepositoryRemote(apiClient: context.read()) as UserRepository, + ), + ..._sharedProviders, + ]; +} + +/// Configure dependencies for local data. +/// This dependency list uses repositories that provide local data. +/// The user is always logged in. +List get providersLocal { + return [ + ChangeNotifierProvider.value(value: AuthRepositoryDev() as AuthRepository), + Provider.value(value: LocalDataService()), + Provider( + create: (context) => + DestinationRepositoryLocal(localDataService: context.read()) + as DestinationRepository, + ), + Provider( + create: (context) => + ContinentRepositoryLocal(localDataService: context.read()) + as ContinentRepository, + ), + Provider( + create: (context) => + ActivityRepositoryLocal(localDataService: context.read()) + as ActivityRepository, + ), + Provider( + create: (context) => + BookingRepositoryLocal(localDataService: context.read()) + as BookingRepository, + ), + Provider.value( + value: ItineraryConfigRepositoryMemory() as ItineraryConfigRepository, + ), + Provider( + create: (context) => + UserRepositoryLocal(localDataService: context.read()) + as UserRepository, + ), + ..._sharedProviders, + ]; +} diff --git a/compass_app/app/lib/data/repositories/activity/activity_repository.dart b/compass_app/app/lib/data/repositories/activity/activity_repository.dart new file mode 100644 index 0000000..ab0c792 --- /dev/null +++ b/compass_app/app/lib/data/repositories/activity/activity_repository.dart @@ -0,0 +1,12 @@ +// 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. + +import '../../../domain/models/activity/activity.dart'; +import '../../../utils/result.dart'; + +/// Data source for activities. +abstract class ActivityRepository { + /// Get activities by [Destination] ref. + Future>> getByDestination(String ref); +} diff --git a/compass_app/app/lib/data/repositories/activity/activity_repository_local.dart b/compass_app/app/lib/data/repositories/activity/activity_repository_local.dart new file mode 100644 index 0000000..bb13df1 --- /dev/null +++ b/compass_app/app/lib/data/repositories/activity/activity_repository_local.dart @@ -0,0 +1,30 @@ +// 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. + +import '../../../domain/models/activity/activity.dart'; +import '../../../utils/result.dart'; +import '../../services/local/local_data_service.dart'; +import 'activity_repository.dart'; + +/// Local implementation of ActivityRepository +/// Uses data from assets folder +class ActivityRepositoryLocal implements ActivityRepository { + ActivityRepositoryLocal({required LocalDataService localDataService}) + : _localDataService = localDataService; + + final LocalDataService _localDataService; + + @override + Future>> getByDestination(String ref) async { + try { + final activities = (await _localDataService.getActivities()) + .where((activity) => activity.destinationRef == ref) + .toList(); + + return Result.ok(activities); + } on Exception catch (error) { + return Result.error(error); + } + } +} diff --git a/compass_app/app/lib/data/repositories/activity/activity_repository_remote.dart b/compass_app/app/lib/data/repositories/activity/activity_repository_remote.dart new file mode 100644 index 0000000..62eef11 --- /dev/null +++ b/compass_app/app/lib/data/repositories/activity/activity_repository_remote.dart @@ -0,0 +1,35 @@ +// 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. + +import '../../../domain/models/activity/activity.dart'; +import '../../../utils/result.dart'; +import '../../services/api/api_client.dart'; +import 'activity_repository.dart'; + +/// Remote data source for [Activity]. +/// Implements basic local caching. +/// See: https://docs.flutter.dev/get-started/fwe/local-caching +class ActivityRepositoryRemote implements ActivityRepository { + ActivityRepositoryRemote({required ApiClient apiClient}) + : _apiClient = apiClient; + + final ApiClient _apiClient; + + final Map> _cachedData = {}; + + @override + Future>> getByDestination(String ref) async { + if (!_cachedData.containsKey(ref)) { + // No cached data, request activities + final result = await _apiClient.getActivityByDestination(ref); + if (result is Ok>) { + _cachedData[ref] = result.value; + } + return result; + } else { + // Return cached data if available + return Result.ok(_cachedData[ref]!); + } + } +} diff --git a/compass_app/app/lib/data/repositories/auth/auth_repository.dart b/compass_app/app/lib/data/repositories/auth/auth_repository.dart new file mode 100644 index 0000000..f0225a5 --- /dev/null +++ b/compass_app/app/lib/data/repositories/auth/auth_repository.dart @@ -0,0 +1,19 @@ +// 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. + +import 'package:flutter/foundation.dart'; + +import '../../../utils/result.dart'; + +abstract class AuthRepository extends ChangeNotifier { + /// Returns true when the user is logged in + /// Returns [Future] because it will load a stored auth state the first time. + Future get isAuthenticated; + + /// Perform login + Future> login({required String email, required String password}); + + /// Perform logout + Future> logout(); +} diff --git a/compass_app/app/lib/data/repositories/auth/auth_repository_dev.dart b/compass_app/app/lib/data/repositories/auth/auth_repository_dev.dart new file mode 100644 index 0000000..aba5651 --- /dev/null +++ b/compass_app/app/lib/data/repositories/auth/auth_repository_dev.dart @@ -0,0 +1,27 @@ +// 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. + +import '../../../utils/result.dart'; +import 'auth_repository.dart'; + +class AuthRepositoryDev extends AuthRepository { + /// User is always authenticated in dev scenarios + @override + Future get isAuthenticated => Future.value(true); + + /// Login is always successful in dev scenarios + @override + Future> login({ + required String email, + required String password, + }) async { + return const Result.ok(null); + } + + /// Logout is always successful in dev scenarios + @override + Future> logout() async { + return const Result.ok(null); + } +} diff --git a/compass_app/app/lib/data/repositories/auth/auth_repository_remote.dart b/compass_app/app/lib/data/repositories/auth/auth_repository_remote.dart new file mode 100644 index 0000000..078e8b7 --- /dev/null +++ b/compass_app/app/lib/data/repositories/auth/auth_repository_remote.dart @@ -0,0 +1,109 @@ +// 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. + +import 'package:logging/logging.dart'; + +import '../../../utils/result.dart'; +import '../../services/api/api_client.dart'; +import '../../services/api/auth_api_client.dart'; +import '../../services/api/model/login_request/login_request.dart'; +import '../../services/api/model/login_response/login_response.dart'; +import '../../services/shared_preferences_service.dart'; +import 'auth_repository.dart'; + +class AuthRepositoryRemote extends AuthRepository { + AuthRepositoryRemote({ + required ApiClient apiClient, + required AuthApiClient authApiClient, + required SharedPreferencesService sharedPreferencesService, + }) : _apiClient = apiClient, + _authApiClient = authApiClient, + _sharedPreferencesService = sharedPreferencesService { + _apiClient.authHeaderProvider = _authHeaderProvider; + } + + final AuthApiClient _authApiClient; + final ApiClient _apiClient; + final SharedPreferencesService _sharedPreferencesService; + + bool? _isAuthenticated; + String? _authToken; + final _log = Logger('AuthRepositoryRemote'); + + /// Fetch token from shared preferences + Future _fetch() async { + final result = await _sharedPreferencesService.fetchToken(); + switch (result) { + case Ok(): + _authToken = result.value; + _isAuthenticated = result.value != null; + case Error(): + _log.severe( + 'Failed to fech Token from SharedPreferences', + result.error, + ); + } + } + + @override + Future get isAuthenticated async { + // Status is cached + if (_isAuthenticated != null) { + return _isAuthenticated!; + } + // No status cached, fetch from storage + await _fetch(); + return _isAuthenticated ?? false; + } + + @override + Future> login({ + required String email, + required String password, + }) async { + try { + final result = await _authApiClient.login( + LoginRequest(email: email, password: password), + ); + switch (result) { + case Ok(): + _log.info('User logged int'); + // Set auth status + _isAuthenticated = true; + _authToken = result.value.token; + // Store in Shared preferences + return await _sharedPreferencesService.saveToken(result.value.token); + case Error(): + _log.warning('Error logging in: ${result.error}'); + return Result.error(result.error); + } + } finally { + notifyListeners(); + } + } + + @override + Future> logout() async { + _log.info('User logged out'); + try { + // Clear stored auth token + final result = await _sharedPreferencesService.saveToken(null); + if (result is Error) { + _log.severe('Failed to clear stored auth token'); + } + + // Clear token in ApiClient + _authToken = null; + + // Clear authenticated status + _isAuthenticated = false; + return result; + } finally { + notifyListeners(); + } + } + + String? _authHeaderProvider() => + _authToken != null ? 'Bearer $_authToken' : null; +} diff --git a/compass_app/app/lib/data/repositories/booking/booking_repository.dart b/compass_app/app/lib/data/repositories/booking/booking_repository.dart new file mode 100644 index 0000000..aeda7c7 --- /dev/null +++ b/compass_app/app/lib/data/repositories/booking/booking_repository.dart @@ -0,0 +1,21 @@ +// 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. + +import '../../../domain/models/booking/booking.dart'; +import '../../../domain/models/booking/booking_summary.dart'; +import '../../../utils/result.dart'; + +abstract class BookingRepository { + /// Returns the list of [BookingSummary] for the current user. + Future>> getBookingsList(); + + /// Returns a full [Booking] given the id. + Future> getBooking(int id); + + /// Creates a new [Booking]. + Future> createBooking(Booking booking); + + /// Delete booking + Future> delete(int id); +} diff --git a/compass_app/app/lib/data/repositories/booking/booking_repository_local.dart b/compass_app/app/lib/data/repositories/booking/booking_repository_local.dart new file mode 100644 index 0000000..a83a1f3 --- /dev/null +++ b/compass_app/app/lib/data/repositories/booking/booking_repository_local.dart @@ -0,0 +1,93 @@ +// 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. + +import 'dart:async'; + +import '../../../domain/models/booking/booking.dart'; +import '../../../domain/models/booking/booking_summary.dart'; +import '../../../utils/result.dart'; +import '../../services/local/local_data_service.dart'; +import 'booking_repository.dart'; + +class BookingRepositoryLocal implements BookingRepository { + BookingRepositoryLocal({required LocalDataService localDataService}) + : _localDataService = localDataService; + + // Only create default booking once + bool _isInitialized = false; + // Used to generate IDs for bookings + int _sequentialId = 0; + + final _bookings = List.empty(growable: true); + final LocalDataService _localDataService; + + @override + Future> createBooking(Booking booking) async { + // Bookings created come without id, we need to assign one + final bookingWithId = booking.copyWith(id: _sequentialId++); + _bookings.add(bookingWithId); + return const Result.ok(null); + } + + @override + Future> getBooking(int id) async { + final booking = _bookings.where((booking) => booking.id == id).firstOrNull; + if (booking == null) { + return Result.error(Exception('Booking not found')); + } + return Result.ok(booking); + } + + @override + Future>> getBookingsList() async { + // Initialize the repository with a default booking + if (!_isInitialized) { + await _createDefaultBooking(); + _isInitialized = true; + } + + return Result.ok(_createSummaries()); + } + + List _createSummaries() { + return _bookings + .map( + (booking) => BookingSummary( + id: booking.id!, + name: + '${booking.destination.name}, ${booking.destination.continent}', + startDate: booking.startDate, + endDate: booking.endDate, + ), + ) + .toList(); + } + + Future _createDefaultBooking() async { + // create a default booking the first time + if (_bookings.isEmpty) { + final destination = (await _localDataService.getDestinations()).first; + final activities = (await _localDataService.getActivities()) + .where((activity) => activity.destinationRef == destination.ref) + .take(4) + .toList(); + + _bookings.add( + Booking( + id: _sequentialId++, + startDate: DateTime(2024, 1, 1), + endDate: DateTime(2024, 2, 1), + destination: destination, + activity: activities, + ), + ); + } + } + + @override + Future> delete(int id) async { + _bookings.removeWhere((booking) => booking.id == id); + return const Result.ok(null); + } +} diff --git a/compass_app/app/lib/data/repositories/booking/booking_repository_remote.dart b/compass_app/app/lib/data/repositories/booking/booking_repository_remote.dart new file mode 100644 index 0000000..61d9bd3 --- /dev/null +++ b/compass_app/app/lib/data/repositories/booking/booking_repository_remote.dart @@ -0,0 +1,129 @@ +// 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. + +import '../../../domain/models/activity/activity.dart'; +import '../../../domain/models/booking/booking.dart'; +import '../../../domain/models/booking/booking_summary.dart'; +import '../../../domain/models/destination/destination.dart'; +import '../../../utils/result.dart'; +import '../../services/api/api_client.dart'; +import '../../services/api/model/booking/booking_api_model.dart'; +import 'booking_repository.dart'; + +class BookingRepositoryRemote implements BookingRepository { + BookingRepositoryRemote({required ApiClient apiClient}) + : _apiClient = apiClient; + + final ApiClient _apiClient; + + List? _cachedDestinations; + + @override + Future> createBooking(Booking booking) async { + try { + final bookingApiModel = BookingApiModel( + startDate: booking.startDate, + endDate: booking.endDate, + name: '${booking.destination.name}, ${booking.destination.continent}', + destinationRef: booking.destination.ref, + activitiesRef: booking.activity + .map((activity) => activity.ref) + .toList(), + ); + return await _apiClient.postBooking(bookingApiModel); + } on Exception catch (e) { + return Result.error(e); + } + } + + @override + Future> getBooking(int id) async { + try { + // Get booking by ID from server + final resultBooking = await _apiClient.getBooking(id); + switch (resultBooking) { + case Error(): + return Result.error(resultBooking.error); + case Ok(): + } + final booking = resultBooking.value; + + // Load destinations if not loaded yet + if (_cachedDestinations == null) { + final resultDestination = await _apiClient.getDestinations(); + switch (resultDestination) { + case Error>(): + return Result.error(resultDestination.error); + case Ok>(): + } + _cachedDestinations = resultDestination.value; + } + + // Get destination for booking + final destination = _cachedDestinations!.firstWhere( + (destination) => destination.ref == booking.destinationRef, + ); + + final resultActivities = await _apiClient.getActivityByDestination( + destination.ref, + ); + switch (resultActivities) { + case Error>(): + return Result.error(resultActivities.error); + case Ok>(): + } + final activities = resultActivities.value + .where((activity) => booking.activitiesRef.contains(activity.ref)) + .toList(); + + return Result.ok( + Booking( + id: booking.id, + startDate: booking.startDate, + endDate: booking.endDate, + destination: destination, + activity: activities, + ), + ); + } on Exception catch (e) { + return Result.error(e); + } + } + + @override + Future>> getBookingsList() async { + try { + final result = await _apiClient.getBookings(); + switch (result) { + case Ok>(): + final bookingsApi = result.value; + return Result.ok( + bookingsApi + .map( + (bookingApi) => BookingSummary( + id: bookingApi.id!, + name: bookingApi.name, + startDate: bookingApi.startDate, + endDate: bookingApi.endDate, + ), + ) + .toList(), + ); + case Error>(): + return Result.error(result.error); + } + } on Exception catch (e) { + return Result.error(e); + } + } + + @override + Future> delete(int id) async { + try { + return await _apiClient.deleteBooking(id); + } on Exception catch (e) { + return Result.error(e); + } + } +} diff --git a/compass_app/app/lib/data/repositories/continent/continent_repository.dart b/compass_app/app/lib/data/repositories/continent/continent_repository.dart new file mode 100644 index 0000000..14690b6 --- /dev/null +++ b/compass_app/app/lib/data/repositories/continent/continent_repository.dart @@ -0,0 +1,12 @@ +// 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. + +import '../../../domain/models/continent/continent.dart'; +import '../../../utils/result.dart'; + +/// Data source with all possible continents. +abstract class ContinentRepository { + /// Get complete list of continents. + Future>> getContinents(); +} diff --git a/compass_app/app/lib/data/repositories/continent/continent_repository_local.dart b/compass_app/app/lib/data/repositories/continent/continent_repository_local.dart new file mode 100644 index 0000000..0ffac42 --- /dev/null +++ b/compass_app/app/lib/data/repositories/continent/continent_repository_local.dart @@ -0,0 +1,21 @@ +// 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. + +import '../../../domain/models/continent/continent.dart'; +import '../../../utils/result.dart'; +import '../../services/local/local_data_service.dart'; +import 'continent_repository.dart'; + +/// Local data source with all possible continents. +class ContinentRepositoryLocal implements ContinentRepository { + ContinentRepositoryLocal({required LocalDataService localDataService}) + : _localDataService = localDataService; + + final LocalDataService _localDataService; + + @override + Future>> getContinents() async { + return Future.value(Result.ok(_localDataService.getContinents())); + } +} diff --git a/compass_app/app/lib/data/repositories/continent/continent_repository_remote.dart b/compass_app/app/lib/data/repositories/continent/continent_repository_remote.dart new file mode 100644 index 0000000..df2803b --- /dev/null +++ b/compass_app/app/lib/data/repositories/continent/continent_repository_remote.dart @@ -0,0 +1,36 @@ +// 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. + +import '../../../domain/models/continent/continent.dart'; +import '../../../utils/result.dart'; +import '../../services/api/api_client.dart'; +import 'continent_repository.dart'; + +/// Remote data source for [Continent]. +/// Implements basic local caching. +/// See: https://docs.flutter.dev/get-started/fwe/local-caching +class ContinentRepositoryRemote implements ContinentRepository { + ContinentRepositoryRemote({required ApiClient apiClient}) + : _apiClient = apiClient; + + final ApiClient _apiClient; + + List? _cachedData; + + @override + Future>> getContinents() async { + if (_cachedData == null) { + // No cached data, request continents + final result = await _apiClient.getContinents(); + if (result is Ok>) { + // Store value if result Ok + _cachedData = result.value; + } + return result; + } else { + // Return cached data if available + return Result.ok(_cachedData!); + } + } +} diff --git a/compass_app/app/lib/data/repositories/destination/destination_repository.dart b/compass_app/app/lib/data/repositories/destination/destination_repository.dart new file mode 100644 index 0000000..64af520 --- /dev/null +++ b/compass_app/app/lib/data/repositories/destination/destination_repository.dart @@ -0,0 +1,12 @@ +// 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. + +import '../../../domain/models/destination/destination.dart'; +import '../../../utils/result.dart'; + +/// Data source with all possible destinations +abstract class DestinationRepository { + /// Get complete list of destinations + Future>> getDestinations(); +} diff --git a/compass_app/app/lib/data/repositories/destination/destination_repository_local.dart b/compass_app/app/lib/data/repositories/destination/destination_repository_local.dart new file mode 100644 index 0000000..f40f3e1 --- /dev/null +++ b/compass_app/app/lib/data/repositories/destination/destination_repository_local.dart @@ -0,0 +1,27 @@ +// 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. + +import '../../../domain/models/destination/destination.dart'; +import '../../../utils/result.dart'; +import '../../services/local/local_data_service.dart'; +import 'destination_repository.dart'; + +/// Local implementation of DestinationRepository +/// Uses data from assets folder +class DestinationRepositoryLocal implements DestinationRepository { + DestinationRepositoryLocal({required LocalDataService localDataService}) + : _localDataService = localDataService; + + final LocalDataService _localDataService; + + /// Obtain list of destinations from local assets + @override + Future>> getDestinations() async { + try { + return Result.ok(await _localDataService.getDestinations()); + } on Exception catch (error) { + return Result.error(error); + } + } +} diff --git a/compass_app/app/lib/data/repositories/destination/destination_repository_remote.dart b/compass_app/app/lib/data/repositories/destination/destination_repository_remote.dart new file mode 100644 index 0000000..28edc20 --- /dev/null +++ b/compass_app/app/lib/data/repositories/destination/destination_repository_remote.dart @@ -0,0 +1,36 @@ +// 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. + +import '../../../domain/models/destination/destination.dart'; +import '../../../utils/result.dart'; +import '../../services/api/api_client.dart'; +import 'destination_repository.dart'; + +/// Remote data source for [Destination]. +/// Implements basic local caching. +/// See: https://docs.flutter.dev/get-started/fwe/local-caching +class DestinationRepositoryRemote implements DestinationRepository { + DestinationRepositoryRemote({required ApiClient apiClient}) + : _apiClient = apiClient; + + final ApiClient _apiClient; + + List? _cachedData; + + @override + Future>> getDestinations() async { + if (_cachedData == null) { + // No cached data, request destinations + final result = await _apiClient.getDestinations(); + if (result is Ok>) { + // Store value if result Ok + _cachedData = result.value; + } + return result; + } else { + // Return cached data if available + return Result.ok(_cachedData!); + } + } +} diff --git a/compass_app/app/lib/data/repositories/itinerary_config/itinerary_config_repository.dart b/compass_app/app/lib/data/repositories/itinerary_config/itinerary_config_repository.dart new file mode 100644 index 0000000..a482976 --- /dev/null +++ b/compass_app/app/lib/data/repositories/itinerary_config/itinerary_config_repository.dart @@ -0,0 +1,17 @@ +// 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. + +import '../../../domain/models/itinerary_config/itinerary_config.dart'; +import '../../../utils/result.dart'; + +/// Data source for the current [ItineraryConfig] +abstract class ItineraryConfigRepository { + /// Get current [ItineraryConfig], may be empty if no configuration started. + /// Method is async to support writing to database, file, etc. + Future> getItineraryConfig(); + + /// Sets [ItineraryConfig], overrides the previous one stored. + /// Returns Result.Ok if set is successful. + Future> setItineraryConfig(ItineraryConfig itineraryConfig); +} diff --git a/compass_app/app/lib/data/repositories/itinerary_config/itinerary_config_repository_memory.dart b/compass_app/app/lib/data/repositories/itinerary_config/itinerary_config_repository_memory.dart new file mode 100644 index 0000000..c819c7f --- /dev/null +++ b/compass_app/app/lib/data/repositories/itinerary_config/itinerary_config_repository_memory.dart @@ -0,0 +1,27 @@ +// 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. + +import 'dart:async'; + +import '../../../domain/models/itinerary_config/itinerary_config.dart'; +import '../../../utils/result.dart'; +import 'itinerary_config_repository.dart'; + +/// In-memory implementation of [ItineraryConfigRepository]. +class ItineraryConfigRepositoryMemory implements ItineraryConfigRepository { + ItineraryConfig? _itineraryConfig; + + @override + Future> getItineraryConfig() async { + return Result.ok(_itineraryConfig ?? const ItineraryConfig()); + } + + @override + Future> setItineraryConfig( + ItineraryConfig itineraryConfig, + ) async { + _itineraryConfig = itineraryConfig; + return const Result.ok(true); + } +} diff --git a/compass_app/app/lib/data/repositories/user/user_repository.dart b/compass_app/app/lib/data/repositories/user/user_repository.dart new file mode 100644 index 0000000..142060c --- /dev/null +++ b/compass_app/app/lib/data/repositories/user/user_repository.dart @@ -0,0 +1,12 @@ +// 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. + +import '../../../domain/models/user/user.dart'; +import '../../../utils/result.dart'; + +/// Data source for user related data +abstract class UserRepository { + /// Get current user + Future> getUser(); +} diff --git a/compass_app/app/lib/data/repositories/user/user_repository_local.dart b/compass_app/app/lib/data/repositories/user/user_repository_local.dart new file mode 100644 index 0000000..0137d92 --- /dev/null +++ b/compass_app/app/lib/data/repositories/user/user_repository_local.dart @@ -0,0 +1,20 @@ +// 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. + +import '../../../domain/models/user/user.dart'; +import '../../../utils/result.dart'; +import '../../services/local/local_data_service.dart'; +import 'user_repository.dart'; + +class UserRepositoryLocal implements UserRepository { + UserRepositoryLocal({required LocalDataService localDataService}) + : _localDataService = localDataService; + + final LocalDataService _localDataService; + + @override + Future> getUser() async { + return Result.ok(_localDataService.getUser()); + } +} diff --git a/compass_app/app/lib/data/repositories/user/user_repository_remote.dart b/compass_app/app/lib/data/repositories/user/user_repository_remote.dart new file mode 100644 index 0000000..bdade1f --- /dev/null +++ b/compass_app/app/lib/data/repositories/user/user_repository_remote.dart @@ -0,0 +1,37 @@ +// 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. + +import '../../../domain/models/user/user.dart'; +import '../../../utils/result.dart'; +import '../../services/api/api_client.dart'; +import '../../services/api/model/user/user_api_model.dart'; +import 'user_repository.dart'; + +class UserRepositoryRemote implements UserRepository { + UserRepositoryRemote({required ApiClient apiClient}) : _apiClient = apiClient; + + final ApiClient _apiClient; + + User? _cachedData; + + @override + Future> getUser() async { + if (_cachedData != null) { + return Future.value(Result.ok(_cachedData!)); + } + + final result = await _apiClient.getUser(); + switch (result) { + case Ok(): + final user = User( + name: result.value.name, + picture: result.value.picture, + ); + _cachedData = user; + return Result.ok(user); + case Error(): + return Result.error(result.error); + } + } +} diff --git a/compass_app/app/lib/data/services/api/api_client.dart b/compass_app/app/lib/data/services/api/api_client.dart new file mode 100644 index 0000000..bb4934f --- /dev/null +++ b/compass_app/app/lib/data/services/api/api_client.dart @@ -0,0 +1,214 @@ +// 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. + +import 'dart:convert'; +import 'dart:io'; + +import '../../../domain/models/activity/activity.dart'; +import '../../../domain/models/continent/continent.dart'; +import '../../../domain/models/destination/destination.dart'; +import '../../../utils/result.dart'; +import 'model/booking/booking_api_model.dart'; +import 'model/user/user_api_model.dart'; + +/// Adds the `Authentication` header to a header configuration. +typedef AuthHeaderProvider = String? Function(); + +class ApiClient { + ApiClient({String? host, int? port, HttpClient Function()? clientFactory}) + : _host = host ?? 'localhost', + _port = port ?? 8080, + _clientFactory = clientFactory ?? HttpClient.new; + + final String _host; + final int _port; + final HttpClient Function() _clientFactory; + + AuthHeaderProvider? _authHeaderProvider; + + set authHeaderProvider(AuthHeaderProvider authHeaderProvider) { + _authHeaderProvider = authHeaderProvider; + } + + Future _authHeader(HttpHeaders headers) async { + final header = _authHeaderProvider?.call(); + if (header != null) { + headers.add(HttpHeaders.authorizationHeader, header); + } + } + + Future>> getContinents() async { + final client = _clientFactory(); + try { + final request = await client.get(_host, _port, '/continent'); + await _authHeader(request.headers); + final response = await request.close(); + if (response.statusCode == 200) { + final stringData = await response.transform(utf8.decoder).join(); + final json = jsonDecode(stringData) as List; + return Result.ok( + json.map((element) => Continent.fromJson(element)).toList(), + ); + } else { + return const Result.error(HttpException("Invalid response")); + } + } on Exception catch (error) { + return Result.error(error); + } finally { + client.close(); + } + } + + Future>> getDestinations() async { + final client = _clientFactory(); + try { + final request = await client.get(_host, _port, '/destination'); + await _authHeader(request.headers); + final response = await request.close(); + if (response.statusCode == 200) { + final stringData = await response.transform(utf8.decoder).join(); + final json = jsonDecode(stringData) as List; + return Result.ok( + json.map((element) => Destination.fromJson(element)).toList(), + ); + } else { + return const Result.error(HttpException("Invalid response")); + } + } on Exception catch (error) { + return Result.error(error); + } finally { + client.close(); + } + } + + Future>> getActivityByDestination(String ref) async { + final client = _clientFactory(); + try { + final request = await client.get( + _host, + _port, + '/destination/$ref/activity', + ); + await _authHeader(request.headers); + final response = await request.close(); + if (response.statusCode == 200) { + final stringData = await response.transform(utf8.decoder).join(); + final json = jsonDecode(stringData) as List; + final activities = json + .map((element) => Activity.fromJson(element)) + .toList(); + return Result.ok(activities); + } else { + return const Result.error(HttpException("Invalid response")); + } + } on Exception catch (error) { + return Result.error(error); + } finally { + client.close(); + } + } + + Future>> getBookings() async { + final client = _clientFactory(); + try { + final request = await client.get(_host, _port, '/booking'); + await _authHeader(request.headers); + final response = await request.close(); + if (response.statusCode == 200) { + final stringData = await response.transform(utf8.decoder).join(); + final json = jsonDecode(stringData) as List; + final bookings = json + .map((element) => BookingApiModel.fromJson(element)) + .toList(); + return Result.ok(bookings); + } else { + return const Result.error(HttpException("Invalid response")); + } + } on Exception catch (error) { + return Result.error(error); + } finally { + client.close(); + } + } + + Future> getBooking(int id) async { + final client = _clientFactory(); + try { + final request = await client.get(_host, _port, '/booking/$id'); + await _authHeader(request.headers); + final response = await request.close(); + if (response.statusCode == 200) { + final stringData = await response.transform(utf8.decoder).join(); + final booking = BookingApiModel.fromJson(jsonDecode(stringData)); + return Result.ok(booking); + } else { + return const Result.error(HttpException("Invalid response")); + } + } on Exception catch (error) { + return Result.error(error); + } finally { + client.close(); + } + } + + Future> postBooking(BookingApiModel booking) async { + final client = _clientFactory(); + try { + final request = await client.post(_host, _port, '/booking'); + await _authHeader(request.headers); + request.write(jsonEncode(booking)); + final response = await request.close(); + if (response.statusCode == 201) { + final stringData = await response.transform(utf8.decoder).join(); + final booking = BookingApiModel.fromJson(jsonDecode(stringData)); + return Result.ok(booking); + } else { + return const Result.error(HttpException("Invalid response")); + } + } on Exception catch (error) { + return Result.error(error); + } finally { + client.close(); + } + } + + Future> getUser() async { + final client = _clientFactory(); + try { + final request = await client.get(_host, _port, '/user'); + await _authHeader(request.headers); + final response = await request.close(); + if (response.statusCode == 200) { + final stringData = await response.transform(utf8.decoder).join(); + final user = UserApiModel.fromJson(jsonDecode(stringData)); + return Result.ok(user); + } else { + return const Result.error(HttpException("Invalid response")); + } + } on Exception catch (error) { + return Result.error(error); + } finally { + client.close(); + } + } + + Future> deleteBooking(int id) async { + final client = _clientFactory(); + try { + final request = await client.delete(_host, _port, '/booking/$id'); + await _authHeader(request.headers); + final response = await request.close(); + // Response 204 "No Content", delete was successful + if (response.statusCode == 204) { + return const Result.ok(null); + } else { + return const Result.error(HttpException("Invalid response")); + } + } on Exception catch (error) { + return Result.error(error); + } finally { + client.close(); + } + } +} diff --git a/compass_app/app/lib/data/services/api/auth_api_client.dart b/compass_app/app/lib/data/services/api/auth_api_client.dart new file mode 100644 index 0000000..a32ef59 --- /dev/null +++ b/compass_app/app/lib/data/services/api/auth_api_client.dart @@ -0,0 +1,40 @@ +// 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. + +import 'dart:convert'; +import 'dart:io'; + +import '../../../utils/result.dart'; +import 'model/login_request/login_request.dart'; +import 'model/login_response/login_response.dart'; + +class AuthApiClient { + AuthApiClient({String? host, int? port, HttpClient Function()? clientFactory}) + : _host = host ?? 'localhost', + _port = port ?? 8080, + _clientFactory = clientFactory ?? HttpClient.new; + + final String _host; + final int _port; + final HttpClient Function() _clientFactory; + + Future> login(LoginRequest loginRequest) async { + final client = _clientFactory(); + try { + final request = await client.post(_host, _port, '/login'); + request.write(jsonEncode(loginRequest)); + final response = await request.close(); + if (response.statusCode == 200) { + final stringData = await response.transform(utf8.decoder).join(); + return Result.ok(LoginResponse.fromJson(jsonDecode(stringData))); + } else { + return const Result.error(HttpException("Login error")); + } + } on Exception catch (error) { + return Result.error(error); + } finally { + client.close(); + } + } +} diff --git a/compass_app/app/lib/data/services/api/model/booking/booking_api_model.dart b/compass_app/app/lib/data/services/api/model/booking/booking_api_model.dart new file mode 100644 index 0000000..bbd0ec3 --- /dev/null +++ b/compass_app/app/lib/data/services/api/model/booking/booking_api_model.dart @@ -0,0 +1,35 @@ +// 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. + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'booking_api_model.freezed.dart'; +part 'booking_api_model.g.dart'; + +@freezed +class BookingApiModel with _$BookingApiModel { + const factory BookingApiModel({ + /// Booking ID. Generated when stored in server. + int? id, + + /// Start date of the trip + required DateTime startDate, + + /// End date of the trip + required DateTime endDate, + + /// Booking name + /// Should be "Destination, Continent" + required String name, + + /// Destination of the trip + required String destinationRef, + + /// List of chosen activities + required List activitiesRef, + }) = _BookingApiModel; + + factory BookingApiModel.fromJson(Map json) => + _$BookingApiModelFromJson(json); +} diff --git a/compass_app/app/lib/data/services/api/model/booking/booking_api_model.freezed.dart b/compass_app/app/lib/data/services/api/model/booking/booking_api_model.freezed.dart new file mode 100644 index 0000000..c637206 --- /dev/null +++ b/compass_app/app/lib/data/services/api/model/booking/booking_api_model.freezed.dart @@ -0,0 +1,338 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'booking_api_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +BookingApiModel _$BookingApiModelFromJson(Map json) { + return _BookingApiModel.fromJson(json); +} + +/// @nodoc +mixin _$BookingApiModel { + /// Booking ID. Generated when stored in server. + int? get id => throw _privateConstructorUsedError; + + /// Start date of the trip + DateTime get startDate => throw _privateConstructorUsedError; + + /// End date of the trip + DateTime get endDate => throw _privateConstructorUsedError; + + /// Booking name + /// Should be "Destination, Continent" + String get name => throw _privateConstructorUsedError; + + /// Destination of the trip + String get destinationRef => throw _privateConstructorUsedError; + + /// List of chosen activities + List get activitiesRef => throw _privateConstructorUsedError; + + /// Serializes this BookingApiModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of BookingApiModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $BookingApiModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $BookingApiModelCopyWith<$Res> { + factory $BookingApiModelCopyWith( + BookingApiModel value, + $Res Function(BookingApiModel) then, + ) = _$BookingApiModelCopyWithImpl<$Res, BookingApiModel>; + @useResult + $Res call({ + int? id, + DateTime startDate, + DateTime endDate, + String name, + String destinationRef, + List activitiesRef, + }); +} + +/// @nodoc +class _$BookingApiModelCopyWithImpl<$Res, $Val extends BookingApiModel> + implements $BookingApiModelCopyWith<$Res> { + _$BookingApiModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of BookingApiModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? startDate = null, + Object? endDate = null, + Object? name = null, + Object? destinationRef = null, + Object? activitiesRef = null, + }) { + return _then( + _value.copyWith( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int?, + startDate: null == startDate + ? _value.startDate + : startDate // ignore: cast_nullable_to_non_nullable + as DateTime, + endDate: null == endDate + ? _value.endDate + : endDate // ignore: cast_nullable_to_non_nullable + as DateTime, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + destinationRef: null == destinationRef + ? _value.destinationRef + : destinationRef // ignore: cast_nullable_to_non_nullable + as String, + activitiesRef: null == activitiesRef + ? _value.activitiesRef + : activitiesRef // ignore: cast_nullable_to_non_nullable + as List, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$BookingApiModelImplCopyWith<$Res> + implements $BookingApiModelCopyWith<$Res> { + factory _$$BookingApiModelImplCopyWith( + _$BookingApiModelImpl value, + $Res Function(_$BookingApiModelImpl) then, + ) = __$$BookingApiModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + int? id, + DateTime startDate, + DateTime endDate, + String name, + String destinationRef, + List activitiesRef, + }); +} + +/// @nodoc +class __$$BookingApiModelImplCopyWithImpl<$Res> + extends _$BookingApiModelCopyWithImpl<$Res, _$BookingApiModelImpl> + implements _$$BookingApiModelImplCopyWith<$Res> { + __$$BookingApiModelImplCopyWithImpl( + _$BookingApiModelImpl _value, + $Res Function(_$BookingApiModelImpl) _then, + ) : super(_value, _then); + + /// Create a copy of BookingApiModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? startDate = null, + Object? endDate = null, + Object? name = null, + Object? destinationRef = null, + Object? activitiesRef = null, + }) { + return _then( + _$BookingApiModelImpl( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int?, + startDate: null == startDate + ? _value.startDate + : startDate // ignore: cast_nullable_to_non_nullable + as DateTime, + endDate: null == endDate + ? _value.endDate + : endDate // ignore: cast_nullable_to_non_nullable + as DateTime, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + destinationRef: null == destinationRef + ? _value.destinationRef + : destinationRef // ignore: cast_nullable_to_non_nullable + as String, + activitiesRef: null == activitiesRef + ? _value._activitiesRef + : activitiesRef // ignore: cast_nullable_to_non_nullable + as List, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$BookingApiModelImpl implements _BookingApiModel { + const _$BookingApiModelImpl({ + this.id, + required this.startDate, + required this.endDate, + required this.name, + required this.destinationRef, + required final List activitiesRef, + }) : _activitiesRef = activitiesRef; + + factory _$BookingApiModelImpl.fromJson(Map json) => + _$$BookingApiModelImplFromJson(json); + + /// Booking ID. Generated when stored in server. + @override + final int? id; + + /// Start date of the trip + @override + final DateTime startDate; + + /// End date of the trip + @override + final DateTime endDate; + + /// Booking name + /// Should be "Destination, Continent" + @override + final String name; + + /// Destination of the trip + @override + final String destinationRef; + + /// List of chosen activities + final List _activitiesRef; + + /// List of chosen activities + @override + List get activitiesRef { + if (_activitiesRef is EqualUnmodifiableListView) return _activitiesRef; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_activitiesRef); + } + + @override + String toString() { + return 'BookingApiModel(id: $id, startDate: $startDate, endDate: $endDate, name: $name, destinationRef: $destinationRef, activitiesRef: $activitiesRef)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BookingApiModelImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.startDate, startDate) || + other.startDate == startDate) && + (identical(other.endDate, endDate) || other.endDate == endDate) && + (identical(other.name, name) || other.name == name) && + (identical(other.destinationRef, destinationRef) || + other.destinationRef == destinationRef) && + const DeepCollectionEquality().equals( + other._activitiesRef, + _activitiesRef, + )); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + startDate, + endDate, + name, + destinationRef, + const DeepCollectionEquality().hash(_activitiesRef), + ); + + /// Create a copy of BookingApiModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$BookingApiModelImplCopyWith<_$BookingApiModelImpl> get copyWith => + __$$BookingApiModelImplCopyWithImpl<_$BookingApiModelImpl>( + this, + _$identity, + ); + + @override + Map toJson() { + return _$$BookingApiModelImplToJson(this); + } +} + +abstract class _BookingApiModel implements BookingApiModel { + const factory _BookingApiModel({ + final int? id, + required final DateTime startDate, + required final DateTime endDate, + required final String name, + required final String destinationRef, + required final List activitiesRef, + }) = _$BookingApiModelImpl; + + factory _BookingApiModel.fromJson(Map json) = + _$BookingApiModelImpl.fromJson; + + /// Booking ID. Generated when stored in server. + @override + int? get id; + + /// Start date of the trip + @override + DateTime get startDate; + + /// End date of the trip + @override + DateTime get endDate; + + /// Booking name + /// Should be "Destination, Continent" + @override + String get name; + + /// Destination of the trip + @override + String get destinationRef; + + /// List of chosen activities + @override + List get activitiesRef; + + /// Create a copy of BookingApiModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$BookingApiModelImplCopyWith<_$BookingApiModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/compass_app/app/lib/data/services/api/model/booking/booking_api_model.g.dart b/compass_app/app/lib/data/services/api/model/booking/booking_api_model.g.dart new file mode 100644 index 0000000..c75b534 --- /dev/null +++ b/compass_app/app/lib/data/services/api/model/booking/booking_api_model.g.dart @@ -0,0 +1,31 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'booking_api_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$BookingApiModelImpl _$$BookingApiModelImplFromJson( + Map json, +) => _$BookingApiModelImpl( + id: (json['id'] as num?)?.toInt(), + startDate: DateTime.parse(json['startDate'] as String), + endDate: DateTime.parse(json['endDate'] as String), + name: json['name'] as String, + destinationRef: json['destinationRef'] as String, + activitiesRef: (json['activitiesRef'] as List) + .map((e) => e as String) + .toList(), +); + +Map _$$BookingApiModelImplToJson( + _$BookingApiModelImpl instance, +) => { + 'id': instance.id, + 'startDate': instance.startDate.toIso8601String(), + 'endDate': instance.endDate.toIso8601String(), + 'name': instance.name, + 'destinationRef': instance.destinationRef, + 'activitiesRef': instance.activitiesRef, +}; diff --git a/compass_app/app/lib/data/services/api/model/login_request/login_request.dart b/compass_app/app/lib/data/services/api/model/login_request/login_request.dart new file mode 100644 index 0000000..e30ec7c --- /dev/null +++ b/compass_app/app/lib/data/services/api/model/login_request/login_request.dart @@ -0,0 +1,24 @@ +// 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. + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'login_request.freezed.dart'; + +part 'login_request.g.dart'; + +/// Simple data class to hold login request data. +@freezed +class LoginRequest with _$LoginRequest { + const factory LoginRequest({ + /// Email address. + required String email, + + /// Plain text password. + required String password, + }) = _LoginRequest; + + factory LoginRequest.fromJson(Map json) => + _$LoginRequestFromJson(json); +} diff --git a/compass_app/app/lib/data/services/api/model/login_request/login_request.freezed.dart b/compass_app/app/lib/data/services/api/model/login_request/login_request.freezed.dart new file mode 100644 index 0000000..db4394e --- /dev/null +++ b/compass_app/app/lib/data/services/api/model/login_request/login_request.freezed.dart @@ -0,0 +1,194 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'login_request.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +LoginRequest _$LoginRequestFromJson(Map json) { + return _LoginRequest.fromJson(json); +} + +/// @nodoc +mixin _$LoginRequest { + /// Email address. + String get email => throw _privateConstructorUsedError; + + /// Plain text password. + String get password => throw _privateConstructorUsedError; + + /// Serializes this LoginRequest to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of LoginRequest + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $LoginRequestCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LoginRequestCopyWith<$Res> { + factory $LoginRequestCopyWith( + LoginRequest value, + $Res Function(LoginRequest) then, + ) = _$LoginRequestCopyWithImpl<$Res, LoginRequest>; + @useResult + $Res call({String email, String password}); +} + +/// @nodoc +class _$LoginRequestCopyWithImpl<$Res, $Val extends LoginRequest> + implements $LoginRequestCopyWith<$Res> { + _$LoginRequestCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of LoginRequest + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? email = null, Object? password = null}) { + return _then( + _value.copyWith( + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + password: null == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$LoginRequestImplCopyWith<$Res> + implements $LoginRequestCopyWith<$Res> { + factory _$$LoginRequestImplCopyWith( + _$LoginRequestImpl value, + $Res Function(_$LoginRequestImpl) then, + ) = __$$LoginRequestImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String email, String password}); +} + +/// @nodoc +class __$$LoginRequestImplCopyWithImpl<$Res> + extends _$LoginRequestCopyWithImpl<$Res, _$LoginRequestImpl> + implements _$$LoginRequestImplCopyWith<$Res> { + __$$LoginRequestImplCopyWithImpl( + _$LoginRequestImpl _value, + $Res Function(_$LoginRequestImpl) _then, + ) : super(_value, _then); + + /// Create a copy of LoginRequest + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? email = null, Object? password = null}) { + return _then( + _$LoginRequestImpl( + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + password: null == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$LoginRequestImpl implements _LoginRequest { + const _$LoginRequestImpl({required this.email, required this.password}); + + factory _$LoginRequestImpl.fromJson(Map json) => + _$$LoginRequestImplFromJson(json); + + /// Email address. + @override + final String email; + + /// Plain text password. + @override + final String password; + + @override + String toString() { + return 'LoginRequest(email: $email, password: $password)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoginRequestImpl && + (identical(other.email, email) || other.email == email) && + (identical(other.password, password) || + other.password == password)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, email, password); + + /// Create a copy of LoginRequest + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LoginRequestImplCopyWith<_$LoginRequestImpl> get copyWith => + __$$LoginRequestImplCopyWithImpl<_$LoginRequestImpl>(this, _$identity); + + @override + Map toJson() { + return _$$LoginRequestImplToJson(this); + } +} + +abstract class _LoginRequest implements LoginRequest { + const factory _LoginRequest({ + required final String email, + required final String password, + }) = _$LoginRequestImpl; + + factory _LoginRequest.fromJson(Map json) = + _$LoginRequestImpl.fromJson; + + /// Email address. + @override + String get email; + + /// Plain text password. + @override + String get password; + + /// Create a copy of LoginRequest + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LoginRequestImplCopyWith<_$LoginRequestImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/compass_app/app/lib/data/services/api/model/login_request/login_request.g.dart b/compass_app/app/lib/data/services/api/model/login_request/login_request.g.dart new file mode 100644 index 0000000..0b7e89c --- /dev/null +++ b/compass_app/app/lib/data/services/api/model/login_request/login_request.g.dart @@ -0,0 +1,16 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'login_request.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$LoginRequestImpl _$$LoginRequestImplFromJson(Map json) => + _$LoginRequestImpl( + email: json['email'] as String, + password: json['password'] as String, + ); + +Map _$$LoginRequestImplToJson(_$LoginRequestImpl instance) => + {'email': instance.email, 'password': instance.password}; diff --git a/compass_app/app/lib/data/services/api/model/login_response/login_response.dart b/compass_app/app/lib/data/services/api/model/login_response/login_response.dart new file mode 100644 index 0000000..bc17d6a --- /dev/null +++ b/compass_app/app/lib/data/services/api/model/login_response/login_response.dart @@ -0,0 +1,24 @@ +// 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. + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'login_response.freezed.dart'; + +part 'login_response.g.dart'; + +/// LoginResponse model. +@freezed +class LoginResponse with _$LoginResponse { + const factory LoginResponse({ + /// The token to be used for authentication. + required String token, + + /// The user id. + required String userId, + }) = _LoginResponse; + + factory LoginResponse.fromJson(Map json) => + _$LoginResponseFromJson(json); +} diff --git a/compass_app/app/lib/data/services/api/model/login_response/login_response.freezed.dart b/compass_app/app/lib/data/services/api/model/login_response/login_response.freezed.dart new file mode 100644 index 0000000..dfaf762 --- /dev/null +++ b/compass_app/app/lib/data/services/api/model/login_response/login_response.freezed.dart @@ -0,0 +1,193 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'login_response.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +LoginResponse _$LoginResponseFromJson(Map json) { + return _LoginResponse.fromJson(json); +} + +/// @nodoc +mixin _$LoginResponse { + /// The token to be used for authentication. + String get token => throw _privateConstructorUsedError; + + /// The user id. + String get userId => throw _privateConstructorUsedError; + + /// Serializes this LoginResponse to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of LoginResponse + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $LoginResponseCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LoginResponseCopyWith<$Res> { + factory $LoginResponseCopyWith( + LoginResponse value, + $Res Function(LoginResponse) then, + ) = _$LoginResponseCopyWithImpl<$Res, LoginResponse>; + @useResult + $Res call({String token, String userId}); +} + +/// @nodoc +class _$LoginResponseCopyWithImpl<$Res, $Val extends LoginResponse> + implements $LoginResponseCopyWith<$Res> { + _$LoginResponseCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of LoginResponse + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? token = null, Object? userId = null}) { + return _then( + _value.copyWith( + token: null == token + ? _value.token + : token // ignore: cast_nullable_to_non_nullable + as String, + userId: null == userId + ? _value.userId + : userId // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$LoginResponseImplCopyWith<$Res> + implements $LoginResponseCopyWith<$Res> { + factory _$$LoginResponseImplCopyWith( + _$LoginResponseImpl value, + $Res Function(_$LoginResponseImpl) then, + ) = __$$LoginResponseImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String token, String userId}); +} + +/// @nodoc +class __$$LoginResponseImplCopyWithImpl<$Res> + extends _$LoginResponseCopyWithImpl<$Res, _$LoginResponseImpl> + implements _$$LoginResponseImplCopyWith<$Res> { + __$$LoginResponseImplCopyWithImpl( + _$LoginResponseImpl _value, + $Res Function(_$LoginResponseImpl) _then, + ) : super(_value, _then); + + /// Create a copy of LoginResponse + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? token = null, Object? userId = null}) { + return _then( + _$LoginResponseImpl( + token: null == token + ? _value.token + : token // ignore: cast_nullable_to_non_nullable + as String, + userId: null == userId + ? _value.userId + : userId // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$LoginResponseImpl implements _LoginResponse { + const _$LoginResponseImpl({required this.token, required this.userId}); + + factory _$LoginResponseImpl.fromJson(Map json) => + _$$LoginResponseImplFromJson(json); + + /// The token to be used for authentication. + @override + final String token; + + /// The user id. + @override + final String userId; + + @override + String toString() { + return 'LoginResponse(token: $token, userId: $userId)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoginResponseImpl && + (identical(other.token, token) || other.token == token) && + (identical(other.userId, userId) || other.userId == userId)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, token, userId); + + /// Create a copy of LoginResponse + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LoginResponseImplCopyWith<_$LoginResponseImpl> get copyWith => + __$$LoginResponseImplCopyWithImpl<_$LoginResponseImpl>(this, _$identity); + + @override + Map toJson() { + return _$$LoginResponseImplToJson(this); + } +} + +abstract class _LoginResponse implements LoginResponse { + const factory _LoginResponse({ + required final String token, + required final String userId, + }) = _$LoginResponseImpl; + + factory _LoginResponse.fromJson(Map json) = + _$LoginResponseImpl.fromJson; + + /// The token to be used for authentication. + @override + String get token; + + /// The user id. + @override + String get userId; + + /// Create a copy of LoginResponse + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LoginResponseImplCopyWith<_$LoginResponseImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/compass_app/app/lib/data/services/api/model/login_response/login_response.g.dart b/compass_app/app/lib/data/services/api/model/login_response/login_response.g.dart new file mode 100644 index 0000000..c044a4d --- /dev/null +++ b/compass_app/app/lib/data/services/api/model/login_response/login_response.g.dart @@ -0,0 +1,16 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'login_response.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$LoginResponseImpl _$$LoginResponseImplFromJson(Map json) => + _$LoginResponseImpl( + token: json['token'] as String, + userId: json['userId'] as String, + ); + +Map _$$LoginResponseImplToJson(_$LoginResponseImpl instance) => + {'token': instance.token, 'userId': instance.userId}; diff --git a/compass_app/app/lib/data/services/api/model/user/user_api_model.dart b/compass_app/app/lib/data/services/api/model/user/user_api_model.dart new file mode 100644 index 0000000..09629cd --- /dev/null +++ b/compass_app/app/lib/data/services/api/model/user/user_api_model.dart @@ -0,0 +1,28 @@ +// 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. + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'user_api_model.freezed.dart'; +part 'user_api_model.g.dart'; + +@freezed +abstract class UserApiModel with _$UserApiModel { + const factory UserApiModel({ + /// The user's ID. + required String id, + + /// The user's name. + required String name, + + /// The user's email. + required String email, + + /// The user's picture URL. + required String picture, + }) = _UserApiModel; + + factory UserApiModel.fromJson(Map json) => + _$UserApiModelFromJson(json); +} diff --git a/compass_app/app/lib/data/services/api/model/user/user_api_model.freezed.dart b/compass_app/app/lib/data/services/api/model/user/user_api_model.freezed.dart new file mode 100644 index 0000000..2d8ad10 --- /dev/null +++ b/compass_app/app/lib/data/services/api/model/user/user_api_model.freezed.dart @@ -0,0 +1,250 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'user_api_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +UserApiModel _$UserApiModelFromJson(Map json) { + return _UserApiModel.fromJson(json); +} + +/// @nodoc +mixin _$UserApiModel { + /// The user's ID. + String get id => throw _privateConstructorUsedError; + + /// The user's name. + String get name => throw _privateConstructorUsedError; + + /// The user's email. + String get email => throw _privateConstructorUsedError; + + /// The user's picture URL. + String get picture => throw _privateConstructorUsedError; + + /// Serializes this UserApiModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of UserApiModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $UserApiModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $UserApiModelCopyWith<$Res> { + factory $UserApiModelCopyWith( + UserApiModel value, + $Res Function(UserApiModel) then, + ) = _$UserApiModelCopyWithImpl<$Res, UserApiModel>; + @useResult + $Res call({String id, String name, String email, String picture}); +} + +/// @nodoc +class _$UserApiModelCopyWithImpl<$Res, $Val extends UserApiModel> + implements $UserApiModelCopyWith<$Res> { + _$UserApiModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of UserApiModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? email = null, + Object? picture = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + picture: null == picture + ? _value.picture + : picture // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$UserApiModelImplCopyWith<$Res> + implements $UserApiModelCopyWith<$Res> { + factory _$$UserApiModelImplCopyWith( + _$UserApiModelImpl value, + $Res Function(_$UserApiModelImpl) then, + ) = __$$UserApiModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String id, String name, String email, String picture}); +} + +/// @nodoc +class __$$UserApiModelImplCopyWithImpl<$Res> + extends _$UserApiModelCopyWithImpl<$Res, _$UserApiModelImpl> + implements _$$UserApiModelImplCopyWith<$Res> { + __$$UserApiModelImplCopyWithImpl( + _$UserApiModelImpl _value, + $Res Function(_$UserApiModelImpl) _then, + ) : super(_value, _then); + + /// Create a copy of UserApiModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? email = null, + Object? picture = null, + }) { + return _then( + _$UserApiModelImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + picture: null == picture + ? _value.picture + : picture // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$UserApiModelImpl implements _UserApiModel { + const _$UserApiModelImpl({ + required this.id, + required this.name, + required this.email, + required this.picture, + }); + + factory _$UserApiModelImpl.fromJson(Map json) => + _$$UserApiModelImplFromJson(json); + + /// The user's ID. + @override + final String id; + + /// The user's name. + @override + final String name; + + /// The user's email. + @override + final String email; + + /// The user's picture URL. + @override + final String picture; + + @override + String toString() { + return 'UserApiModel(id: $id, name: $name, email: $email, picture: $picture)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UserApiModelImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.email, email) || other.email == email) && + (identical(other.picture, picture) || other.picture == picture)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, id, name, email, picture); + + /// Create a copy of UserApiModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$UserApiModelImplCopyWith<_$UserApiModelImpl> get copyWith => + __$$UserApiModelImplCopyWithImpl<_$UserApiModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$UserApiModelImplToJson(this); + } +} + +abstract class _UserApiModel implements UserApiModel { + const factory _UserApiModel({ + required final String id, + required final String name, + required final String email, + required final String picture, + }) = _$UserApiModelImpl; + + factory _UserApiModel.fromJson(Map json) = + _$UserApiModelImpl.fromJson; + + /// The user's ID. + @override + String get id; + + /// The user's name. + @override + String get name; + + /// The user's email. + @override + String get email; + + /// The user's picture URL. + @override + String get picture; + + /// Create a copy of UserApiModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$UserApiModelImplCopyWith<_$UserApiModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/compass_app/app/lib/data/services/api/model/user/user_api_model.g.dart b/compass_app/app/lib/data/services/api/model/user/user_api_model.g.dart new file mode 100644 index 0000000..b40cb81 --- /dev/null +++ b/compass_app/app/lib/data/services/api/model/user/user_api_model.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user_api_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$UserApiModelImpl _$$UserApiModelImplFromJson(Map json) => + _$UserApiModelImpl( + id: json['id'] as String, + name: json['name'] as String, + email: json['email'] as String, + picture: json['picture'] as String, + ); + +Map _$$UserApiModelImplToJson(_$UserApiModelImpl instance) => + { + 'id': instance.id, + 'name': instance.name, + 'email': instance.email, + 'picture': instance.picture, + }; diff --git a/compass_app/app/lib/data/services/local/local_data_service.dart b/compass_app/app/lib/data/services/local/local_data_service.dart new file mode 100644 index 0000000..bf19cf8 --- /dev/null +++ b/compass_app/app/lib/data/services/local/local_data_service.dart @@ -0,0 +1,71 @@ +// 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. + +import 'dart:convert'; + +import 'package:flutter/services.dart'; + +import '../../../config/assets.dart'; +import '../../../domain/models/activity/activity.dart'; +import '../../../domain/models/continent/continent.dart'; +import '../../../domain/models/destination/destination.dart'; +import '../../../domain/models/user/user.dart'; + +class LocalDataService { + List getContinents() { + return [ + const Continent( + name: 'Europe', + imageUrl: 'https://rstr.in/google/tripedia/TmR12QdlVTT', + ), + const Continent( + name: 'Asia', + imageUrl: 'https://rstr.in/google/tripedia/VJ8BXlQg8O1', + ), + const Continent( + name: 'South America', + imageUrl: 'https://rstr.in/google/tripedia/flm_-o1aI8e', + ), + const Continent( + name: 'Africa', + imageUrl: 'https://rstr.in/google/tripedia/-nzi8yFOBpF', + ), + const Continent( + name: 'North America', + imageUrl: 'https://rstr.in/google/tripedia/jlbgFDrSUVE', + ), + const Continent( + name: 'Oceania', + imageUrl: 'https://rstr.in/google/tripedia/vxyrDE-fZVL', + ), + const Continent( + name: 'Australia', + imageUrl: 'https://rstr.in/google/tripedia/z6vy6HeRyvZ', + ), + ]; + } + + Future> getActivities() async { + final json = await _loadStringAsset(Assets.activities); + return json.map(Activity.fromJson).toList(); + } + + Future> getDestinations() async { + final json = await _loadStringAsset(Assets.destinations); + return json.map(Destination.fromJson).toList(); + } + + Future>> _loadStringAsset(String asset) async { + final localData = await rootBundle.loadString(asset); + return (jsonDecode(localData) as List).cast>(); + } + + User getUser() { + return const User( + name: 'Sofie', + // For demo purposes we use a local asset + picture: 'assets/user.jpg', + ); + } +} diff --git a/compass_app/app/lib/data/services/shared_preferences_service.dart b/compass_app/app/lib/data/services/shared_preferences_service.dart new file mode 100644 index 0000000..09c3439 --- /dev/null +++ b/compass_app/app/lib/data/services/shared_preferences_service.dart @@ -0,0 +1,47 @@ +// 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. + +import 'package:logging/logging.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../utils/result.dart'; + +class SharedPreferencesService { + static const _tokenKey = 'TOKEN'; + final _log = Logger('SharedPreferencesService'); + + Future> fetchToken() async { + try { + final sharedPreferences = SharedPreferencesAsync(); + + _log.finer('Got token from SharedPreferences'); + + return Result.ok(await sharedPreferences.getString(_tokenKey)); + } on Exception catch (e) { + _log.warning('Failed to get token', e); + return Result.error(e); + } + } + + Future> saveToken(String? token) async { + try { + final sharedPreferences = SharedPreferencesAsync(); + + if (token == null) { + _log.finer('Removed token'); + + await sharedPreferences.remove(_tokenKey); + } else { + _log.finer('Replaced token'); + + await sharedPreferences.setString(_tokenKey, token); + } + + return const Result.ok(null); + } on Exception catch (e) { + _log.warning('Failed to set token', e); + return Result.error(e); + } + } +} diff --git a/compass_app/app/lib/domain/models/activity/activity.dart b/compass_app/app/lib/domain/models/activity/activity.dart new file mode 100644 index 0000000..654861a --- /dev/null +++ b/compass_app/app/lib/domain/models/activity/activity.dart @@ -0,0 +1,50 @@ +// 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. + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'activity.freezed.dart'; + +part 'activity.g.dart'; + +enum TimeOfDay { any, morning, afternoon, evening, night } + +@freezed +class Activity with _$Activity { + const factory Activity({ + /// e.g. 'Glacier Trekking and Ice Climbing' + required String name, + + /// e.g. 'Embark on a thrilling adventure exploring the awe-inspiring glaciers of Alaska. Hike across the icy terrain, marvel at the deep blue crevasses, and even try your hand at ice climbing for an unforgettable experience.' + required String description, + + /// e.g. 'Matanuska Glacier or Mendenhall Glacier' + required String locationName, + + /// Duration in days. + /// e.g. 8 + required int duration, + + /// e.g. 'morning' + required TimeOfDay timeOfDay, + + /// e.g. false + required bool familyFriendly, + + /// e.g. 4 + required int price, + + /// e.g. 'alaska' + required String destinationRef, + + /// e.g. 'glacier-trekking-and-ice-climbing' + required String ref, + + /// e.g. 'https://storage.googleapis.com/tripedia-images/activities/alaska_glacier-trekking-and-ice-climbing.jpg' + required String imageUrl, + }) = _Activity; + + factory Activity.fromJson(Map json) => + _$ActivityFromJson(json); +} diff --git a/compass_app/app/lib/domain/models/activity/activity.freezed.dart b/compass_app/app/lib/domain/models/activity/activity.freezed.dart new file mode 100644 index 0000000..0c89f88 --- /dev/null +++ b/compass_app/app/lib/domain/models/activity/activity.freezed.dart @@ -0,0 +1,436 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'activity.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +Activity _$ActivityFromJson(Map json) { + return _Activity.fromJson(json); +} + +/// @nodoc +mixin _$Activity { + /// e.g. 'Glacier Trekking and Ice Climbing' + String get name => throw _privateConstructorUsedError; + + /// e.g. 'Embark on a thrilling adventure exploring the awe-inspiring glaciers of Alaska. Hike across the icy terrain, marvel at the deep blue crevasses, and even try your hand at ice climbing for an unforgettable experience.' + String get description => throw _privateConstructorUsedError; + + /// e.g. 'Matanuska Glacier or Mendenhall Glacier' + String get locationName => throw _privateConstructorUsedError; + + /// Duration in days. + /// e.g. 8 + int get duration => throw _privateConstructorUsedError; + + /// e.g. 'morning' + TimeOfDay get timeOfDay => throw _privateConstructorUsedError; + + /// e.g. false + bool get familyFriendly => throw _privateConstructorUsedError; + + /// e.g. 4 + int get price => throw _privateConstructorUsedError; + + /// e.g. 'alaska' + String get destinationRef => throw _privateConstructorUsedError; + + /// e.g. 'glacier-trekking-and-ice-climbing' + String get ref => throw _privateConstructorUsedError; + + /// e.g. 'https://storage.googleapis.com/tripedia-images/activities/alaska_glacier-trekking-and-ice-climbing.jpg' + String get imageUrl => throw _privateConstructorUsedError; + + /// Serializes this Activity to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of Activity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ActivityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ActivityCopyWith<$Res> { + factory $ActivityCopyWith(Activity value, $Res Function(Activity) then) = + _$ActivityCopyWithImpl<$Res, Activity>; + @useResult + $Res call({ + String name, + String description, + String locationName, + int duration, + TimeOfDay timeOfDay, + bool familyFriendly, + int price, + String destinationRef, + String ref, + String imageUrl, + }); +} + +/// @nodoc +class _$ActivityCopyWithImpl<$Res, $Val extends Activity> + implements $ActivityCopyWith<$Res> { + _$ActivityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Activity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? description = null, + Object? locationName = null, + Object? duration = null, + Object? timeOfDay = null, + Object? familyFriendly = null, + Object? price = null, + Object? destinationRef = null, + Object? ref = null, + Object? imageUrl = null, + }) { + return _then( + _value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + locationName: null == locationName + ? _value.locationName + : locationName // ignore: cast_nullable_to_non_nullable + as String, + duration: null == duration + ? _value.duration + : duration // ignore: cast_nullable_to_non_nullable + as int, + timeOfDay: null == timeOfDay + ? _value.timeOfDay + : timeOfDay // ignore: cast_nullable_to_non_nullable + as TimeOfDay, + familyFriendly: null == familyFriendly + ? _value.familyFriendly + : familyFriendly // ignore: cast_nullable_to_non_nullable + as bool, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as int, + destinationRef: null == destinationRef + ? _value.destinationRef + : destinationRef // ignore: cast_nullable_to_non_nullable + as String, + ref: null == ref + ? _value.ref + : ref // ignore: cast_nullable_to_non_nullable + as String, + imageUrl: null == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ActivityImplCopyWith<$Res> + implements $ActivityCopyWith<$Res> { + factory _$$ActivityImplCopyWith( + _$ActivityImpl value, + $Res Function(_$ActivityImpl) then, + ) = __$$ActivityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String name, + String description, + String locationName, + int duration, + TimeOfDay timeOfDay, + bool familyFriendly, + int price, + String destinationRef, + String ref, + String imageUrl, + }); +} + +/// @nodoc +class __$$ActivityImplCopyWithImpl<$Res> + extends _$ActivityCopyWithImpl<$Res, _$ActivityImpl> + implements _$$ActivityImplCopyWith<$Res> { + __$$ActivityImplCopyWithImpl( + _$ActivityImpl _value, + $Res Function(_$ActivityImpl) _then, + ) : super(_value, _then); + + /// Create a copy of Activity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? description = null, + Object? locationName = null, + Object? duration = null, + Object? timeOfDay = null, + Object? familyFriendly = null, + Object? price = null, + Object? destinationRef = null, + Object? ref = null, + Object? imageUrl = null, + }) { + return _then( + _$ActivityImpl( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + locationName: null == locationName + ? _value.locationName + : locationName // ignore: cast_nullable_to_non_nullable + as String, + duration: null == duration + ? _value.duration + : duration // ignore: cast_nullable_to_non_nullable + as int, + timeOfDay: null == timeOfDay + ? _value.timeOfDay + : timeOfDay // ignore: cast_nullable_to_non_nullable + as TimeOfDay, + familyFriendly: null == familyFriendly + ? _value.familyFriendly + : familyFriendly // ignore: cast_nullable_to_non_nullable + as bool, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as int, + destinationRef: null == destinationRef + ? _value.destinationRef + : destinationRef // ignore: cast_nullable_to_non_nullable + as String, + ref: null == ref + ? _value.ref + : ref // ignore: cast_nullable_to_non_nullable + as String, + imageUrl: null == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$ActivityImpl implements _Activity { + const _$ActivityImpl({ + required this.name, + required this.description, + required this.locationName, + required this.duration, + required this.timeOfDay, + required this.familyFriendly, + required this.price, + required this.destinationRef, + required this.ref, + required this.imageUrl, + }); + + factory _$ActivityImpl.fromJson(Map json) => + _$$ActivityImplFromJson(json); + + /// e.g. 'Glacier Trekking and Ice Climbing' + @override + final String name; + + /// e.g. 'Embark on a thrilling adventure exploring the awe-inspiring glaciers of Alaska. Hike across the icy terrain, marvel at the deep blue crevasses, and even try your hand at ice climbing for an unforgettable experience.' + @override + final String description; + + /// e.g. 'Matanuska Glacier or Mendenhall Glacier' + @override + final String locationName; + + /// Duration in days. + /// e.g. 8 + @override + final int duration; + + /// e.g. 'morning' + @override + final TimeOfDay timeOfDay; + + /// e.g. false + @override + final bool familyFriendly; + + /// e.g. 4 + @override + final int price; + + /// e.g. 'alaska' + @override + final String destinationRef; + + /// e.g. 'glacier-trekking-and-ice-climbing' + @override + final String ref; + + /// e.g. 'https://storage.googleapis.com/tripedia-images/activities/alaska_glacier-trekking-and-ice-climbing.jpg' + @override + final String imageUrl; + + @override + String toString() { + return 'Activity(name: $name, description: $description, locationName: $locationName, duration: $duration, timeOfDay: $timeOfDay, familyFriendly: $familyFriendly, price: $price, destinationRef: $destinationRef, ref: $ref, imageUrl: $imageUrl)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ActivityImpl && + (identical(other.name, name) || other.name == name) && + (identical(other.description, description) || + other.description == description) && + (identical(other.locationName, locationName) || + other.locationName == locationName) && + (identical(other.duration, duration) || + other.duration == duration) && + (identical(other.timeOfDay, timeOfDay) || + other.timeOfDay == timeOfDay) && + (identical(other.familyFriendly, familyFriendly) || + other.familyFriendly == familyFriendly) && + (identical(other.price, price) || other.price == price) && + (identical(other.destinationRef, destinationRef) || + other.destinationRef == destinationRef) && + (identical(other.ref, ref) || other.ref == ref) && + (identical(other.imageUrl, imageUrl) || + other.imageUrl == imageUrl)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + name, + description, + locationName, + duration, + timeOfDay, + familyFriendly, + price, + destinationRef, + ref, + imageUrl, + ); + + /// Create a copy of Activity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ActivityImplCopyWith<_$ActivityImpl> get copyWith => + __$$ActivityImplCopyWithImpl<_$ActivityImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ActivityImplToJson(this); + } +} + +abstract class _Activity implements Activity { + const factory _Activity({ + required final String name, + required final String description, + required final String locationName, + required final int duration, + required final TimeOfDay timeOfDay, + required final bool familyFriendly, + required final int price, + required final String destinationRef, + required final String ref, + required final String imageUrl, + }) = _$ActivityImpl; + + factory _Activity.fromJson(Map json) = + _$ActivityImpl.fromJson; + + /// e.g. 'Glacier Trekking and Ice Climbing' + @override + String get name; + + /// e.g. 'Embark on a thrilling adventure exploring the awe-inspiring glaciers of Alaska. Hike across the icy terrain, marvel at the deep blue crevasses, and even try your hand at ice climbing for an unforgettable experience.' + @override + String get description; + + /// e.g. 'Matanuska Glacier or Mendenhall Glacier' + @override + String get locationName; + + /// Duration in days. + /// e.g. 8 + @override + int get duration; + + /// e.g. 'morning' + @override + TimeOfDay get timeOfDay; + + /// e.g. false + @override + bool get familyFriendly; + + /// e.g. 4 + @override + int get price; + + /// e.g. 'alaska' + @override + String get destinationRef; + + /// e.g. 'glacier-trekking-and-ice-climbing' + @override + String get ref; + + /// e.g. 'https://storage.googleapis.com/tripedia-images/activities/alaska_glacier-trekking-and-ice-climbing.jpg' + @override + String get imageUrl; + + /// Create a copy of Activity + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ActivityImplCopyWith<_$ActivityImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/compass_app/app/lib/domain/models/activity/activity.g.dart b/compass_app/app/lib/domain/models/activity/activity.g.dart new file mode 100644 index 0000000..8ad67d6 --- /dev/null +++ b/compass_app/app/lib/domain/models/activity/activity.g.dart @@ -0,0 +1,43 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'activity.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ActivityImpl _$$ActivityImplFromJson(Map json) => + _$ActivityImpl( + name: json['name'] as String, + description: json['description'] as String, + locationName: json['locationName'] as String, + duration: (json['duration'] as num).toInt(), + timeOfDay: $enumDecode(_$TimeOfDayEnumMap, json['timeOfDay']), + familyFriendly: json['familyFriendly'] as bool, + price: (json['price'] as num).toInt(), + destinationRef: json['destinationRef'] as String, + ref: json['ref'] as String, + imageUrl: json['imageUrl'] as String, + ); + +Map _$$ActivityImplToJson(_$ActivityImpl instance) => + { + 'name': instance.name, + 'description': instance.description, + 'locationName': instance.locationName, + 'duration': instance.duration, + 'timeOfDay': _$TimeOfDayEnumMap[instance.timeOfDay]!, + 'familyFriendly': instance.familyFriendly, + 'price': instance.price, + 'destinationRef': instance.destinationRef, + 'ref': instance.ref, + 'imageUrl': instance.imageUrl, + }; + +const _$TimeOfDayEnumMap = { + TimeOfDay.any: 'any', + TimeOfDay.morning: 'morning', + TimeOfDay.afternoon: 'afternoon', + TimeOfDay.evening: 'evening', + TimeOfDay.night: 'night', +}; diff --git a/compass_app/app/lib/domain/models/booking/booking.dart b/compass_app/app/lib/domain/models/booking/booking.dart new file mode 100644 index 0000000..e94f51b --- /dev/null +++ b/compass_app/app/lib/domain/models/booking/booking.dart @@ -0,0 +1,35 @@ +// 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. + +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../activity/activity.dart'; +import '../destination/destination.dart'; + +part 'booking.freezed.dart'; +part 'booking.g.dart'; + +@freezed +class Booking with _$Booking { + const factory Booking({ + /// Optional ID of the booking. + /// May be null if the booking is not yet stored. + int? id, + + /// Start date of the trip + required DateTime startDate, + + /// End date of the trip + required DateTime endDate, + + /// Destination of the trip + required Destination destination, + + /// List of chosen activities + required List activity, + }) = _Booking; + + factory Booking.fromJson(Map json) => + _$BookingFromJson(json); +} diff --git a/compass_app/app/lib/domain/models/booking/booking.freezed.dart b/compass_app/app/lib/domain/models/booking/booking.freezed.dart new file mode 100644 index 0000000..03a1ebc --- /dev/null +++ b/compass_app/app/lib/domain/models/booking/booking.freezed.dart @@ -0,0 +1,315 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'booking.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +Booking _$BookingFromJson(Map json) { + return _Booking.fromJson(json); +} + +/// @nodoc +mixin _$Booking { + /// Optional ID of the booking. + /// May be null if the booking is not yet stored. + int? get id => throw _privateConstructorUsedError; + + /// Start date of the trip + DateTime get startDate => throw _privateConstructorUsedError; + + /// End date of the trip + DateTime get endDate => throw _privateConstructorUsedError; + + /// Destination of the trip + Destination get destination => throw _privateConstructorUsedError; + + /// List of chosen activities + List get activity => throw _privateConstructorUsedError; + + /// Serializes this Booking to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of Booking + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $BookingCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $BookingCopyWith<$Res> { + factory $BookingCopyWith(Booking value, $Res Function(Booking) then) = + _$BookingCopyWithImpl<$Res, Booking>; + @useResult + $Res call({ + int? id, + DateTime startDate, + DateTime endDate, + Destination destination, + List activity, + }); + + $DestinationCopyWith<$Res> get destination; +} + +/// @nodoc +class _$BookingCopyWithImpl<$Res, $Val extends Booking> + implements $BookingCopyWith<$Res> { + _$BookingCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Booking + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? startDate = null, + Object? endDate = null, + Object? destination = null, + Object? activity = null, + }) { + return _then( + _value.copyWith( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int?, + startDate: null == startDate + ? _value.startDate + : startDate // ignore: cast_nullable_to_non_nullable + as DateTime, + endDate: null == endDate + ? _value.endDate + : endDate // ignore: cast_nullable_to_non_nullable + as DateTime, + destination: null == destination + ? _value.destination + : destination // ignore: cast_nullable_to_non_nullable + as Destination, + activity: null == activity + ? _value.activity + : activity // ignore: cast_nullable_to_non_nullable + as List, + ) + as $Val, + ); + } + + /// Create a copy of Booking + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $DestinationCopyWith<$Res> get destination { + return $DestinationCopyWith<$Res>(_value.destination, (value) { + return _then(_value.copyWith(destination: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$BookingImplCopyWith<$Res> implements $BookingCopyWith<$Res> { + factory _$$BookingImplCopyWith( + _$BookingImpl value, + $Res Function(_$BookingImpl) then, + ) = __$$BookingImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + int? id, + DateTime startDate, + DateTime endDate, + Destination destination, + List activity, + }); + + @override + $DestinationCopyWith<$Res> get destination; +} + +/// @nodoc +class __$$BookingImplCopyWithImpl<$Res> + extends _$BookingCopyWithImpl<$Res, _$BookingImpl> + implements _$$BookingImplCopyWith<$Res> { + __$$BookingImplCopyWithImpl( + _$BookingImpl _value, + $Res Function(_$BookingImpl) _then, + ) : super(_value, _then); + + /// Create a copy of Booking + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? startDate = null, + Object? endDate = null, + Object? destination = null, + Object? activity = null, + }) { + return _then( + _$BookingImpl( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int?, + startDate: null == startDate + ? _value.startDate + : startDate // ignore: cast_nullable_to_non_nullable + as DateTime, + endDate: null == endDate + ? _value.endDate + : endDate // ignore: cast_nullable_to_non_nullable + as DateTime, + destination: null == destination + ? _value.destination + : destination // ignore: cast_nullable_to_non_nullable + as Destination, + activity: null == activity + ? _value._activity + : activity // ignore: cast_nullable_to_non_nullable + as List, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$BookingImpl implements _Booking { + const _$BookingImpl({ + this.id, + required this.startDate, + required this.endDate, + required this.destination, + required final List activity, + }) : _activity = activity; + + factory _$BookingImpl.fromJson(Map json) => + _$$BookingImplFromJson(json); + + /// Optional ID of the booking. + /// May be null if the booking is not yet stored. + @override + final int? id; + + /// Start date of the trip + @override + final DateTime startDate; + + /// End date of the trip + @override + final DateTime endDate; + + /// Destination of the trip + @override + final Destination destination; + + /// List of chosen activities + final List _activity; + + /// List of chosen activities + @override + List get activity { + if (_activity is EqualUnmodifiableListView) return _activity; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_activity); + } + + @override + String toString() { + return 'Booking(id: $id, startDate: $startDate, endDate: $endDate, destination: $destination, activity: $activity)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BookingImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.startDate, startDate) || + other.startDate == startDate) && + (identical(other.endDate, endDate) || other.endDate == endDate) && + (identical(other.destination, destination) || + other.destination == destination) && + const DeepCollectionEquality().equals(other._activity, _activity)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + startDate, + endDate, + destination, + const DeepCollectionEquality().hash(_activity), + ); + + /// Create a copy of Booking + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$BookingImplCopyWith<_$BookingImpl> get copyWith => + __$$BookingImplCopyWithImpl<_$BookingImpl>(this, _$identity); + + @override + Map toJson() { + return _$$BookingImplToJson(this); + } +} + +abstract class _Booking implements Booking { + const factory _Booking({ + final int? id, + required final DateTime startDate, + required final DateTime endDate, + required final Destination destination, + required final List activity, + }) = _$BookingImpl; + + factory _Booking.fromJson(Map json) = _$BookingImpl.fromJson; + + /// Optional ID of the booking. + /// May be null if the booking is not yet stored. + @override + int? get id; + + /// Start date of the trip + @override + DateTime get startDate; + + /// End date of the trip + @override + DateTime get endDate; + + /// Destination of the trip + @override + Destination get destination; + + /// List of chosen activities + @override + List get activity; + + /// Create a copy of Booking + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$BookingImplCopyWith<_$BookingImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/compass_app/app/lib/domain/models/booking/booking.g.dart b/compass_app/app/lib/domain/models/booking/booking.g.dart new file mode 100644 index 0000000..6c851ab --- /dev/null +++ b/compass_app/app/lib/domain/models/booking/booking.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'booking.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$BookingImpl _$$BookingImplFromJson(Map json) => + _$BookingImpl( + id: (json['id'] as num?)?.toInt(), + startDate: DateTime.parse(json['startDate'] as String), + endDate: DateTime.parse(json['endDate'] as String), + destination: Destination.fromJson( + json['destination'] as Map, + ), + activity: (json['activity'] as List) + .map((e) => Activity.fromJson(e as Map)) + .toList(), + ); + +Map _$$BookingImplToJson(_$BookingImpl instance) => + { + 'id': instance.id, + 'startDate': instance.startDate.toIso8601String(), + 'endDate': instance.endDate.toIso8601String(), + 'destination': instance.destination, + 'activity': instance.activity, + }; diff --git a/compass_app/app/lib/domain/models/booking/booking_summary.dart b/compass_app/app/lib/domain/models/booking/booking_summary.dart new file mode 100644 index 0000000..633747f --- /dev/null +++ b/compass_app/app/lib/domain/models/booking/booking_summary.dart @@ -0,0 +1,34 @@ +// 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. + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'booking_summary.freezed.dart'; +part 'booking_summary.g.dart'; + +/// BookingSummary contains the necessary data to display a booking +/// in the user home screen, but lacks the rest of the booking data +/// like activities or destination. +/// +/// Use the [BookingRepository] to obtain a full [Booking] +/// using the [BookingSummary.id]. +@freezed +class BookingSummary with _$BookingSummary { + const factory BookingSummary({ + /// Booking id + required int id, + + /// Name to be displayed + required String name, + + /// Start date of the booking + required DateTime startDate, + + /// End date of the booking + required DateTime endDate, + }) = _BookingSummary; + + factory BookingSummary.fromJson(Map json) => + _$BookingSummaryFromJson(json); +} diff --git a/compass_app/app/lib/domain/models/booking/booking_summary.freezed.dart b/compass_app/app/lib/domain/models/booking/booking_summary.freezed.dart new file mode 100644 index 0000000..a06a75d --- /dev/null +++ b/compass_app/app/lib/domain/models/booking/booking_summary.freezed.dart @@ -0,0 +1,254 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'booking_summary.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +BookingSummary _$BookingSummaryFromJson(Map json) { + return _BookingSummary.fromJson(json); +} + +/// @nodoc +mixin _$BookingSummary { + /// Booking id + int get id => throw _privateConstructorUsedError; + + /// Name to be displayed + String get name => throw _privateConstructorUsedError; + + /// Start date of the booking + DateTime get startDate => throw _privateConstructorUsedError; + + /// End date of the booking + DateTime get endDate => throw _privateConstructorUsedError; + + /// Serializes this BookingSummary to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of BookingSummary + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $BookingSummaryCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $BookingSummaryCopyWith<$Res> { + factory $BookingSummaryCopyWith( + BookingSummary value, + $Res Function(BookingSummary) then, + ) = _$BookingSummaryCopyWithImpl<$Res, BookingSummary>; + @useResult + $Res call({int id, String name, DateTime startDate, DateTime endDate}); +} + +/// @nodoc +class _$BookingSummaryCopyWithImpl<$Res, $Val extends BookingSummary> + implements $BookingSummaryCopyWith<$Res> { + _$BookingSummaryCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of BookingSummary + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? startDate = null, + Object? endDate = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + startDate: null == startDate + ? _value.startDate + : startDate // ignore: cast_nullable_to_non_nullable + as DateTime, + endDate: null == endDate + ? _value.endDate + : endDate // ignore: cast_nullable_to_non_nullable + as DateTime, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$BookingSummaryImplCopyWith<$Res> + implements $BookingSummaryCopyWith<$Res> { + factory _$$BookingSummaryImplCopyWith( + _$BookingSummaryImpl value, + $Res Function(_$BookingSummaryImpl) then, + ) = __$$BookingSummaryImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int id, String name, DateTime startDate, DateTime endDate}); +} + +/// @nodoc +class __$$BookingSummaryImplCopyWithImpl<$Res> + extends _$BookingSummaryCopyWithImpl<$Res, _$BookingSummaryImpl> + implements _$$BookingSummaryImplCopyWith<$Res> { + __$$BookingSummaryImplCopyWithImpl( + _$BookingSummaryImpl _value, + $Res Function(_$BookingSummaryImpl) _then, + ) : super(_value, _then); + + /// Create a copy of BookingSummary + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? startDate = null, + Object? endDate = null, + }) { + return _then( + _$BookingSummaryImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + startDate: null == startDate + ? _value.startDate + : startDate // ignore: cast_nullable_to_non_nullable + as DateTime, + endDate: null == endDate + ? _value.endDate + : endDate // ignore: cast_nullable_to_non_nullable + as DateTime, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$BookingSummaryImpl implements _BookingSummary { + const _$BookingSummaryImpl({ + required this.id, + required this.name, + required this.startDate, + required this.endDate, + }); + + factory _$BookingSummaryImpl.fromJson(Map json) => + _$$BookingSummaryImplFromJson(json); + + /// Booking id + @override + final int id; + + /// Name to be displayed + @override + final String name; + + /// Start date of the booking + @override + final DateTime startDate; + + /// End date of the booking + @override + final DateTime endDate; + + @override + String toString() { + return 'BookingSummary(id: $id, name: $name, startDate: $startDate, endDate: $endDate)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BookingSummaryImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.startDate, startDate) || + other.startDate == startDate) && + (identical(other.endDate, endDate) || other.endDate == endDate)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, id, name, startDate, endDate); + + /// Create a copy of BookingSummary + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$BookingSummaryImplCopyWith<_$BookingSummaryImpl> get copyWith => + __$$BookingSummaryImplCopyWithImpl<_$BookingSummaryImpl>( + this, + _$identity, + ); + + @override + Map toJson() { + return _$$BookingSummaryImplToJson(this); + } +} + +abstract class _BookingSummary implements BookingSummary { + const factory _BookingSummary({ + required final int id, + required final String name, + required final DateTime startDate, + required final DateTime endDate, + }) = _$BookingSummaryImpl; + + factory _BookingSummary.fromJson(Map json) = + _$BookingSummaryImpl.fromJson; + + /// Booking id + @override + int get id; + + /// Name to be displayed + @override + String get name; + + /// Start date of the booking + @override + DateTime get startDate; + + /// End date of the booking + @override + DateTime get endDate; + + /// Create a copy of BookingSummary + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$BookingSummaryImplCopyWith<_$BookingSummaryImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/compass_app/app/lib/domain/models/booking/booking_summary.g.dart b/compass_app/app/lib/domain/models/booking/booking_summary.g.dart new file mode 100644 index 0000000..91d9e4d --- /dev/null +++ b/compass_app/app/lib/domain/models/booking/booking_summary.g.dart @@ -0,0 +1,24 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'booking_summary.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$BookingSummaryImpl _$$BookingSummaryImplFromJson(Map json) => + _$BookingSummaryImpl( + id: (json['id'] as num).toInt(), + name: json['name'] as String, + startDate: DateTime.parse(json['startDate'] as String), + endDate: DateTime.parse(json['endDate'] as String), + ); + +Map _$$BookingSummaryImplToJson( + _$BookingSummaryImpl instance, +) => { + 'id': instance.id, + 'name': instance.name, + 'startDate': instance.startDate.toIso8601String(), + 'endDate': instance.endDate.toIso8601String(), +}; diff --git a/compass_app/app/lib/domain/models/continent/continent.dart b/compass_app/app/lib/domain/models/continent/continent.dart new file mode 100644 index 0000000..e0a309d --- /dev/null +++ b/compass_app/app/lib/domain/models/continent/continent.dart @@ -0,0 +1,23 @@ +// 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. + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'continent.freezed.dart'; + +part 'continent.g.dart'; + +@freezed +class Continent with _$Continent { + const factory Continent({ + /// e.g. 'Europe' + required String name, + + /// e.g. 'https://rstr.in/google/tripedia/TmR12QdlVTT' + required String imageUrl, + }) = _Continent; + + factory Continent.fromJson(Map json) => + _$ContinentFromJson(json); +} diff --git a/compass_app/app/lib/domain/models/continent/continent.freezed.dart b/compass_app/app/lib/domain/models/continent/continent.freezed.dart new file mode 100644 index 0000000..6f13817 --- /dev/null +++ b/compass_app/app/lib/domain/models/continent/continent.freezed.dart @@ -0,0 +1,192 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'continent.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +Continent _$ContinentFromJson(Map json) { + return _Continent.fromJson(json); +} + +/// @nodoc +mixin _$Continent { + /// e.g. 'Europe' + String get name => throw _privateConstructorUsedError; + + /// e.g. 'https://rstr.in/google/tripedia/TmR12QdlVTT' + String get imageUrl => throw _privateConstructorUsedError; + + /// Serializes this Continent to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of Continent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ContinentCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ContinentCopyWith<$Res> { + factory $ContinentCopyWith(Continent value, $Res Function(Continent) then) = + _$ContinentCopyWithImpl<$Res, Continent>; + @useResult + $Res call({String name, String imageUrl}); +} + +/// @nodoc +class _$ContinentCopyWithImpl<$Res, $Val extends Continent> + implements $ContinentCopyWith<$Res> { + _$ContinentCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Continent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? name = null, Object? imageUrl = null}) { + return _then( + _value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + imageUrl: null == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ContinentImplCopyWith<$Res> + implements $ContinentCopyWith<$Res> { + factory _$$ContinentImplCopyWith( + _$ContinentImpl value, + $Res Function(_$ContinentImpl) then, + ) = __$$ContinentImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String name, String imageUrl}); +} + +/// @nodoc +class __$$ContinentImplCopyWithImpl<$Res> + extends _$ContinentCopyWithImpl<$Res, _$ContinentImpl> + implements _$$ContinentImplCopyWith<$Res> { + __$$ContinentImplCopyWithImpl( + _$ContinentImpl _value, + $Res Function(_$ContinentImpl) _then, + ) : super(_value, _then); + + /// Create a copy of Continent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? name = null, Object? imageUrl = null}) { + return _then( + _$ContinentImpl( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + imageUrl: null == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$ContinentImpl implements _Continent { + const _$ContinentImpl({required this.name, required this.imageUrl}); + + factory _$ContinentImpl.fromJson(Map json) => + _$$ContinentImplFromJson(json); + + /// e.g. 'Europe' + @override + final String name; + + /// e.g. 'https://rstr.in/google/tripedia/TmR12QdlVTT' + @override + final String imageUrl; + + @override + String toString() { + return 'Continent(name: $name, imageUrl: $imageUrl)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ContinentImpl && + (identical(other.name, name) || other.name == name) && + (identical(other.imageUrl, imageUrl) || + other.imageUrl == imageUrl)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, name, imageUrl); + + /// Create a copy of Continent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ContinentImplCopyWith<_$ContinentImpl> get copyWith => + __$$ContinentImplCopyWithImpl<_$ContinentImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ContinentImplToJson(this); + } +} + +abstract class _Continent implements Continent { + const factory _Continent({ + required final String name, + required final String imageUrl, + }) = _$ContinentImpl; + + factory _Continent.fromJson(Map json) = + _$ContinentImpl.fromJson; + + /// e.g. 'Europe' + @override + String get name; + + /// e.g. 'https://rstr.in/google/tripedia/TmR12QdlVTT' + @override + String get imageUrl; + + /// Create a copy of Continent + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ContinentImplCopyWith<_$ContinentImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/compass_app/app/lib/domain/models/continent/continent.g.dart b/compass_app/app/lib/domain/models/continent/continent.g.dart new file mode 100644 index 0000000..8b34752 --- /dev/null +++ b/compass_app/app/lib/domain/models/continent/continent.g.dart @@ -0,0 +1,16 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'continent.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ContinentImpl _$$ContinentImplFromJson(Map json) => + _$ContinentImpl( + name: json['name'] as String, + imageUrl: json['imageUrl'] as String, + ); + +Map _$$ContinentImplToJson(_$ContinentImpl instance) => + {'name': instance.name, 'imageUrl': instance.imageUrl}; diff --git a/compass_app/app/lib/domain/models/destination/destination.dart b/compass_app/app/lib/domain/models/destination/destination.dart new file mode 100644 index 0000000..457a125 --- /dev/null +++ b/compass_app/app/lib/domain/models/destination/destination.dart @@ -0,0 +1,38 @@ +// 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. + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'destination.freezed.dart'; + +part 'destination.g.dart'; + +@freezed +class Destination with _$Destination { + const factory Destination({ + /// e.g. 'alaska' + required String ref, + + /// e.g. 'Alaska' + required String name, + + /// e.g. 'United States' + required String country, + + /// e.g. 'North America' + required String continent, + + /// e.g. 'Alaska is a haven for outdoor enthusiasts ...' + required String knownFor, + + /// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching'] + required List tags, + + /// e.g. 'https://storage.googleapis.com/tripedia-images/destinations/alaska.jpg' + required String imageUrl, + }) = _Destination; + + factory Destination.fromJson(Map json) => + _$DestinationFromJson(json); +} diff --git a/compass_app/app/lib/domain/models/destination/destination.freezed.dart b/compass_app/app/lib/domain/models/destination/destination.freezed.dart new file mode 100644 index 0000000..1babb93 --- /dev/null +++ b/compass_app/app/lib/domain/models/destination/destination.freezed.dart @@ -0,0 +1,357 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'destination.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +Destination _$DestinationFromJson(Map json) { + return _Destination.fromJson(json); +} + +/// @nodoc +mixin _$Destination { + /// e.g. 'alaska' + String get ref => throw _privateConstructorUsedError; + + /// e.g. 'Alaska' + String get name => throw _privateConstructorUsedError; + + /// e.g. 'United States' + String get country => throw _privateConstructorUsedError; + + /// e.g. 'North America' + String get continent => throw _privateConstructorUsedError; + + /// e.g. 'Alaska is a haven for outdoor enthusiasts ...' + String get knownFor => throw _privateConstructorUsedError; + + /// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching'] + List get tags => throw _privateConstructorUsedError; + + /// e.g. 'https://storage.googleapis.com/tripedia-images/destinations/alaska.jpg' + String get imageUrl => throw _privateConstructorUsedError; + + /// Serializes this Destination to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of Destination + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $DestinationCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $DestinationCopyWith<$Res> { + factory $DestinationCopyWith( + Destination value, + $Res Function(Destination) then, + ) = _$DestinationCopyWithImpl<$Res, Destination>; + @useResult + $Res call({ + String ref, + String name, + String country, + String continent, + String knownFor, + List tags, + String imageUrl, + }); +} + +/// @nodoc +class _$DestinationCopyWithImpl<$Res, $Val extends Destination> + implements $DestinationCopyWith<$Res> { + _$DestinationCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Destination + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? ref = null, + Object? name = null, + Object? country = null, + Object? continent = null, + Object? knownFor = null, + Object? tags = null, + Object? imageUrl = null, + }) { + return _then( + _value.copyWith( + ref: null == ref + ? _value.ref + : ref // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + country: null == country + ? _value.country + : country // ignore: cast_nullable_to_non_nullable + as String, + continent: null == continent + ? _value.continent + : continent // ignore: cast_nullable_to_non_nullable + as String, + knownFor: null == knownFor + ? _value.knownFor + : knownFor // ignore: cast_nullable_to_non_nullable + as String, + tags: null == tags + ? _value.tags + : tags // ignore: cast_nullable_to_non_nullable + as List, + imageUrl: null == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$DestinationImplCopyWith<$Res> + implements $DestinationCopyWith<$Res> { + factory _$$DestinationImplCopyWith( + _$DestinationImpl value, + $Res Function(_$DestinationImpl) then, + ) = __$$DestinationImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String ref, + String name, + String country, + String continent, + String knownFor, + List tags, + String imageUrl, + }); +} + +/// @nodoc +class __$$DestinationImplCopyWithImpl<$Res> + extends _$DestinationCopyWithImpl<$Res, _$DestinationImpl> + implements _$$DestinationImplCopyWith<$Res> { + __$$DestinationImplCopyWithImpl( + _$DestinationImpl _value, + $Res Function(_$DestinationImpl) _then, + ) : super(_value, _then); + + /// Create a copy of Destination + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? ref = null, + Object? name = null, + Object? country = null, + Object? continent = null, + Object? knownFor = null, + Object? tags = null, + Object? imageUrl = null, + }) { + return _then( + _$DestinationImpl( + ref: null == ref + ? _value.ref + : ref // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + country: null == country + ? _value.country + : country // ignore: cast_nullable_to_non_nullable + as String, + continent: null == continent + ? _value.continent + : continent // ignore: cast_nullable_to_non_nullable + as String, + knownFor: null == knownFor + ? _value.knownFor + : knownFor // ignore: cast_nullable_to_non_nullable + as String, + tags: null == tags + ? _value._tags + : tags // ignore: cast_nullable_to_non_nullable + as List, + imageUrl: null == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$DestinationImpl implements _Destination { + const _$DestinationImpl({ + required this.ref, + required this.name, + required this.country, + required this.continent, + required this.knownFor, + required final List tags, + required this.imageUrl, + }) : _tags = tags; + + factory _$DestinationImpl.fromJson(Map json) => + _$$DestinationImplFromJson(json); + + /// e.g. 'alaska' + @override + final String ref; + + /// e.g. 'Alaska' + @override + final String name; + + /// e.g. 'United States' + @override + final String country; + + /// e.g. 'North America' + @override + final String continent; + + /// e.g. 'Alaska is a haven for outdoor enthusiasts ...' + @override + final String knownFor; + + /// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching'] + final List _tags; + + /// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching'] + @override + List get tags { + if (_tags is EqualUnmodifiableListView) return _tags; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_tags); + } + + /// e.g. 'https://storage.googleapis.com/tripedia-images/destinations/alaska.jpg' + @override + final String imageUrl; + + @override + String toString() { + return 'Destination(ref: $ref, name: $name, country: $country, continent: $continent, knownFor: $knownFor, tags: $tags, imageUrl: $imageUrl)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$DestinationImpl && + (identical(other.ref, ref) || other.ref == ref) && + (identical(other.name, name) || other.name == name) && + (identical(other.country, country) || other.country == country) && + (identical(other.continent, continent) || + other.continent == continent) && + (identical(other.knownFor, knownFor) || + other.knownFor == knownFor) && + const DeepCollectionEquality().equals(other._tags, _tags) && + (identical(other.imageUrl, imageUrl) || + other.imageUrl == imageUrl)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + ref, + name, + country, + continent, + knownFor, + const DeepCollectionEquality().hash(_tags), + imageUrl, + ); + + /// Create a copy of Destination + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$DestinationImplCopyWith<_$DestinationImpl> get copyWith => + __$$DestinationImplCopyWithImpl<_$DestinationImpl>(this, _$identity); + + @override + Map toJson() { + return _$$DestinationImplToJson(this); + } +} + +abstract class _Destination implements Destination { + const factory _Destination({ + required final String ref, + required final String name, + required final String country, + required final String continent, + required final String knownFor, + required final List tags, + required final String imageUrl, + }) = _$DestinationImpl; + + factory _Destination.fromJson(Map json) = + _$DestinationImpl.fromJson; + + /// e.g. 'alaska' + @override + String get ref; + + /// e.g. 'Alaska' + @override + String get name; + + /// e.g. 'United States' + @override + String get country; + + /// e.g. 'North America' + @override + String get continent; + + /// e.g. 'Alaska is a haven for outdoor enthusiasts ...' + @override + String get knownFor; + + /// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching'] + @override + List get tags; + + /// e.g. 'https://storage.googleapis.com/tripedia-images/destinations/alaska.jpg' + @override + String get imageUrl; + + /// Create a copy of Destination + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$DestinationImplCopyWith<_$DestinationImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/compass_app/app/lib/domain/models/destination/destination.g.dart b/compass_app/app/lib/domain/models/destination/destination.g.dart new file mode 100644 index 0000000..796e2bb --- /dev/null +++ b/compass_app/app/lib/domain/models/destination/destination.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'destination.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$DestinationImpl _$$DestinationImplFromJson(Map json) => + _$DestinationImpl( + ref: json['ref'] as String, + name: json['name'] as String, + country: json['country'] as String, + continent: json['continent'] as String, + knownFor: json['knownFor'] as String, + tags: (json['tags'] as List).map((e) => e as String).toList(), + imageUrl: json['imageUrl'] as String, + ); + +Map _$$DestinationImplToJson(_$DestinationImpl instance) => + { + 'ref': instance.ref, + 'name': instance.name, + 'country': instance.country, + 'continent': instance.continent, + 'knownFor': instance.knownFor, + 'tags': instance.tags, + 'imageUrl': instance.imageUrl, + }; diff --git a/compass_app/app/lib/domain/models/itinerary_config/itinerary_config.dart b/compass_app/app/lib/domain/models/itinerary_config/itinerary_config.dart new file mode 100644 index 0000000..7d55b7c --- /dev/null +++ b/compass_app/app/lib/domain/models/itinerary_config/itinerary_config.dart @@ -0,0 +1,35 @@ +// 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. + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'itinerary_config.freezed.dart'; + +part 'itinerary_config.g.dart'; + +@freezed +class ItineraryConfig with _$ItineraryConfig { + const factory ItineraryConfig({ + /// [Continent] name + String? continent, + + /// Start date (check in) of itinerary + DateTime? startDate, + + /// End date (check out) of itinerary + DateTime? endDate, + + /// Number of guests + int? guests, + + /// Selected [Destination] reference + String? destination, + + /// Selected [Activity] references + @Default([]) List activities, + }) = _ItineraryConfig; + + factory ItineraryConfig.fromJson(Map json) => + _$ItineraryConfigFromJson(json); +} diff --git a/compass_app/app/lib/domain/models/itinerary_config/itinerary_config.freezed.dart b/compass_app/app/lib/domain/models/itinerary_config/itinerary_config.freezed.dart new file mode 100644 index 0000000..e933300 --- /dev/null +++ b/compass_app/app/lib/domain/models/itinerary_config/itinerary_config.freezed.dart @@ -0,0 +1,337 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'itinerary_config.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +ItineraryConfig _$ItineraryConfigFromJson(Map json) { + return _ItineraryConfig.fromJson(json); +} + +/// @nodoc +mixin _$ItineraryConfig { + /// [Continent] name + String? get continent => throw _privateConstructorUsedError; + + /// Start date (check in) of itinerary + DateTime? get startDate => throw _privateConstructorUsedError; + + /// End date (check out) of itinerary + DateTime? get endDate => throw _privateConstructorUsedError; + + /// Number of guests + int? get guests => throw _privateConstructorUsedError; + + /// Selected [Destination] reference + String? get destination => throw _privateConstructorUsedError; + + /// Selected [Activity] references + List get activities => throw _privateConstructorUsedError; + + /// Serializes this ItineraryConfig to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ItineraryConfig + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ItineraryConfigCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ItineraryConfigCopyWith<$Res> { + factory $ItineraryConfigCopyWith( + ItineraryConfig value, + $Res Function(ItineraryConfig) then, + ) = _$ItineraryConfigCopyWithImpl<$Res, ItineraryConfig>; + @useResult + $Res call({ + String? continent, + DateTime? startDate, + DateTime? endDate, + int? guests, + String? destination, + List activities, + }); +} + +/// @nodoc +class _$ItineraryConfigCopyWithImpl<$Res, $Val extends ItineraryConfig> + implements $ItineraryConfigCopyWith<$Res> { + _$ItineraryConfigCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ItineraryConfig + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? continent = freezed, + Object? startDate = freezed, + Object? endDate = freezed, + Object? guests = freezed, + Object? destination = freezed, + Object? activities = null, + }) { + return _then( + _value.copyWith( + continent: freezed == continent + ? _value.continent + : continent // ignore: cast_nullable_to_non_nullable + as String?, + startDate: freezed == startDate + ? _value.startDate + : startDate // ignore: cast_nullable_to_non_nullable + as DateTime?, + endDate: freezed == endDate + ? _value.endDate + : endDate // ignore: cast_nullable_to_non_nullable + as DateTime?, + guests: freezed == guests + ? _value.guests + : guests // ignore: cast_nullable_to_non_nullable + as int?, + destination: freezed == destination + ? _value.destination + : destination // ignore: cast_nullable_to_non_nullable + as String?, + activities: null == activities + ? _value.activities + : activities // ignore: cast_nullable_to_non_nullable + as List, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ItineraryConfigImplCopyWith<$Res> + implements $ItineraryConfigCopyWith<$Res> { + factory _$$ItineraryConfigImplCopyWith( + _$ItineraryConfigImpl value, + $Res Function(_$ItineraryConfigImpl) then, + ) = __$$ItineraryConfigImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String? continent, + DateTime? startDate, + DateTime? endDate, + int? guests, + String? destination, + List activities, + }); +} + +/// @nodoc +class __$$ItineraryConfigImplCopyWithImpl<$Res> + extends _$ItineraryConfigCopyWithImpl<$Res, _$ItineraryConfigImpl> + implements _$$ItineraryConfigImplCopyWith<$Res> { + __$$ItineraryConfigImplCopyWithImpl( + _$ItineraryConfigImpl _value, + $Res Function(_$ItineraryConfigImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ItineraryConfig + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? continent = freezed, + Object? startDate = freezed, + Object? endDate = freezed, + Object? guests = freezed, + Object? destination = freezed, + Object? activities = null, + }) { + return _then( + _$ItineraryConfigImpl( + continent: freezed == continent + ? _value.continent + : continent // ignore: cast_nullable_to_non_nullable + as String?, + startDate: freezed == startDate + ? _value.startDate + : startDate // ignore: cast_nullable_to_non_nullable + as DateTime?, + endDate: freezed == endDate + ? _value.endDate + : endDate // ignore: cast_nullable_to_non_nullable + as DateTime?, + guests: freezed == guests + ? _value.guests + : guests // ignore: cast_nullable_to_non_nullable + as int?, + destination: freezed == destination + ? _value.destination + : destination // ignore: cast_nullable_to_non_nullable + as String?, + activities: null == activities + ? _value._activities + : activities // ignore: cast_nullable_to_non_nullable + as List, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$ItineraryConfigImpl implements _ItineraryConfig { + const _$ItineraryConfigImpl({ + this.continent, + this.startDate, + this.endDate, + this.guests, + this.destination, + final List activities = const [], + }) : _activities = activities; + + factory _$ItineraryConfigImpl.fromJson(Map json) => + _$$ItineraryConfigImplFromJson(json); + + /// [Continent] name + @override + final String? continent; + + /// Start date (check in) of itinerary + @override + final DateTime? startDate; + + /// End date (check out) of itinerary + @override + final DateTime? endDate; + + /// Number of guests + @override + final int? guests; + + /// Selected [Destination] reference + @override + final String? destination; + + /// Selected [Activity] references + final List _activities; + + /// Selected [Activity] references + @override + @JsonKey() + List get activities { + if (_activities is EqualUnmodifiableListView) return _activities; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_activities); + } + + @override + String toString() { + return 'ItineraryConfig(continent: $continent, startDate: $startDate, endDate: $endDate, guests: $guests, destination: $destination, activities: $activities)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ItineraryConfigImpl && + (identical(other.continent, continent) || + other.continent == continent) && + (identical(other.startDate, startDate) || + other.startDate == startDate) && + (identical(other.endDate, endDate) || other.endDate == endDate) && + (identical(other.guests, guests) || other.guests == guests) && + (identical(other.destination, destination) || + other.destination == destination) && + const DeepCollectionEquality().equals( + other._activities, + _activities, + )); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + continent, + startDate, + endDate, + guests, + destination, + const DeepCollectionEquality().hash(_activities), + ); + + /// Create a copy of ItineraryConfig + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ItineraryConfigImplCopyWith<_$ItineraryConfigImpl> get copyWith => + __$$ItineraryConfigImplCopyWithImpl<_$ItineraryConfigImpl>( + this, + _$identity, + ); + + @override + Map toJson() { + return _$$ItineraryConfigImplToJson(this); + } +} + +abstract class _ItineraryConfig implements ItineraryConfig { + const factory _ItineraryConfig({ + final String? continent, + final DateTime? startDate, + final DateTime? endDate, + final int? guests, + final String? destination, + final List activities, + }) = _$ItineraryConfigImpl; + + factory _ItineraryConfig.fromJson(Map json) = + _$ItineraryConfigImpl.fromJson; + + /// [Continent] name + @override + String? get continent; + + /// Start date (check in) of itinerary + @override + DateTime? get startDate; + + /// End date (check out) of itinerary + @override + DateTime? get endDate; + + /// Number of guests + @override + int? get guests; + + /// Selected [Destination] reference + @override + String? get destination; + + /// Selected [Activity] references + @override + List get activities; + + /// Create a copy of ItineraryConfig + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ItineraryConfigImplCopyWith<_$ItineraryConfigImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/compass_app/app/lib/domain/models/itinerary_config/itinerary_config.g.dart b/compass_app/app/lib/domain/models/itinerary_config/itinerary_config.g.dart new file mode 100644 index 0000000..c012d7a --- /dev/null +++ b/compass_app/app/lib/domain/models/itinerary_config/itinerary_config.g.dart @@ -0,0 +1,37 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'itinerary_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ItineraryConfigImpl _$$ItineraryConfigImplFromJson( + Map json, +) => _$ItineraryConfigImpl( + continent: json['continent'] as String?, + startDate: json['startDate'] == null + ? null + : DateTime.parse(json['startDate'] as String), + endDate: json['endDate'] == null + ? null + : DateTime.parse(json['endDate'] as String), + guests: (json['guests'] as num?)?.toInt(), + destination: json['destination'] as String?, + activities: + (json['activities'] as List?) + ?.map((e) => e as String) + .toList() ?? + const [], +); + +Map _$$ItineraryConfigImplToJson( + _$ItineraryConfigImpl instance, +) => { + 'continent': instance.continent, + 'startDate': instance.startDate?.toIso8601String(), + 'endDate': instance.endDate?.toIso8601String(), + 'guests': instance.guests, + 'destination': instance.destination, + 'activities': instance.activities, +}; diff --git a/compass_app/app/lib/domain/models/user/user.dart b/compass_app/app/lib/domain/models/user/user.dart new file mode 100644 index 0000000..001a5ad --- /dev/null +++ b/compass_app/app/lib/domain/models/user/user.dart @@ -0,0 +1,21 @@ +// 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. + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'user.freezed.dart'; +part 'user.g.dart'; + +@freezed +abstract class User with _$User { + const factory User({ + /// The user's name. + required String name, + + /// The user's picture URL. + required String picture, + }) = _User; + + factory User.fromJson(Map json) => _$UserFromJson(json); +} diff --git a/compass_app/app/lib/domain/models/user/user.freezed.dart b/compass_app/app/lib/domain/models/user/user.freezed.dart new file mode 100644 index 0000000..b510aac --- /dev/null +++ b/compass_app/app/lib/domain/models/user/user.freezed.dart @@ -0,0 +1,186 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'user.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +User _$UserFromJson(Map json) { + return _User.fromJson(json); +} + +/// @nodoc +mixin _$User { + /// The user's name. + String get name => throw _privateConstructorUsedError; + + /// The user's picture URL. + String get picture => throw _privateConstructorUsedError; + + /// Serializes this User to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of User + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $UserCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $UserCopyWith<$Res> { + factory $UserCopyWith(User value, $Res Function(User) then) = + _$UserCopyWithImpl<$Res, User>; + @useResult + $Res call({String name, String picture}); +} + +/// @nodoc +class _$UserCopyWithImpl<$Res, $Val extends User> + implements $UserCopyWith<$Res> { + _$UserCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of User + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? name = null, Object? picture = null}) { + return _then( + _value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + picture: null == picture + ? _value.picture + : picture // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$UserImplCopyWith<$Res> implements $UserCopyWith<$Res> { + factory _$$UserImplCopyWith( + _$UserImpl value, + $Res Function(_$UserImpl) then, + ) = __$$UserImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String name, String picture}); +} + +/// @nodoc +class __$$UserImplCopyWithImpl<$Res> + extends _$UserCopyWithImpl<$Res, _$UserImpl> + implements _$$UserImplCopyWith<$Res> { + __$$UserImplCopyWithImpl(_$UserImpl _value, $Res Function(_$UserImpl) _then) + : super(_value, _then); + + /// Create a copy of User + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? name = null, Object? picture = null}) { + return _then( + _$UserImpl( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + picture: null == picture + ? _value.picture + : picture // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$UserImpl implements _User { + const _$UserImpl({required this.name, required this.picture}); + + factory _$UserImpl.fromJson(Map json) => + _$$UserImplFromJson(json); + + /// The user's name. + @override + final String name; + + /// The user's picture URL. + @override + final String picture; + + @override + String toString() { + return 'User(name: $name, picture: $picture)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UserImpl && + (identical(other.name, name) || other.name == name) && + (identical(other.picture, picture) || other.picture == picture)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, name, picture); + + /// Create a copy of User + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$UserImplCopyWith<_$UserImpl> get copyWith => + __$$UserImplCopyWithImpl<_$UserImpl>(this, _$identity); + + @override + Map toJson() { + return _$$UserImplToJson(this); + } +} + +abstract class _User implements User { + const factory _User({ + required final String name, + required final String picture, + }) = _$UserImpl; + + factory _User.fromJson(Map json) = _$UserImpl.fromJson; + + /// The user's name. + @override + String get name; + + /// The user's picture URL. + @override + String get picture; + + /// Create a copy of User + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$UserImplCopyWith<_$UserImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/compass_app/app/lib/domain/models/user/user.g.dart b/compass_app/app/lib/domain/models/user/user.g.dart new file mode 100644 index 0000000..7bb221c --- /dev/null +++ b/compass_app/app/lib/domain/models/user/user.g.dart @@ -0,0 +1,15 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$UserImpl _$$UserImplFromJson(Map json) => _$UserImpl( + name: json['name'] as String, + picture: json['picture'] as String, +); + +Map _$$UserImplToJson(_$UserImpl instance) => + {'name': instance.name, 'picture': instance.picture}; diff --git a/compass_app/app/lib/domain/use_cases/booking/booking_create_use_case.dart b/compass_app/app/lib/domain/use_cases/booking/booking_create_use_case.dart new file mode 100644 index 0000000..cec85af --- /dev/null +++ b/compass_app/app/lib/domain/use_cases/booking/booking_create_use_case.dart @@ -0,0 +1,109 @@ +// 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. + +import 'package:logging/logging.dart'; + +import '../../../data/repositories/activity/activity_repository.dart'; +import '../../../data/repositories/booking/booking_repository.dart'; +import '../../../data/repositories/destination/destination_repository.dart'; +import '../../../utils/result.dart'; +import '../../models/activity/activity.dart'; +import '../../models/booking/booking.dart'; +import '../../models/destination/destination.dart'; +import '../../models/itinerary_config/itinerary_config.dart'; + +/// UseCase for creating [Booking] objects from [ItineraryConfig]. +/// +/// Fetches [Destination] and [Activity] objects from repositories, +/// checks if dates are set and creates a [Booking] object. +class BookingCreateUseCase { + BookingCreateUseCase({ + required DestinationRepository destinationRepository, + required ActivityRepository activityRepository, + required BookingRepository bookingRepository, + }) : _destinationRepository = destinationRepository, + _activityRepository = activityRepository, + _bookingRepository = bookingRepository; + + final DestinationRepository _destinationRepository; + final ActivityRepository _activityRepository; + final BookingRepository _bookingRepository; + final _log = Logger('BookingCreateUseCase'); + + /// Create [Booking] from a stored [ItineraryConfig] + Future> createFrom(ItineraryConfig itineraryConfig) async { + // Get Destination object from repository + if (itineraryConfig.destination == null) { + _log.warning('Destination is not set'); + return Result.error(Exception('Destination is not set')); + } + final destinationResult = await _fetchDestination( + itineraryConfig.destination!, + ); + switch (destinationResult) { + case Ok(): + _log.fine('Destination loaded: ${destinationResult.value.ref}'); + case Error(): + _log.warning('Error fetching destination: ${destinationResult.error}'); + return Result.error(destinationResult.error); + } + + // Get Activity objects from repository + if (itineraryConfig.activities.isEmpty) { + _log.warning('Activities are not set'); + return Result.error(Exception('Activities are not set')); + } + final activitiesResult = await _activityRepository.getByDestination( + itineraryConfig.destination!, + ); + switch (activitiesResult) { + case Error>(): + _log.warning('Error fetching activities: ${activitiesResult.error}'); + return Result.error(activitiesResult.error); + case Ok>(): + } + final activities = activitiesResult.value + .where((activity) => itineraryConfig.activities.contains(activity.ref)) + .toList(); + _log.fine('Activities loaded (${activities.length})'); + + // Check if dates are set + if (itineraryConfig.startDate == null || itineraryConfig.endDate == null) { + _log.warning('Dates are not set'); + return Result.error(Exception('Dates are not set')); + } + + final booking = Booking( + startDate: itineraryConfig.startDate!, + endDate: itineraryConfig.endDate!, + destination: destinationResult.value, + activity: activities, + ); + + final saveBookingResult = await _bookingRepository.createBooking(booking); + switch (saveBookingResult) { + case Ok(): + _log.fine('Booking saved successfully'); + case Error(): + _log.warning('Failed to save booking', saveBookingResult.error); + return Result.error(saveBookingResult.error); + } + + // Create Booking object + return Result.ok(booking); + } + + Future> _fetchDestination(String destinationRef) async { + final result = await _destinationRepository.getDestinations(); + switch (result) { + case Ok>(): + final destination = result.value.firstWhere( + (destination) => destination.ref == destinationRef, + ); + return Result.ok(destination); + case Error>(): + return Result.error(result.error); + } + } +} diff --git a/compass_app/app/lib/domain/use_cases/booking/booking_share_use_case.dart b/compass_app/app/lib/domain/use_cases/booking/booking_share_use_case.dart new file mode 100644 index 0000000..c7abf67 --- /dev/null +++ b/compass_app/app/lib/domain/use_cases/booking/booking_share_use_case.dart @@ -0,0 +1,47 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:logging/logging.dart'; +import 'package:share_plus/share_plus.dart'; + +import '../../../ui/core/ui/date_format_start_end.dart'; +import '../../../utils/result.dart'; +import '../../models/booking/booking.dart'; + +typedef ShareFunction = Future Function(String text); + +/// UseCase for sharing a booking. +class BookingShareUseCase { + BookingShareUseCase._(this._share); + + /// Create a [BookingShareUseCase] that uses `share_plus` package. + factory BookingShareUseCase.withSharePlus() => + BookingShareUseCase._(Share.share); + + /// Create a [BookingShareUseCase] with a custom share function. + factory BookingShareUseCase.custom(ShareFunction share) => + BookingShareUseCase._(share); + + final ShareFunction _share; + final _log = Logger('BookingShareUseCase'); + + Future> shareBooking(Booking booking) async { + final text = + 'Trip to ${booking.destination.name}\n' + 'on ${dateFormatStartEnd(DateTimeRange(start: booking.startDate, end: booking.endDate))}\n' + 'Activities:\n' + '${booking.activity.map((a) => ' - ${a.name}').join('\n')}.'; + + _log.info('Sharing booking: $text'); + try { + await _share(text); + _log.fine('Shared booking'); + return const Result.ok(null); + } on Exception catch (error) { + _log.severe('Failed to share booking', error); + return Result.error(error); + } + } +} diff --git a/compass_app/app/lib/main.dart b/compass_app/app/lib/main.dart new file mode 100644 index 0000000..880d91d --- /dev/null +++ b/compass_app/app/lib/main.dart @@ -0,0 +1,39 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:provider/provider.dart'; + +import 'main_development.dart' as development; +import 'routing/router.dart'; +import 'ui/core/localization/applocalization.dart'; +import 'ui/core/themes/theme.dart'; +import 'ui/core/ui/scroll_behavior.dart'; + +/// Default main method +void main() { + // Launch development config by default + development.main(); +} + +class MainApp extends StatelessWidget { + const MainApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp.router( + localizationsDelegates: [ + GlobalWidgetsLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + AppLocalizationDelegate(), + ], + scrollBehavior: AppCustomScrollBehavior(), + theme: AppTheme.lightTheme, + darkTheme: AppTheme.darkTheme, + themeMode: ThemeMode.system, + routerConfig: router(context.read()), + ); + } +} diff --git a/compass_app/app/lib/main_development.dart b/compass_app/app/lib/main_development.dart new file mode 100644 index 0000000..163f2f4 --- /dev/null +++ b/compass_app/app/lib/main_development.dart @@ -0,0 +1,19 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:logging/logging.dart'; +import 'package:provider/provider.dart'; + +import 'config/dependencies.dart'; +import 'main.dart'; + +/// Development config entry point. +/// Launch with `flutter run --target lib/main_development.dart`. +/// Uses local data. +void main() { + Logger.root.level = Level.ALL; + + runApp(MultiProvider(providers: providersLocal, child: const MainApp())); +} diff --git a/compass_app/app/lib/main_staging.dart b/compass_app/app/lib/main_staging.dart new file mode 100644 index 0000000..4f4e51c --- /dev/null +++ b/compass_app/app/lib/main_staging.dart @@ -0,0 +1,19 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:logging/logging.dart'; +import 'package:provider/provider.dart'; + +import 'config/dependencies.dart'; +import 'main.dart'; + +/// Staging config entry point. +/// Launch with `flutter run --target lib/main_staging.dart`. +/// Uses remote data from a server. +void main() { + Logger.root.level = Level.ALL; + + runApp(MultiProvider(providers: providersRemote, child: const MainApp())); +} diff --git a/compass_app/app/lib/routing/router.dart b/compass_app/app/lib/routing/router.dart new file mode 100644 index 0000000..390f32e --- /dev/null +++ b/compass_app/app/lib/routing/router.dart @@ -0,0 +1,141 @@ +// 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. + +import 'package:flutter/cupertino.dart'; +import 'package:go_router/go_router.dart'; +import 'package:provider/provider.dart'; + +import '../data/repositories/auth/auth_repository.dart'; +import '../ui/activities/view_models/activities_viewmodel.dart'; +import '../ui/activities/widgets/activities_screen.dart'; +import '../ui/auth/login/view_models/login_viewmodel.dart'; +import '../ui/auth/login/widgets/login_screen.dart'; +import '../ui/auth/logout/view_models/logout_viewmodel.dart'; +import '../ui/booking/view_models/booking_viewmodel.dart'; +import '../ui/booking/widgets/booking_screen.dart'; +import '../ui/home/widgets/home_screen_container.dart'; +import '../ui/results/view_models/results_viewmodel.dart'; +import '../ui/results/widgets/results_screen.dart'; +import '../ui/search_form/view_models/search_form_viewmodel.dart'; +import '../ui/search_form/widgets/search_form_screen.dart'; +import 'routes.dart'; + +/// Top go_router entry point. +/// +/// Listens to changes in [AuthTokenRepository] to redirect the user +/// to /login when the user logs out. +GoRouter router(AuthRepository authRepository) => GoRouter( + initialLocation: Routes.home, + debugLogDiagnostics: true, + redirect: _redirect, + refreshListenable: authRepository, + routes: [ + GoRoute( + path: Routes.login, + builder: (context, state) { + return LoginScreen( + viewModel: LoginViewModel(authRepository: context.read()), + ); + }, + ), + GoRoute( + path: Routes.home, + builder: (context, state) { + final logoutViewModel = LogoutViewModel( + authRepository: context.read(), + itineraryConfigRepository: context.read(), + ); + return HomeScreenContainer(logoutViewModel: logoutViewModel); + }, + routes: [ + GoRoute( + path: Routes.searchRelative, + builder: (context, state) { + final viewModel = SearchFormViewModel( + continentRepository: context.read(), + itineraryConfigRepository: context.read(), + ); + return SearchFormScreen(viewModel: viewModel); + }, + ), + GoRoute( + path: Routes.resultsRelative, + builder: (context, state) { + final viewModel = ResultsViewModel( + destinationRepository: context.read(), + itineraryConfigRepository: context.read(), + ); + return ResultsScreen(viewModel: viewModel); + }, + ), + GoRoute( + path: Routes.activitiesRelative, + builder: (context, state) { + final viewModel = ActivitiesViewModel( + activityRepository: context.read(), + itineraryConfigRepository: context.read(), + ); + return ActivitiesScreen(viewModel: viewModel); + }, + ), + GoRoute( + path: Routes.bookingRelative, + builder: (context, state) { + final viewModel = BookingViewModel( + itineraryConfigRepository: context.read(), + createBookingUseCase: context.read(), + shareBookingUseCase: context.read(), + bookingRepository: context.read(), + ); + + // When opening the booking screen directly + // create a new booking from the stored ItineraryConfig. + viewModel.createBooking.execute(); + + return BookingScreen(viewModel: viewModel); + }, + routes: [ + GoRoute( + path: ':id', + builder: (context, state) { + final id = int.parse(state.pathParameters['id']!); + final viewModel = BookingViewModel( + itineraryConfigRepository: context.read(), + createBookingUseCase: context.read(), + shareBookingUseCase: context.read(), + bookingRepository: context.read(), + ); + + // When opening the booking screen with an existing id + // load and display that booking. + viewModel.loadBooking.execute(id); + + return BookingScreen(viewModel: viewModel); + }, + ), + ], + ), + ], + ), + ], +); + +// From https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart +Future _redirect(BuildContext context, GoRouterState state) async { + // if the user is not logged in, they need to login + final loggedIn = await context.read().isAuthenticated; + final loggingIn = state.matchedLocation == Routes.login; + if (!loggedIn) { + return Routes.login; + } + + // if the user is logged in but still on the login page, send them to + // the home page + if (loggingIn) { + return Routes.home; + } + + // no need to redirect at all + return null; +} diff --git a/compass_app/app/lib/routing/routes.dart b/compass_app/app/lib/routing/routes.dart new file mode 100644 index 0000000..86ea0b7 --- /dev/null +++ b/compass_app/app/lib/routing/routes.dart @@ -0,0 +1,17 @@ +// 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. + +abstract final class Routes { + static const home = '/'; + static const login = '/login'; + static const search = '/$searchRelative'; + static const searchRelative = 'search'; + static const results = '/$resultsRelative'; + static const resultsRelative = 'results'; + static const activities = '/$activitiesRelative'; + static const activitiesRelative = 'activities'; + static const booking = '/$bookingRelative'; + static const bookingRelative = 'booking'; + static String bookingWithId(int id) => '$booking/$id'; +} diff --git a/compass_app/app/lib/ui/activities/view_models/activities_viewmodel.dart b/compass_app/app/lib/ui/activities/view_models/activities_viewmodel.dart new file mode 100644 index 0000000..61ecd66 --- /dev/null +++ b/compass_app/app/lib/ui/activities/view_models/activities_viewmodel.dart @@ -0,0 +1,151 @@ +// 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. + +import 'package:flutter/foundation.dart'; +import 'package:logging/logging.dart'; + +import '../../../data/repositories/activity/activity_repository.dart'; +import '../../../data/repositories/itinerary_config/itinerary_config_repository.dart'; +import '../../../domain/models/activity/activity.dart'; +import '../../../domain/models/itinerary_config/itinerary_config.dart'; +import '../../../utils/command.dart'; +import '../../../utils/result.dart'; + +class ActivitiesViewModel extends ChangeNotifier { + ActivitiesViewModel({ + required ActivityRepository activityRepository, + required ItineraryConfigRepository itineraryConfigRepository, + }) : _activityRepository = activityRepository, + _itineraryConfigRepository = itineraryConfigRepository { + loadActivities = Command0(_loadActivities)..execute(); + saveActivities = Command0(_saveActivities); + } + + final _log = Logger('ActivitiesViewModel'); + final ActivityRepository _activityRepository; + final ItineraryConfigRepository _itineraryConfigRepository; + List _daytimeActivities = []; + List _eveningActivities = []; + final Set _selectedActivities = {}; + + /// List of daytime [Activity] per destination. + List get daytimeActivities => _daytimeActivities; + + /// List of evening [Activity] per destination. + List get eveningActivities => _eveningActivities; + + /// Selected [Activity] by ref. + Set get selectedActivities => _selectedActivities; + + /// Load list of [Activity] for a [Destination] by ref. + late final Command0 loadActivities; + + /// Save list [selectedActivities] into itinerary configuration. + late final Command0 saveActivities; + + Future> _loadActivities() async { + final result = await _itineraryConfigRepository.getItineraryConfig(); + switch (result) { + case Error(): + _log.warning('Failed to load stored ItineraryConfig', result.error); + return result; + case Ok(): + } + + final destinationRef = result.value.destination; + if (destinationRef == null) { + _log.severe('Destination missing in ItineraryConfig'); + return Result.error(Exception('Destination not found')); + } + + _selectedActivities.addAll(result.value.activities); + + final resultActivities = await _activityRepository.getByDestination( + destinationRef, + ); + switch (resultActivities) { + case Ok(): + { + _daytimeActivities = resultActivities.value + .where( + (activity) => [ + TimeOfDay.any, + TimeOfDay.morning, + TimeOfDay.afternoon, + ].contains(activity.timeOfDay), + ) + .toList(); + + _eveningActivities = resultActivities.value + .where( + (activity) => [ + TimeOfDay.evening, + TimeOfDay.night, + ].contains(activity.timeOfDay), + ) + .toList(); + + _log.fine( + 'Activities (daytime: ${_daytimeActivities.length}, ' + 'evening: ${_eveningActivities.length}) loaded', + ); + } + case Error(): + { + _log.warning('Failed to load activities', resultActivities.error); + } + } + + notifyListeners(); + return resultActivities; + } + + /// Add [Activity] to selected list. + void addActivity(String activityRef) { + assert( + (_daytimeActivities + _eveningActivities).any( + (activity) => activity.ref == activityRef, + ), + "Activity $activityRef not found", + ); + _selectedActivities.add(activityRef); + _log.finest('Activity $activityRef added'); + notifyListeners(); + } + + /// Remove [Activity] from selected list. + void removeActivity(String activityRef) { + assert( + (_daytimeActivities + _eveningActivities).any( + (activity) => activity.ref == activityRef, + ), + "Activity $activityRef not found", + ); + _selectedActivities.remove(activityRef); + _log.finest('Activity $activityRef removed'); + notifyListeners(); + } + + Future> _saveActivities() async { + final resultConfig = await _itineraryConfigRepository.getItineraryConfig(); + switch (resultConfig) { + case Error(): + _log.warning( + 'Failed to load stored ItineraryConfig', + resultConfig.error, + ); + return resultConfig; + case Ok(): + } + + final itineraryConfig = resultConfig.value; + final result = await _itineraryConfigRepository.setItineraryConfig( + itineraryConfig.copyWith(activities: _selectedActivities.toList()), + ); + if (result is Error) { + _log.warning('Failed to store ItineraryConfig', result.error); + } + return result; + } +} diff --git a/compass_app/app/lib/ui/activities/widgets/activities_header.dart b/compass_app/app/lib/ui/activities/widgets/activities_header.dart new file mode 100644 index 0000000..3e91c10 --- /dev/null +++ b/compass_app/app/lib/ui/activities/widgets/activities_header.dart @@ -0,0 +1,48 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../../../routing/routes.dart'; +import '../../core/localization/applocalization.dart'; +import '../../core/themes/dimens.dart'; +import '../../core/ui/back_button.dart'; +import '../../core/ui/home_button.dart'; + +class ActivitiesHeader extends StatelessWidget { + const ActivitiesHeader({super.key}); + + @override + Widget build(BuildContext context) { + return SafeArea( + top: true, + bottom: false, + child: Padding( + padding: EdgeInsets.only( + left: Dimens.of(context).paddingScreenHorizontal, + right: Dimens.of(context).paddingScreenHorizontal, + top: Dimens.of(context).paddingScreenVertical, + bottom: Dimens.paddingVertical, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomBackButton( + onTap: () { + // Navigate to ResultsScreen and edit search + context.go(Routes.results); + }, + ), + Text( + AppLocalization.of(context).activities, + style: Theme.of(context).textTheme.titleLarge, + ), + const HomeButton(), + ], + ), + ), + ); + } +} diff --git a/compass_app/app/lib/ui/activities/widgets/activities_list.dart b/compass_app/app/lib/ui/activities/widgets/activities_list.dart new file mode 100644 index 0000000..197fe0f --- /dev/null +++ b/compass_app/app/lib/ui/activities/widgets/activities_list.dart @@ -0,0 +1,57 @@ +// 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. + +import 'package:flutter/material.dart'; + +import '../../core/themes/dimens.dart'; +import '../view_models/activities_viewmodel.dart'; +import 'activity_entry.dart'; +import 'activity_time_of_day.dart'; + +class ActivitiesList extends StatelessWidget { + const ActivitiesList({ + super.key, + required this.viewModel, + required this.activityTimeOfDay, + }); + + final ActivitiesViewModel viewModel; + final ActivityTimeOfDay activityTimeOfDay; + + @override + Widget build(BuildContext context) { + final list = switch (activityTimeOfDay) { + ActivityTimeOfDay.daytime => viewModel.daytimeActivities, + ActivityTimeOfDay.evening => viewModel.eveningActivities, + }; + return SliverPadding( + padding: EdgeInsets.only( + top: Dimens.paddingVertical, + left: Dimens.of(context).paddingScreenHorizontal, + right: Dimens.of(context).paddingScreenHorizontal, + bottom: Dimens.paddingVertical, + ), + sliver: SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + final activity = list[index]; + return Padding( + padding: EdgeInsets.only(bottom: index < list.length - 1 ? 20 : 0), + child: ActivityEntry( + key: ValueKey(activity.ref), + activity: activity, + selected: viewModel.selectedActivities.contains(activity.ref), + onChanged: (value) { + if (value!) { + viewModel.addActivity(activity.ref); + } else { + viewModel.removeActivity(activity.ref); + } + }, + ), + ); + }, childCount: list.length), + ), + ); + } +} diff --git a/compass_app/app/lib/ui/activities/widgets/activities_screen.dart b/compass_app/app/lib/ui/activities/widgets/activities_screen.dart new file mode 100644 index 0000000..f2ba884 --- /dev/null +++ b/compass_app/app/lib/ui/activities/widgets/activities_screen.dart @@ -0,0 +1,184 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../../../routing/routes.dart'; +import '../../core/localization/applocalization.dart'; +import '../../core/themes/dimens.dart'; +import '../../core/ui/error_indicator.dart'; +import '../view_models/activities_viewmodel.dart'; +import 'activities_header.dart'; +import 'activities_list.dart'; +import 'activities_title.dart'; +import 'activity_time_of_day.dart'; + +const String confirmButtonKey = 'confirm-button'; + +class ActivitiesScreen extends StatefulWidget { + const ActivitiesScreen({super.key, required this.viewModel}); + + final ActivitiesViewModel viewModel; + + @override + State createState() => _ActivitiesScreenState(); +} + +class _ActivitiesScreenState extends State { + @override + void initState() { + super.initState(); + widget.viewModel.saveActivities.addListener(_onResult); + } + + @override + void didUpdateWidget(covariant ActivitiesScreen oldWidget) { + super.didUpdateWidget(oldWidget); + oldWidget.viewModel.saveActivities.removeListener(_onResult); + widget.viewModel.saveActivities.addListener(_onResult); + } + + @override + void dispose() { + widget.viewModel.saveActivities.removeListener(_onResult); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, r) { + if (!didPop) context.go(Routes.results); + }, + child: Scaffold( + body: ListenableBuilder( + listenable: widget.viewModel.loadActivities, + builder: (context, child) { + if (widget.viewModel.loadActivities.completed) { + return child!; + } + return Column( + children: [ + const ActivitiesHeader(), + if (widget.viewModel.loadActivities.running) + const Expanded( + child: Center(child: CircularProgressIndicator()), + ), + if (widget.viewModel.loadActivities.error) + Expanded( + child: Center( + child: ErrorIndicator( + title: AppLocalization.of( + context, + ).errorWhileLoadingActivities, + label: AppLocalization.of(context).tryAgain, + onPressed: widget.viewModel.loadActivities.execute, + ), + ), + ), + ], + ); + }, + child: ListenableBuilder( + listenable: widget.viewModel, + builder: (context, child) { + return Column( + children: [ + Expanded( + child: CustomScrollView( + slivers: [ + const SliverToBoxAdapter(child: ActivitiesHeader()), + ActivitiesTitle( + viewModel: widget.viewModel, + activityTimeOfDay: ActivityTimeOfDay.daytime, + ), + ActivitiesList( + viewModel: widget.viewModel, + activityTimeOfDay: ActivityTimeOfDay.daytime, + ), + ActivitiesTitle( + viewModel: widget.viewModel, + activityTimeOfDay: ActivityTimeOfDay.evening, + ), + ActivitiesList( + viewModel: widget.viewModel, + activityTimeOfDay: ActivityTimeOfDay.evening, + ), + ], + ), + ), + _BottomArea(viewModel: widget.viewModel), + ], + ); + }, + ), + ), + ), + ); + } + + void _onResult() { + if (widget.viewModel.saveActivities.completed) { + widget.viewModel.saveActivities.clearResult(); + context.go(Routes.booking); + } + + if (widget.viewModel.saveActivities.error) { + widget.viewModel.saveActivities.clearResult(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalization.of(context).errorWhileSavingActivities), + action: SnackBarAction( + label: AppLocalization.of(context).tryAgain, + onPressed: widget.viewModel.saveActivities.execute, + ), + ), + ); + } + } +} + +class _BottomArea extends StatelessWidget { + const _BottomArea({required this.viewModel}); + + final ActivitiesViewModel viewModel; + + @override + Widget build(BuildContext context) { + return SafeArea( + bottom: true, + child: Material( + elevation: 8, + child: Padding( + padding: EdgeInsets.only( + left: Dimens.of(context).paddingScreenHorizontal, + right: Dimens.of(context).paddingScreenVertical, + top: Dimens.paddingVertical, + bottom: Dimens.of(context).paddingScreenVertical, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalization.of( + context, + ).selected(viewModel.selectedActivities.length), + style: Theme.of(context).textTheme.labelLarge, + ), + FilledButton( + key: const Key(confirmButtonKey), + onPressed: viewModel.selectedActivities.isNotEmpty + ? viewModel.saveActivities.execute + : null, + child: Text(AppLocalization.of(context).confirm), + ), + ], + ), + ), + ), + ); + } +} diff --git a/compass_app/app/lib/ui/activities/widgets/activities_title.dart b/compass_app/app/lib/ui/activities/widgets/activities_title.dart new file mode 100644 index 0000000..2c381f7 --- /dev/null +++ b/compass_app/app/lib/ui/activities/widgets/activities_title.dart @@ -0,0 +1,43 @@ +// 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. + +import 'package:flutter/material.dart'; + +import '../../core/localization/applocalization.dart'; +import '../../core/themes/dimens.dart'; +import '../view_models/activities_viewmodel.dart'; +import 'activity_time_of_day.dart'; + +class ActivitiesTitle extends StatelessWidget { + const ActivitiesTitle({ + super.key, + required this.activityTimeOfDay, + required this.viewModel, + }); + + final ActivitiesViewModel viewModel; + final ActivityTimeOfDay activityTimeOfDay; + + @override + Widget build(BuildContext context) { + final list = switch (activityTimeOfDay) { + ActivityTimeOfDay.daytime => viewModel.daytimeActivities, + ActivityTimeOfDay.evening => viewModel.eveningActivities, + }; + if (list.isEmpty) { + return const SliverToBoxAdapter(child: SizedBox()); + } + return SliverToBoxAdapter( + child: Padding( + padding: Dimens.of(context).edgeInsetsScreenHorizontal, + child: Text(_label(context)), + ), + ); + } + + String _label(BuildContext context) => switch (activityTimeOfDay) { + ActivityTimeOfDay.daytime => AppLocalization.of(context).daytime, + ActivityTimeOfDay.evening => AppLocalization.of(context).evening, + }; +} diff --git a/compass_app/app/lib/ui/activities/widgets/activity_entry.dart b/compass_app/app/lib/ui/activities/widgets/activity_entry.dart new file mode 100644 index 0000000..b561844 --- /dev/null +++ b/compass_app/app/lib/ui/activities/widgets/activity_entry.dart @@ -0,0 +1,89 @@ +// 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. + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; + +import '../../../domain/models/activity/activity.dart'; +import '../../../utils/image_error_listener.dart'; +import '../../core/ui/custom_checkbox.dart'; + +class ActivityEntry extends StatelessWidget { + const ActivityEntry({ + super.key, + required this.activity, + required this.selected, + required this.onChanged, + }); + + final Activity activity; + final bool selected; + final ValueChanged onChanged; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 80, + child: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: CachedNetworkImage( + errorWidget: (context, url, error) => _ActivityErrorPlaceholder(), + imageUrl: activity.imageUrl, + height: 80, + width: 80, + errorListener: imageErrorListener, + ), + ), + const SizedBox(width: 20), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + activity.timeOfDay.name.toUpperCase(), + style: Theme.of(context).textTheme.labelSmall, + ), + Text( + activity.name, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + ), + const SizedBox(width: 20), + CustomCheckbox( + key: ValueKey('${activity.ref}-checkbox'), + value: selected, + onChanged: onChanged, + ), + ], + ), + ); + } +} + +class _ActivityErrorPlaceholder extends StatelessWidget { + const _ActivityErrorPlaceholder(); + + @override + Widget build(BuildContext context) { + return Container( + width: 80, + height: 80, + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.broken_image_outlined, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ); + } +} diff --git a/compass_app/app/lib/ui/activities/widgets/activity_time_of_day.dart b/compass_app/app/lib/ui/activities/widgets/activity_time_of_day.dart new file mode 100644 index 0000000..04cf7df --- /dev/null +++ b/compass_app/app/lib/ui/activities/widgets/activity_time_of_day.dart @@ -0,0 +1,5 @@ +// 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. + +enum ActivityTimeOfDay { daytime, evening } diff --git a/compass_app/app/lib/ui/auth/login/view_models/login_viewmodel.dart b/compass_app/app/lib/ui/auth/login/view_models/login_viewmodel.dart new file mode 100644 index 0000000..76fc000 --- /dev/null +++ b/compass_app/app/lib/ui/auth/login/view_models/login_viewmodel.dart @@ -0,0 +1,33 @@ +// 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. + +import 'package:logging/logging.dart'; + +import '../../../../data/repositories/auth/auth_repository.dart'; +import '../../../../utils/command.dart'; +import '../../../../utils/result.dart'; + +class LoginViewModel { + LoginViewModel({required AuthRepository authRepository}) + : _authRepository = authRepository { + login = Command1(_login); + } + + final AuthRepository _authRepository; + final _log = Logger('LoginViewModel'); + + late Command1 login; + + Future> _login((String, String) credentials) async { + final (email, password) = credentials; + final result = await _authRepository.login( + email: email, + password: password, + ); + if (result is Error) { + _log.warning('Login failed! ${result.error}'); + } + return result; + } +} diff --git a/compass_app/app/lib/ui/auth/login/widgets/login_screen.dart b/compass_app/app/lib/ui/auth/login/widgets/login_screen.dart new file mode 100644 index 0000000..066fd00 --- /dev/null +++ b/compass_app/app/lib/ui/auth/login/widgets/login_screen.dart @@ -0,0 +1,115 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../../../../routing/routes.dart'; +import '../../../core/localization/applocalization.dart'; +import '../../../core/themes/dimens.dart'; +import '../view_models/login_viewmodel.dart'; +import 'tilted_cards.dart'; + +class LoginScreen extends StatefulWidget { + const LoginScreen({super.key, required this.viewModel}); + + final LoginViewModel viewModel; + + @override + State createState() => _LoginScreenState(); +} + +class _LoginScreenState extends State { + final TextEditingController _email = TextEditingController( + text: 'email@example.com', + ); + final TextEditingController _password = TextEditingController( + text: 'password', + ); + + @override + void initState() { + super.initState(); + widget.viewModel.login.addListener(_onResult); + } + + @override + void didUpdateWidget(covariant LoginScreen oldWidget) { + super.didUpdateWidget(oldWidget); + oldWidget.viewModel.login.removeListener(_onResult); + widget.viewModel.login.addListener(_onResult); + } + + @override + void dispose() { + _email.dispose(); + _password.dispose(); + widget.viewModel.login.removeListener(_onResult); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + const TiltedCards(), + Padding( + padding: Dimens.of(context).edgeInsetsScreenSymmetric, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextField(controller: _email), + const SizedBox(height: Dimens.paddingVertical), + TextField(controller: _password, obscureText: true), + const SizedBox(height: Dimens.paddingVertical), + ListenableBuilder( + listenable: widget.viewModel.login, + builder: (context, _) { + return FilledButton( + onPressed: () { + widget.viewModel.login.execute(( + _email.value.text, + _password.value.text, + )); + }, + child: Text(AppLocalization.of(context).login), + ); + }, + ), + ], + ), + ), + ], + ), + ), + ); + } + + void _onResult() { + if (widget.viewModel.login.completed) { + widget.viewModel.login.clearResult(); + context.go(Routes.home); + } + + if (widget.viewModel.login.error) { + widget.viewModel.login.clearResult(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalization.of(context).errorWhileLogin), + action: SnackBarAction( + label: AppLocalization.of(context).tryAgain, + onPressed: () => widget.viewModel.login.execute(( + _email.value.text, + _password.value.text, + )), + ), + ), + ); + } + } +} diff --git a/compass_app/app/lib/ui/auth/login/widgets/tilted_cards.dart b/compass_app/app/lib/ui/auth/login/widgets/tilted_cards.dart new file mode 100644 index 0000000..4d9e934 --- /dev/null +++ b/compass_app/app/lib/ui/auth/login/widgets/tilted_cards.dart @@ -0,0 +1,97 @@ +// 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. + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../../../utils/image_error_listener.dart'; + +class TiltedCards extends StatelessWidget { + const TiltedCards({super.key}); + + @override + Widget build(BuildContext context) { + return ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 300), + child: const AspectRatio( + aspectRatio: 1, + child: Stack( + alignment: Alignment.center, + children: [ + Positioned( + left: 0, + child: _Card( + imageUrl: 'https://rstr.in/google/tripedia/g2i0BsYPKW-', + width: 200, + height: 273, + tilt: -3.83 / 360, + ), + ), + Positioned( + right: 0, + child: _Card( + imageUrl: 'https://rstr.in/google/tripedia/980sqNgaDRK', + width: 180, + height: 230, + tilt: 3.46 / 360, + ), + ), + _Card( + imageUrl: 'https://rstr.in/google/tripedia/pHfPmf3o5NU', + width: 225, + height: 322, + tilt: 0, + showTitle: true, + ), + ], + ), + ), + ); + } +} + +class _Card extends StatelessWidget { + const _Card({ + required this.imageUrl, + required this.width, + required this.height, + required this.tilt, + this.showTitle = false, + }); + + final double tilt; + final double width; + final double height; + final String imageUrl; + final bool showTitle; + + @override + Widget build(BuildContext context) { + return RotationTransition( + turns: AlwaysStoppedAnimation(tilt), + child: SizedBox( + width: width, + height: height, + child: ClipRRect( + borderRadius: BorderRadius.circular(20), + child: Stack( + fit: StackFit.expand, + alignment: Alignment.center, + children: [ + CachedNetworkImage( + imageUrl: imageUrl, + fit: BoxFit.cover, + color: showTitle ? Colors.black.withValues(alpha: 0.5) : null, + colorBlendMode: showTitle ? BlendMode.darken : null, + errorListener: imageErrorListener, + ), + if (showTitle) Center(child: SvgPicture.asset('assets/logo.svg')), + ], + ), + ), + ), + ); + } +} diff --git a/compass_app/app/lib/ui/auth/logout/view_models/logout_viewmodel.dart b/compass_app/app/lib/ui/auth/logout/view_models/logout_viewmodel.dart new file mode 100644 index 0000000..c0f8566 --- /dev/null +++ b/compass_app/app/lib/ui/auth/logout/view_models/logout_viewmodel.dart @@ -0,0 +1,35 @@ +// 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. + +import '../../../../data/repositories/auth/auth_repository.dart'; +import '../../../../data/repositories/itinerary_config/itinerary_config_repository.dart'; +import '../../../../domain/models/itinerary_config/itinerary_config.dart'; +import '../../../../utils/command.dart'; +import '../../../../utils/result.dart'; + +class LogoutViewModel { + LogoutViewModel({ + required AuthRepository authRepository, + required ItineraryConfigRepository itineraryConfigRepository, + }) : _authLogoutRepository = authRepository, + _itineraryConfigRepository = itineraryConfigRepository { + logout = Command0(_logout); + } + final AuthRepository _authLogoutRepository; + final ItineraryConfigRepository _itineraryConfigRepository; + late Command0 logout; + + Future _logout() async { + final result = await _authLogoutRepository.logout(); + switch (result) { + case Ok(): + // clear stored itinerary config + return _itineraryConfigRepository.setItineraryConfig( + const ItineraryConfig(), + ); + case Error(): + return result; + } + } +} diff --git a/compass_app/app/lib/ui/auth/logout/widgets/logout_button.dart b/compass_app/app/lib/ui/auth/logout/widgets/logout_button.dart new file mode 100644 index 0000000..4175871 --- /dev/null +++ b/compass_app/app/lib/ui/auth/logout/widgets/logout_button.dart @@ -0,0 +1,85 @@ +// 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. + +import 'package:flutter/material.dart'; + +import '../../../core/localization/applocalization.dart'; +import '../../../core/themes/colors.dart'; +import '../view_models/logout_viewmodel.dart'; + +class LogoutButton extends StatefulWidget { + const LogoutButton({super.key, required this.viewModel}); + + final LogoutViewModel viewModel; + + @override + State createState() => _LogoutButtonState(); +} + +class _LogoutButtonState extends State { + @override + void initState() { + super.initState(); + widget.viewModel.logout.addListener(_onResult); + } + + @override + void didUpdateWidget(covariant LogoutButton oldWidget) { + super.didUpdateWidget(oldWidget); + oldWidget.viewModel.logout.removeListener(_onResult); + widget.viewModel.logout.addListener(_onResult); + } + + @override + void dispose() { + widget.viewModel.logout.removeListener(_onResult); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 40.0, + width: 40.0, + child: DecoratedBox( + decoration: BoxDecoration( + border: Border.all(color: AppColors.grey1), + borderRadius: BorderRadius.circular(8.0), + color: Colors.transparent, + ), + child: InkResponse( + borderRadius: BorderRadius.circular(8.0), + onTap: () { + widget.viewModel.logout.execute(); + }, + child: Center( + child: Icon( + size: 24.0, + Icons.logout, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + ), + ), + ); + } + + void _onResult() { + // We do not need to navigate to `/login` on logout, + // it is done automatically by GoRouter. + + if (widget.viewModel.logout.error) { + widget.viewModel.logout.clearResult(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalization.of(context).errorWhileLogout), + action: SnackBarAction( + label: AppLocalization.of(context).tryAgain, + onPressed: widget.viewModel.logout.execute, + ), + ), + ); + } + } +} diff --git a/compass_app/app/lib/ui/booking/view_models/booking_viewmodel.dart b/compass_app/app/lib/ui/booking/view_models/booking_viewmodel.dart new file mode 100644 index 0000000..21d2e39 --- /dev/null +++ b/compass_app/app/lib/ui/booking/view_models/booking_viewmodel.dart @@ -0,0 +1,89 @@ +// 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. + +import 'package:flutter/foundation.dart'; +import 'package:logging/logging.dart'; + +import '../../../data/repositories/booking/booking_repository.dart'; +import '../../../data/repositories/itinerary_config/itinerary_config_repository.dart'; +import '../../../domain/models/booking/booking.dart'; +import '../../../domain/models/itinerary_config/itinerary_config.dart'; +import '../../../domain/use_cases/booking/booking_create_use_case.dart'; +import '../../../domain/use_cases/booking/booking_share_use_case.dart'; +import '../../../utils/command.dart'; +import '../../../utils/result.dart'; + +class BookingViewModel extends ChangeNotifier { + BookingViewModel({ + required BookingCreateUseCase createBookingUseCase, + required BookingShareUseCase shareBookingUseCase, + required ItineraryConfigRepository itineraryConfigRepository, + required BookingRepository bookingRepository, + }) : _createUseCase = createBookingUseCase, + _shareUseCase = shareBookingUseCase, + _itineraryConfigRepository = itineraryConfigRepository, + _bookingRepository = bookingRepository { + createBooking = Command0(_createBooking); + shareBooking = Command0(() => _shareUseCase.shareBooking(_booking!)); + loadBooking = Command1(_load); + } + + final BookingCreateUseCase _createUseCase; + final BookingShareUseCase _shareUseCase; + final ItineraryConfigRepository _itineraryConfigRepository; + final BookingRepository _bookingRepository; + final _log = Logger('BookingViewModel'); + Booking? _booking; + + Booking? get booking => _booking; + + /// Creates a booking from the ItineraryConfig + /// and saves it to the user bookins + late final Command0 createBooking; + + /// Loads booking by id + late final Command1 loadBooking; + + /// Share the current booking using the OS share dialog. + late final Command0 shareBooking; + + Future> _createBooking() async { + _log.fine('Loading booking'); + final itineraryConfig = await _itineraryConfigRepository + .getItineraryConfig(); + switch (itineraryConfig) { + case Ok(): + _log.fine('Loaded stored ItineraryConfig'); + final result = await _createUseCase.createFrom(itineraryConfig.value); + switch (result) { + case Ok(): + _log.fine('Created Booking'); + _booking = result.value; + notifyListeners(); + return const Result.ok(null); + case Error(): + _log.warning('Booking error: ${result.error}'); + notifyListeners(); + return Result.error(result.error); + } + case Error(): + _log.warning('ItineraryConfig error: ${itineraryConfig.error}'); + notifyListeners(); + return Result.error(itineraryConfig.error); + } + } + + Future> _load(int id) async { + final result = await _bookingRepository.getBooking(id); + switch (result) { + case Ok(): + _log.fine('Loaded booking $id'); + _booking = result.value; + notifyListeners(); + case Error(): + _log.warning('Failed to load booking $id'); + } + return result; + } +} diff --git a/compass_app/app/lib/ui/booking/widgets/booking_body.dart b/compass_app/app/lib/ui/booking/widgets/booking_body.dart new file mode 100644 index 0000000..0e55ce1 --- /dev/null +++ b/compass_app/app/lib/ui/booking/widgets/booking_body.dart @@ -0,0 +1,96 @@ +// 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. + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; + +import '../../../domain/models/activity/activity.dart'; +import '../../../utils/image_error_listener.dart'; +import '../../core/themes/dimens.dart'; +import '../view_models/booking_viewmodel.dart'; +import 'booking_header.dart'; + +class BookingBody extends StatelessWidget { + const BookingBody({super.key, required this.viewModel}); + + final BookingViewModel viewModel; + + @override + Widget build(BuildContext context) { + return ListenableBuilder( + listenable: viewModel, + builder: (context, _) { + final booking = viewModel.booking; + if (booking == null) return const SizedBox(); + return CustomScrollView( + slivers: [ + SliverToBoxAdapter(child: BookingHeader(booking: booking)), + SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + final activity = booking.activity[index]; + return _Activity(activity: activity); + }, childCount: booking.activity.length), + ), + const SliverToBoxAdapter(child: SizedBox(height: 200)), + ], + ); + }, + ); + } +} + +class _Activity extends StatelessWidget { + const _Activity({required this.activity}); + + final Activity activity; + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.only( + top: Dimens.paddingVertical, + left: Dimens.of(context).paddingScreenHorizontal, + right: Dimens.of(context).paddingScreenHorizontal, + ), + child: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: CachedNetworkImage( + imageUrl: activity.imageUrl, + height: 80, + width: 80, + errorListener: imageErrorListener, + ), + ), + const SizedBox(width: 20), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + activity.timeOfDay.name.toUpperCase(), + style: Theme.of(context).textTheme.labelSmall, + ), + Text( + activity.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodyMedium, + ), + Text( + activity.description, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/compass_app/app/lib/ui/booking/widgets/booking_header.dart b/compass_app/app/lib/ui/booking/widgets/booking_header.dart new file mode 100644 index 0000000..0c889ae --- /dev/null +++ b/compass_app/app/lib/ui/booking/widgets/booking_header.dart @@ -0,0 +1,171 @@ +// 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. + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; + +import '../../../domain/models/booking/booking.dart'; +import '../../../utils/image_error_listener.dart'; +import '../../core/localization/applocalization.dart'; +import '../../core/themes/colors.dart'; +import '../../core/themes/dimens.dart'; +import '../../core/ui/date_format_start_end.dart'; +import '../../core/ui/home_button.dart'; +import '../../core/ui/tag_chip.dart'; + +class BookingHeader extends StatelessWidget { + const BookingHeader({super.key, required this.booking}); + + final Booking booking; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _Top(booking: booking), + Padding( + padding: Dimens.of(context).edgeInsetsScreenHorizontal, + child: Text( + booking.destination.knownFor, + style: Theme.of(context).textTheme.bodyLarge, + ), + ), + const SizedBox(height: Dimens.paddingVertical), + _Tags(booking: booking), + const SizedBox(height: Dimens.paddingVertical), + Padding( + padding: Dimens.of(context).edgeInsetsScreenHorizontal, + child: Text( + AppLocalization.of(context).yourChosenActivities, + style: Theme.of(context).textTheme.headlineSmall, + ), + ), + ], + ); + } +} + +class _Top extends StatelessWidget { + const _Top({required this.booking}); + + final Booking booking; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 260, + child: Stack( + fit: StackFit.expand, + children: [ + _HeaderImage(booking: booking), + const _Gradient(), + _Headline(booking: booking), + Positioned( + right: Dimens.of(context).paddingScreenHorizontal, + top: Dimens.of(context).paddingScreenVertical, + child: const SafeArea(top: true, child: HomeButton(blur: true)), + ), + ], + ), + ); + } +} + +class _Tags extends StatelessWidget { + const _Tags({required this.booking}); + + final Booking booking; + + @override + Widget build(BuildContext context) { + final brightness = Theme.of(context).brightness; + final chipColor = switch (brightness) { + Brightness.dark => AppColors.whiteTransparent, + Brightness.light => AppColors.blackTransparent, + }; + return Padding( + padding: Dimens.of(context).edgeInsetsScreenHorizontal, + child: Wrap( + spacing: 6, + runSpacing: 6, + children: booking.destination.tags + .map( + (tag) => TagChip( + tag: tag, + fontSize: 16, + height: 32, + chipColor: chipColor, + onChipColor: Theme.of(context).colorScheme.onSurface, + ), + ) + .toList(), + ), + ); + } +} + +class _Headline extends StatelessWidget { + const _Headline({required this.booking}); + + final Booking booking; + + @override + Widget build(BuildContext context) { + return Align( + alignment: AlignmentDirectional.bottomStart, + child: Padding( + padding: Dimens.of(context).edgeInsetsScreenSymmetric, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + booking.destination.name, + style: Theme.of(context).textTheme.headlineLarge, + ), + Text( + dateFormatStartEnd( + DateTimeRange(start: booking.startDate, end: booking.endDate), + ), + style: Theme.of(context).textTheme.headlineSmall, + ), + ], + ), + ), + ); + } +} + +class _HeaderImage extends StatelessWidget { + const _HeaderImage({required this.booking}); + + final Booking booking; + + @override + Widget build(BuildContext context) { + return CachedNetworkImage( + fit: BoxFit.fitWidth, + imageUrl: booking.destination.imageUrl, + errorListener: imageErrorListener, + ); + } +} + +class _Gradient extends StatelessWidget { + const _Gradient(); + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Colors.transparent, Theme.of(context).colorScheme.surface], + ), + ), + ); + } +} diff --git a/compass_app/app/lib/ui/booking/widgets/booking_screen.dart b/compass_app/app/lib/ui/booking/widgets/booking_screen.dart new file mode 100644 index 0000000..0180231 --- /dev/null +++ b/compass_app/app/lib/ui/booking/widgets/booking_screen.dart @@ -0,0 +1,112 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../../../routing/routes.dart'; +import '../../core/localization/applocalization.dart'; +import '../../core/ui/error_indicator.dart'; +import '../view_models/booking_viewmodel.dart'; +import 'booking_body.dart'; + +class BookingScreen extends StatefulWidget { + const BookingScreen({super.key, required this.viewModel}); + + final BookingViewModel viewModel; + + @override + State createState() => _BookingScreenState(); +} + +class _BookingScreenState extends State { + @override + void initState() { + super.initState(); + widget.viewModel.shareBooking.addListener(_listener); + } + + @override + void dispose() { + widget.viewModel.shareBooking.removeListener(_listener); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, r) { + // Back navigation always goes to home + if (!didPop) context.go(Routes.home); + }, + child: Scaffold( + floatingActionButton: ListenableBuilder( + listenable: widget.viewModel, + builder: (context, _) => FloatingActionButton.extended( + // Workaround for https://github.com/flutter/flutter/issues/115358#issuecomment-2117157419 + heroTag: null, + key: const ValueKey('share-button'), + onPressed: widget.viewModel.booking != null + ? widget.viewModel.shareBooking.execute + : null, + label: Text(AppLocalization.of(context).shareTrip), + icon: const Icon(Icons.share_outlined), + ), + ), + body: ListenableBuilder( + // Listen to changes in both commands + listenable: Listenable.merge([ + widget.viewModel.createBooking, + widget.viewModel.loadBooking, + ]), + builder: (context, child) { + // If either command is running, show progress indicator + if (widget.viewModel.createBooking.running || + widget.viewModel.loadBooking.running) { + return const Center(child: CircularProgressIndicator()); + } + // If fails to create booking, tap to try again + if (widget.viewModel.createBooking.error) { + return Center( + child: ErrorIndicator( + title: AppLocalization.of(context).errorWhileLoadingBooking, + label: AppLocalization.of(context).tryAgain, + onPressed: widget.viewModel.createBooking.execute, + ), + ); + } + // If existing booking fails to load, tap to go /home + if (widget.viewModel.loadBooking.error) { + return Center( + child: ErrorIndicator( + title: AppLocalization.of(context).errorWhileLoadingBooking, + label: AppLocalization.of(context).close, + onPressed: () => context.go(Routes.home), + ), + ); + } + return child!; + }, + child: BookingBody(viewModel: widget.viewModel), + ), + ), + ); + } + + void _listener() { + if (widget.viewModel.shareBooking.error) { + widget.viewModel.shareBooking.clearResult(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalization.of(context).errorWhileSharing), + action: SnackBarAction( + label: AppLocalization.of(context).tryAgain, + onPressed: widget.viewModel.shareBooking.execute, + ), + ), + ); + } + } +} diff --git a/compass_app/app/lib/ui/core/localization/applocalization.dart b/compass_app/app/lib/ui/core/localization/applocalization.dart new file mode 100644 index 0000000..d9ecd31 --- /dev/null +++ b/compass_app/app/lib/ui/core/localization/applocalization.dart @@ -0,0 +1,119 @@ +// 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. + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +class AppLocalization { + static AppLocalization of(BuildContext context) { + return Localizations.of(context, AppLocalization)!; + } + + static const _strings = { + 'activities': 'Activities', + 'addDates': 'Add Dates', + 'bookingDeleted': 'Booking deleted', + 'bookNewTrip': 'Book New Trip', + 'close': 'Close', + 'confirm': 'Confirm', + 'daytime': 'Daytime', + 'errorWhileDeletingBooking': 'Error while deleting booking', + 'errorWhileLoadingActivities': 'Error while loading activities', + 'errorWhileLoadingBooking': 'Error while loading booking', + 'errorWhileLoadingContinents': 'Error while loading continents', + 'errorWhileLoadingDestinations': 'Error while loading destinations', + 'errorWhileLoadingHome': 'Error while loading home', + 'errorWhileLogin': 'Error while trying to login', + 'errorWhileLogout': 'Error while trying to logout', + 'errorWhileSavingActivities': 'Error while saving activities', + 'errorWhileSavingItinerary': 'Error while saving itinerary', + 'errorWhileSharing': 'Error while sharing booking', + 'evening': 'Evening', + 'login': 'Login', + 'nameTrips': '{name}\'s Trips', + 'search': 'Search', + 'searchDestination': 'Search destination', + 'selected': '{1} selected', + 'shareTrip': 'Share Trip', + 'tryAgain': 'Try again', + 'yourChosenActivities': 'Your chosen activities', + 'when': 'When', + }; + + // If string for "label" does not exist, will show "[LABEL]" + static String _get(String label) => + _strings[label] ?? '[${label.toUpperCase()}]'; + + String get activities => _get('activities'); + + String get addDates => _get('addDates'); + + String get confirm => _get('confirm'); + + String get daytime => _get('daytime'); + + String get errorWhileLoadingActivities => _get('errorWhileLoadingActivities'); + + String get errorWhileLoadingBooking => _get('errorWhileLoadingBooking'); + + String get errorWhileLoadingContinents => _get('errorWhileLoadingContinents'); + + String get errorWhileLoadingDestinations => + _get('errorWhileLoadingDestinations'); + + String get errorWhileSavingActivities => _get('errorWhileSavingActivities'); + + String get errorWhileSavingItinerary => _get('errorWhileSavingItinerary'); + + String get evening => _get('evening'); + + String get search => _get('search'); + + String get searchDestination => _get('searchDestination'); + + String get shareTrip => _get('shareTrip'); + + String get tryAgain => _get('tryAgain'); + + String get yourChosenActivities => _get('yourChosenActivities'); + + String get when => _get('when'); + + String get errorWhileLogin => _get('errorWhileLogin'); + + String get login => _get('login'); + + String get errorWhileLogout => _get('errorWhileLogout'); + + String get close => _get('close'); + + String get errorWhileSharing => _get('errorWhileSharing'); + + String get bookNewTrip => _get('bookNewTrip'); + + String get errorWhileLoadingHome => _get('errorWhileLoadingHome'); + + String get bookingDeleted => _get('bookingDeleted'); + + String get errorWhileDeletingBooking => _get('errorWhileDeletingBooking'); + + String nameTrips(String name) => _get('nameTrips').replaceAll('{name}', name); + + String selected(int value) => + _get('selected').replaceAll('{1}', value.toString()); +} + +class AppLocalizationDelegate extends LocalizationsDelegate { + @override + bool isSupported(Locale locale) => locale.languageCode == 'en'; + + @override + Future load(Locale locale) { + return SynchronousFuture(AppLocalization()); + } + + @override + bool shouldReload(covariant LocalizationsDelegate old) => + false; +} diff --git a/compass_app/app/lib/ui/core/themes/colors.dart b/compass_app/app/lib/ui/core/themes/colors.dart new file mode 100644 index 0000000..817728a --- /dev/null +++ b/compass_app/app/lib/ui/core/themes/colors.dart @@ -0,0 +1,42 @@ +// 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. + +import 'package:flutter/material.dart'; + +abstract final class AppColors { + static const black1 = Color(0xFF101010); + static const white1 = Color(0xFFFFF7FA); + static const grey1 = Color(0xFFF2F2F2); + static const grey2 = Color(0xFF4D4D4D); + static const grey3 = Color(0xFFA4A4A4); + static const whiteTransparent = Color( + 0x4DFFFFFF, + ); // Figma rgba(255, 255, 255, 0.3) + static const blackTransparent = Color(0x4D000000); + static const red1 = Color(0xFFE74C3C); + + static const lightColorScheme = ColorScheme( + brightness: Brightness.light, + primary: AppColors.black1, + onPrimary: AppColors.white1, + secondary: AppColors.black1, + onSecondary: AppColors.white1, + surface: Colors.white, + onSurface: AppColors.black1, + error: Colors.white, + onError: Colors.red, + ); + + static const darkColorScheme = ColorScheme( + brightness: Brightness.dark, + primary: AppColors.white1, + onPrimary: AppColors.black1, + secondary: AppColors.white1, + onSecondary: AppColors.black1, + surface: AppColors.black1, + onSurface: Colors.white, + error: Colors.black, + onError: AppColors.red1, + ); +} diff --git a/compass_app/app/lib/ui/core/themes/dimens.dart b/compass_app/app/lib/ui/core/themes/dimens.dart new file mode 100644 index 0000000..ea5d101 --- /dev/null +++ b/compass_app/app/lib/ui/core/themes/dimens.dart @@ -0,0 +1,71 @@ +// 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. + +import 'package:flutter/material.dart'; + +abstract final class Dimens { + const Dimens(); + + /// General horizontal padding used to separate UI items + static const paddingHorizontal = 20.0; + + /// General vertical padding used to separate UI items + static const paddingVertical = 24.0; + + /// Horizontal padding for screen edges + double get paddingScreenHorizontal; + + /// Vertical padding for screen edges + double get paddingScreenVertical; + + double get profilePictureSize; + + /// Horizontal symmetric padding for screen edges + EdgeInsets get edgeInsetsScreenHorizontal => + EdgeInsets.symmetric(horizontal: paddingScreenHorizontal); + + /// Symmetric padding for screen edges + EdgeInsets get edgeInsetsScreenSymmetric => EdgeInsets.symmetric( + horizontal: paddingScreenHorizontal, + vertical: paddingScreenVertical, + ); + + static const Dimens desktop = _DimensDesktop(); + static const Dimens mobile = _DimensMobile(); + + /// Get dimensions definition based on screen size + factory Dimens.of(BuildContext context) => + switch (MediaQuery.sizeOf(context).width) { + > 600 && < 840 => desktop, + _ => mobile, + }; +} + +/// Mobile dimensions +final class _DimensMobile extends Dimens { + @override + final double paddingScreenHorizontal = Dimens.paddingHorizontal; + + @override + final double paddingScreenVertical = Dimens.paddingVertical; + + @override + final double profilePictureSize = 64.0; + + const _DimensMobile(); +} + +/// Desktop/Web dimensions +final class _DimensDesktop extends Dimens { + @override + final double paddingScreenHorizontal = 100.0; + + @override + final double paddingScreenVertical = 64.0; + + @override + final double profilePictureSize = 128.0; + + const _DimensDesktop(); +} diff --git a/compass_app/app/lib/ui/core/themes/theme.dart b/compass_app/app/lib/ui/core/themes/theme.dart new file mode 100644 index 0000000..79c4a54 --- /dev/null +++ b/compass_app/app/lib/ui/core/themes/theme.dart @@ -0,0 +1,68 @@ +// 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. + +import 'package:flutter/material.dart'; + +import '../ui/tag_chip.dart'; +import 'colors.dart'; + +abstract final class AppTheme { + static const _textTheme = TextTheme( + headlineLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.w500), + headlineSmall: TextStyle(fontSize: 18, fontWeight: FontWeight.w400), + titleMedium: TextStyle(fontSize: 18, fontWeight: FontWeight.w500), + bodyLarge: TextStyle(fontSize: 18, fontWeight: FontWeight.w400), + bodyMedium: TextStyle(fontSize: 16, fontWeight: FontWeight.w400), + bodySmall: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: AppColors.grey3, + ), + labelSmall: TextStyle( + fontSize: 10, + fontWeight: FontWeight.w500, + color: AppColors.grey3, + ), + labelLarge: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w400, + color: AppColors.grey3, + ), + ); + + static const _inputDecorationTheme = InputDecorationTheme( + hintStyle: TextStyle( + // grey3 works for both light and dark themes + color: AppColors.grey3, + fontSize: 18.0, + fontWeight: FontWeight.w400, + ), + ); + + static ThemeData lightTheme = ThemeData( + brightness: Brightness.light, + colorScheme: AppColors.lightColorScheme, + textTheme: _textTheme, + inputDecorationTheme: _inputDecorationTheme, + extensions: [ + TagChipTheme( + chipColor: AppColors.whiteTransparent, + onChipColor: Colors.white, + ), + ], + ); + + static ThemeData darkTheme = ThemeData( + brightness: Brightness.dark, + colorScheme: AppColors.darkColorScheme, + textTheme: _textTheme, + inputDecorationTheme: _inputDecorationTheme, + extensions: [ + TagChipTheme( + chipColor: AppColors.blackTransparent, + onChipColor: Colors.white, + ), + ], + ); +} diff --git a/compass_app/app/lib/ui/core/ui/back_button.dart b/compass_app/app/lib/ui/core/ui/back_button.dart new file mode 100644 index 0000000..8bb78f7 --- /dev/null +++ b/compass_app/app/lib/ui/core/ui/back_button.dart @@ -0,0 +1,59 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../themes/colors.dart'; +import 'blur_filter.dart'; + +/// Custom back button to pop navigation. +class CustomBackButton extends StatelessWidget { + const CustomBackButton({super.key, this.onTap, this.blur = false}); + + final bool blur; + final GestureTapCallback? onTap; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 40.0, + width: 40.0, + child: Stack( + children: [ + if (blur) + ClipRect( + child: BackdropFilter( + filter: kBlurFilter, + child: const SizedBox(height: 40.0, width: 40.0), + ), + ), + DecoratedBox( + decoration: BoxDecoration( + border: Border.all(color: AppColors.grey1), + borderRadius: BorderRadius.circular(8.0), + ), + child: InkWell( + borderRadius: BorderRadius.circular(8.0), + onTap: () { + if (onTap != null) { + onTap!(); + } else { + context.pop(); + } + }, + child: Center( + child: Icon( + size: 24.0, + Icons.arrow_back, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/compass_app/app/lib/ui/core/ui/blur_filter.dart b/compass_app/app/lib/ui/core/ui/blur_filter.dart new file mode 100644 index 0000000..5ceccd6 --- /dev/null +++ b/compass_app/app/lib/ui/core/ui/blur_filter.dart @@ -0,0 +1,7 @@ +// 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. + +import 'dart:ui'; + +final kBlurFilter = ImageFilter.blur(sigmaX: 2, sigmaY: 2); diff --git a/compass_app/app/lib/ui/core/ui/custom_checkbox.dart b/compass_app/app/lib/ui/core/ui/custom_checkbox.dart new file mode 100644 index 0000000..515e91a --- /dev/null +++ b/compass_app/app/lib/ui/core/ui/custom_checkbox.dart @@ -0,0 +1,50 @@ +// 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. + +import 'package:flutter/material.dart'; + +import '../themes/colors.dart'; + +class CustomCheckbox extends StatelessWidget { + const CustomCheckbox({ + super.key, + required this.value, + required this.onChanged, + }); + + final bool value; + final ValueChanged onChanged; + + @override + Widget build(BuildContext context) { + return InkResponse( + radius: 24, + onTap: () => onChanged(!value), + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + border: Border.all(color: AppColors.grey3), + ), + child: Material( + borderRadius: BorderRadius.circular(24), + color: value + ? Theme.of(context).colorScheme.primary + : Colors.transparent, + child: SizedBox( + width: 24, + height: 24, + child: Visibility( + visible: value, + child: Icon( + Icons.check, + size: 14, + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + ), + ), + ); + } +} diff --git a/compass_app/app/lib/ui/core/ui/date_format_start_end.dart b/compass_app/app/lib/ui/core/ui/date_format_start_end.dart new file mode 100644 index 0000000..2b2d00e --- /dev/null +++ b/compass_app/app/lib/ui/core/ui/date_format_start_end.dart @@ -0,0 +1,24 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +final _dateFormatDay = DateFormat('d'); +final _dateFormatDayMonth = DateFormat('d MMM'); + +String dateFormatStartEnd(DateTimeRange dateTimeRange) { + final start = dateTimeRange.start; + final end = dateTimeRange.end; + + final dayMonthEnd = _dateFormatDayMonth.format(end); + + if (start.month == end.month) { + final dayStart = _dateFormatDay.format(start); + return '$dayStart - $dayMonthEnd'; + } + + final dayMonthStart = _dateFormatDayMonth.format(start); + return '$dayMonthStart - $dayMonthEnd'; +} diff --git a/compass_app/app/lib/ui/core/ui/error_indicator.dart b/compass_app/app/lib/ui/core/ui/error_indicator.dart new file mode 100644 index 0000000..5c6692f --- /dev/null +++ b/compass_app/app/lib/ui/core/ui/error_indicator.dart @@ -0,0 +1,61 @@ +// 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. + +import 'package:flutter/material.dart'; + +import '../themes/colors.dart'; + +class ErrorIndicator extends StatelessWidget { + const ErrorIndicator({ + super.key, + required this.title, + required this.label, + required this.onPressed, + }); + + final String title; + final String label; + final VoidCallback onPressed; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IntrinsicWidth( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Row( + children: [ + Icon( + Icons.error_outline, + color: Theme.of(context).colorScheme.onError, + ), + const SizedBox(width: 10), + Text( + title, + style: TextStyle( + color: Theme.of(context).colorScheme.onError, + ), + ), + ], + ), + ), + ), + ), + const SizedBox(height: 10), + FilledButton( + onPressed: onPressed, + style: const ButtonStyle( + backgroundColor: WidgetStatePropertyAll(AppColors.red1), + foregroundColor: WidgetStatePropertyAll(Colors.white), + ), + child: Text(label), + ), + ], + ); + } +} diff --git a/compass_app/app/lib/ui/core/ui/home_button.dart b/compass_app/app/lib/ui/core/ui/home_button.dart new file mode 100644 index 0000000..a7d4efa --- /dev/null +++ b/compass_app/app/lib/ui/core/ui/home_button.dart @@ -0,0 +1,57 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../../../routing/routes.dart'; +import '../themes/colors.dart'; +import 'blur_filter.dart'; + +/// Home button to navigate back to the '/' path. +class HomeButton extends StatelessWidget { + const HomeButton({super.key, this.blur = false}); + + final bool blur; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 40.0, + width: 40.0, + child: Stack( + fit: StackFit.expand, + children: [ + if (blur) + ClipRect( + child: BackdropFilter( + filter: kBlurFilter, + child: const SizedBox(height: 40.0, width: 40.0), + ), + ), + DecoratedBox( + decoration: BoxDecoration( + border: Border.all(color: AppColors.grey1), + borderRadius: BorderRadius.circular(8.0), + color: Colors.transparent, + ), + child: InkWell( + borderRadius: BorderRadius.circular(8.0), + onTap: () { + context.go(Routes.home); + }, + child: Center( + child: Icon( + size: 24.0, + Icons.home_outlined, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/compass_app/app/lib/ui/core/ui/scroll_behavior.dart b/compass_app/app/lib/ui/core/ui/scroll_behavior.dart new file mode 100644 index 0000000..6d83424 --- /dev/null +++ b/compass_app/app/lib/ui/core/ui/scroll_behavior.dart @@ -0,0 +1,17 @@ +// 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. + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +/// Custom scroll behavior to allow dragging with mouse. +/// Necessary to allow dragging with mouse on Continents carousel. +class AppCustomScrollBehavior extends MaterialScrollBehavior { + @override + Set get dragDevices => { + PointerDeviceKind.touch, + // Allow to drag with mouse on Regions carousel + PointerDeviceKind.mouse, + }; +} diff --git a/compass_app/app/lib/ui/core/ui/search_bar.dart b/compass_app/app/lib/ui/core/ui/search_bar.dart new file mode 100644 index 0000000..1c50779 --- /dev/null +++ b/compass_app/app/lib/ui/core/ui/search_bar.dart @@ -0,0 +1,107 @@ +// 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. + +import 'package:flutter/material.dart'; + +import '../../../domain/models/itinerary_config/itinerary_config.dart'; +import '../localization/applocalization.dart'; +import '../themes/colors.dart'; +import '../themes/dimens.dart'; +import 'date_format_start_end.dart'; +import 'home_button.dart'; + +/// Application top search bar. +/// +/// Displays a search bar with the current configuration. +/// Includes [HomeButton] to navigate back to the '/' path. +class AppSearchBar extends StatelessWidget { + const AppSearchBar({super.key, this.config, this.onTap}); + + final ItineraryConfig? config; + final GestureTapCallback? onTap; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: InkWell( + borderRadius: BorderRadius.circular(16.0), + onTap: onTap, + child: Container( + height: 64, + decoration: BoxDecoration( + border: Border.all(color: AppColors.grey1), + borderRadius: BorderRadius.circular(16.0), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: Dimens.paddingHorizontal, + ), + child: Align( + alignment: AlignmentDirectional.centerStart, + child: _QueryText(config: config), + ), + ), + ), + ), + ), + const SizedBox(width: 10), + const HomeButton(), + ], + ); + } +} + +class _QueryText extends StatelessWidget { + const _QueryText({required this.config}); + + final ItineraryConfig? config; + + @override + Widget build(BuildContext context) { + if (config == null) { + return const _EmptySearch(); + } + + final ItineraryConfig(:continent, :startDate, :endDate, :guests) = config!; + if (startDate == null || + endDate == null || + guests == null || + continent == null) { + return const _EmptySearch(); + } + + return Text( + '$continent - ' + '${dateFormatStartEnd(DateTimeRange(start: startDate, end: endDate))} - ' + 'Guests: $guests', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodyLarge, + ); + } +} + +class _EmptySearch extends StatelessWidget { + const _EmptySearch(); + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const Icon(Icons.search), + const SizedBox(width: 12), + Expanded( + child: Text( + AppLocalization.of(context).searchDestination, + textAlign: TextAlign.start, + style: Theme.of(context).inputDecorationTheme.hintStyle, + ), + ), + ], + ); + } +} diff --git a/compass_app/app/lib/ui/core/ui/tag_chip.dart b/compass_app/app/lib/ui/core/ui/tag_chip.dart new file mode 100644 index 0000000..1979909 --- /dev/null +++ b/compass_app/app/lib/ui/core/ui/tag_chip.dart @@ -0,0 +1,145 @@ +// 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. + +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +import '../themes/colors.dart'; + +class TagChip extends StatelessWidget { + const TagChip({ + super.key, + required this.tag, + this.fontSize = 10, + this.height = 20, + this.chipColor, + this.onChipColor, + }); + + final String tag; + + final double fontSize; + final double height; + final Color? chipColor; + final Color? onChipColor; + + @override + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: BorderRadius.circular(height / 2), + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3), + child: DecoratedBox( + decoration: BoxDecoration( + color: + chipColor ?? + Theme.of(context).extension()?.chipColor ?? + AppColors.whiteTransparent, + ), + child: SizedBox( + height: height, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 6.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + _iconFrom(tag), + color: + onChipColor ?? + Theme.of( + context, + ).extension()?.onChipColor ?? + Colors.white, + size: fontSize, + ), + const SizedBox(width: 4), + Text( + tag, + textAlign: TextAlign.center, + style: _textStyle(context), + ), + ], + ), + ), + ), + ), + ), + ); + } + + IconData? _iconFrom(String tag) { + return switch (tag) { + 'Adventure sports' => Icons.kayaking_outlined, + 'Beach' => Icons.beach_access_outlined, + 'City' => Icons.location_city_outlined, + 'Cultural experiences' => Icons.museum_outlined, + 'Foodie' || 'Food tours' => Icons.restaurant, + 'Hiking' => Icons.hiking, + 'Historic' => Icons.menu_book_outlined, + 'Island' || 'Coastal' || 'Lake' || 'River' => Icons.water, + 'Luxury' => Icons.attach_money_outlined, + 'Mountain' || 'Wildlife watching' => Icons.landscape_outlined, + 'Nightlife' => Icons.local_bar_outlined, + 'Off-the-beaten-path' => Icons.do_not_step_outlined, + 'Romantic' => Icons.favorite_border_outlined, + 'Rural' => Icons.agriculture_outlined, + 'Secluded' => Icons.church_outlined, + 'Sightseeing' => Icons.attractions_outlined, + 'Skiing' => Icons.downhill_skiing_outlined, + 'Wine tasting' => Icons.wine_bar_outlined, + 'Winter destination' => Icons.ac_unit, + _ => Icons.label_outlined, + }; + } + + // Note: original Figma file uses Google Sans + // which is not available on GoogleFonts + TextStyle _textStyle(BuildContext context) => GoogleFonts.openSans( + textStyle: TextStyle( + fontWeight: FontWeight.w500, + fontSize: fontSize, + color: + onChipColor ?? + Theme.of(context).extension()?.onChipColor ?? + Colors.white, + textBaseline: TextBaseline.alphabetic, + ), + ); +} + +class TagChipTheme extends ThemeExtension { + final Color chipColor; + final Color onChipColor; + + TagChipTheme({required this.chipColor, required this.onChipColor}); + + @override + ThemeExtension copyWith({ + Color? chipColor, + Color? onChipColor, + }) { + return TagChipTheme( + chipColor: chipColor ?? this.chipColor, + onChipColor: onChipColor ?? this.onChipColor, + ); + } + + @override + ThemeExtension lerp( + covariant ThemeExtension other, + double t, + ) { + if (other is! TagChipTheme) { + return this; + } + return TagChipTheme( + chipColor: Color.lerp(chipColor, other.chipColor, t) ?? chipColor, + onChipColor: Color.lerp(onChipColor, other.onChipColor, t) ?? onChipColor, + ); + } +} diff --git a/compass_app/app/lib/ui/home/view_models/home_viewmodel.dart b/compass_app/app/lib/ui/home/view_models/home_viewmodel.dart new file mode 100644 index 0000000..fba9905 --- /dev/null +++ b/compass_app/app/lib/ui/home/view_models/home_viewmodel.dart @@ -0,0 +1,91 @@ +// 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. + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:logging/logging.dart'; + +import '../../../data/repositories/booking/booking_repository.dart'; +import '../../../data/repositories/user/user_repository.dart'; +import '../../../domain/models/booking/booking_summary.dart'; +import '../../../domain/models/user/user.dart'; +import '../../../utils/command.dart'; +import '../../../utils/result.dart'; + +class HomeViewModel extends ChangeNotifier { + HomeViewModel({ + required BookingRepository bookingRepository, + required UserRepository userRepository, + }) : _bookingRepository = bookingRepository, + _userRepository = userRepository { + load = Command0(_load)..execute(); + deleteBooking = Command1(_deleteBooking); + } + + final BookingRepository _bookingRepository; + final UserRepository _userRepository; + final _log = Logger('HomeViewModel'); + List _bookings = []; + User? _user; + + late Command0 load; + late Command1 deleteBooking; + + List get bookings => _bookings; + + User? get user => _user; + + Future _load() async { + try { + final result = await _bookingRepository.getBookingsList(); + switch (result) { + case Ok>(): + _bookings = result.value; + _log.fine('Loaded bookings'); + case Error>(): + _log.warning('Failed to load bookings', result.error); + return result; + } + + final userResult = await _userRepository.getUser(); + switch (userResult) { + case Ok(): + _user = userResult.value; + _log.fine('Loaded user'); + case Error(): + _log.warning('Failed to load user', userResult.error); + } + + return userResult; + } finally { + notifyListeners(); + } + } + + Future> _deleteBooking(int id) async { + final resultDelete = await _bookingRepository.delete(id); + switch (resultDelete) { + case Ok(): + _log.fine('Deleted booking $id'); + case Error(): + _log.warning('Failed to delete booking $id', resultDelete.error); + return resultDelete; + } + + // After deleting the booking, reload the bookings list. + final resultLoadBookings = await _bookingRepository.getBookingsList(); + switch (resultLoadBookings) { + case Ok>(): + _bookings = resultLoadBookings.value; + _log.fine('Loaded bookings'); + notifyListeners(); // notify only when data changes + case Error>(): + _log.warning('Failed to load bookings', resultLoadBookings.error); + return resultLoadBookings; + } + + return resultLoadBookings; + } +} diff --git a/compass_app/app/lib/ui/home/widgets/home_screen.dart b/compass_app/app/lib/ui/home/widgets/home_screen.dart new file mode 100644 index 0000000..09d1e49 --- /dev/null +++ b/compass_app/app/lib/ui/home/widgets/home_screen.dart @@ -0,0 +1,210 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../../../domain/models/booking/booking_summary.dart'; +import '../../../routing/routes.dart'; +import '../../auth/logout/view_models/logout_viewmodel.dart'; +import '../../core/localization/applocalization.dart'; +import '../../core/themes/colors.dart'; +import '../../core/themes/dimens.dart'; +import '../../core/ui/date_format_start_end.dart'; +import '../../core/ui/error_indicator.dart'; +import '../view_models/home_viewmodel.dart'; +import 'home_title.dart'; + +const String bookingButtonKey = 'booking-button'; + +class HomeScreen extends StatefulWidget { + const HomeScreen({ + super.key, + required this.viewModel, + required this.logoutViewModel, + }); + + final HomeViewModel viewModel; + final LogoutViewModel logoutViewModel; + + @override + State createState() => _HomeScreenState(); +} + +class _HomeScreenState extends State { + @override + void initState() { + super.initState(); + widget.viewModel.deleteBooking.addListener(_onResult); + } + + @override + void didUpdateWidget(covariant HomeScreen oldWidget) { + super.didUpdateWidget(oldWidget); + oldWidget.viewModel.deleteBooking.removeListener(_onResult); + widget.viewModel.deleteBooking.addListener(_onResult); + } + + @override + void dispose() { + widget.viewModel.deleteBooking.removeListener(_onResult); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + floatingActionButton: FloatingActionButton.extended( + // Workaround for https://github.com/flutter/flutter/issues/115358#issuecomment-2117157419 + heroTag: null, + key: const ValueKey(bookingButtonKey), + onPressed: () => context.go(Routes.search), + label: Text(AppLocalization.of(context).bookNewTrip), + icon: const Icon(Icons.add_location_outlined), + ), + body: SafeArea( + top: true, + bottom: true, + child: ListenableBuilder( + listenable: widget.viewModel.load, + builder: (context, child) { + if (widget.viewModel.load.running) { + return const Center(child: CircularProgressIndicator()); + } + + if (widget.viewModel.load.error) { + return ErrorIndicator( + title: AppLocalization.of(context).errorWhileLoadingHome, + label: AppLocalization.of(context).tryAgain, + onPressed: widget.viewModel.load.execute, + ); + } + + return child!; + }, + child: ListenableBuilder( + listenable: widget.viewModel, + builder: (context, _) { + return CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: Padding( + padding: EdgeInsets.symmetric( + vertical: Dimens.of(context).paddingScreenVertical, + horizontal: Dimens.of(context).paddingScreenHorizontal, + ), + child: HomeHeader( + viewModel: widget.viewModel, + logoutViewModel: widget.logoutViewModel, + ), + ), + ), + SliverList.builder( + itemCount: widget.viewModel.bookings.length, + itemBuilder: (_, index) => _Booking( + key: ValueKey(widget.viewModel.bookings[index].id), + booking: widget.viewModel.bookings[index], + onTap: () => context.push( + Routes.bookingWithId( + widget.viewModel.bookings[index].id, + ), + ), + confirmDismiss: (_) async { + // wait for command to complete + await widget.viewModel.deleteBooking.execute( + widget.viewModel.bookings[index].id, + ); + // if command completed successfully, return true + if (widget.viewModel.deleteBooking.completed) { + // removes the dismissable from the list + return true; + } else { + // the dismissable stays in the list + return false; + } + }, + ), + ), + ], + ); + }, + ), + ), + ), + ); + } + + void _onResult() { + if (widget.viewModel.deleteBooking.completed) { + widget.viewModel.deleteBooking.clearResult(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppLocalization.of(context).bookingDeleted)), + ); + } + + if (widget.viewModel.deleteBooking.error) { + widget.viewModel.deleteBooking.clearResult(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalization.of(context).errorWhileDeletingBooking), + ), + ); + } + } +} + +class _Booking extends StatelessWidget { + const _Booking({ + super.key, + required this.booking, + required this.onTap, + required this.confirmDismiss, + }); + + final BookingSummary booking; + final GestureTapCallback onTap; + final ConfirmDismissCallback confirmDismiss; + + @override + Widget build(BuildContext context) { + return Dismissible( + key: ValueKey(booking.id), + direction: DismissDirection.endToStart, + confirmDismiss: confirmDismiss, + background: Container( + color: AppColors.grey1, + child: const Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Padding( + padding: EdgeInsets.only(right: Dimens.paddingHorizontal), + child: Icon(Icons.delete), + ), + ], + ), + ), + child: InkWell( + onTap: onTap, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: Dimens.of(context).paddingScreenHorizontal, + vertical: Dimens.paddingVertical, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(booking.name, style: Theme.of(context).textTheme.titleLarge), + Text( + dateFormatStartEnd( + DateTimeRange(start: booking.startDate, end: booking.endDate), + ), + style: Theme.of(context).textTheme.bodyLarge, + ), + ], + ), + ), + ), + ); + } +} diff --git a/compass_app/app/lib/ui/home/widgets/home_screen_container.dart b/compass_app/app/lib/ui/home/widgets/home_screen_container.dart new file mode 100644 index 0000000..5ec0cbc --- /dev/null +++ b/compass_app/app/lib/ui/home/widgets/home_screen_container.dart @@ -0,0 +1,45 @@ +import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; + +import '../../../data/repositories/booking/booking_repository.dart'; +import '../../../data/repositories/user/user_repository.dart'; +import '../../auth/logout/view_models/logout_viewmodel.dart'; +import '../view_models/home_viewmodel.dart'; +import 'home_screen.dart'; + +class HomeScreenContainer extends StatefulWidget { + const HomeScreenContainer({super.key, required this.logoutViewModel}); + + final LogoutViewModel logoutViewModel; + + @override + State createState() => _HomeScreenContainerState(); +} + +class _HomeScreenContainerState extends State { + late final HomeViewModel _viewModel; + + @override + void initState() { + super.initState(); + + _viewModel = HomeViewModel( + bookingRepository: context.read(), + userRepository: context.read(), + ); + } + + @override + Widget build(BuildContext context) { + return HomeScreen( + viewModel: _viewModel, + logoutViewModel: widget.logoutViewModel, + ); + } + + @override + void dispose() { + _viewModel.dispose(); + super.dispose(); + } +} diff --git a/compass_app/app/lib/ui/home/widgets/home_title.dart b/compass_app/app/lib/ui/home/widgets/home_title.dart new file mode 100644 index 0000000..1675579 --- /dev/null +++ b/compass_app/app/lib/ui/home/widgets/home_title.dart @@ -0,0 +1,76 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +import '../../auth/logout/view_models/logout_viewmodel.dart'; +import '../../auth/logout/widgets/logout_button.dart'; +import '../../core/localization/applocalization.dart'; +import '../../core/themes/dimens.dart'; +import '../view_models/home_viewmodel.dart'; + +class HomeHeader extends StatelessWidget { + const HomeHeader({ + super.key, + required this.viewModel, + required this.logoutViewModel, + }); + + final HomeViewModel viewModel; + final LogoutViewModel logoutViewModel; + + @override + Widget build(BuildContext context) { + final user = viewModel.user; + if (user == null) { + return const SizedBox(); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipOval( + child: Image.asset( + user.picture, + width: Dimens.of(context).profilePictureSize, + height: Dimens.of(context).profilePictureSize, + ), + ), + LogoutButton(viewModel: logoutViewModel), + ], + ), + const SizedBox(height: Dimens.paddingVertical), + _Title(text: AppLocalization.of(context).nameTrips(user.name)), + ], + ); + } +} + +class _Title extends StatelessWidget { + const _Title({required this.text}); + + final String text; + + @override + Widget build(BuildContext context) { + return ShaderMask( + blendMode: BlendMode.srcIn, + shaderCallback: (bounds) => RadialGradient( + center: Alignment.bottomLeft, + radius: 2, + colors: [Colors.purple.shade700, Colors.purple.shade400], + ).createShader(Rect.fromLTWH(0, 0, bounds.width, bounds.height)), + child: Text( + text, + style: GoogleFonts.rubik( + textStyle: Theme.of(context).textTheme.headlineLarge, + ), + ), + ); + } +} diff --git a/compass_app/app/lib/ui/results/view_models/results_viewmodel.dart b/compass_app/app/lib/ui/results/view_models/results_viewmodel.dart new file mode 100644 index 0000000..b5af3de --- /dev/null +++ b/compass_app/app/lib/ui/results/view_models/results_viewmodel.dart @@ -0,0 +1,112 @@ +// 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. + +import 'package:flutter/cupertino.dart'; +import 'package:logging/logging.dart'; + +import '../../../data/repositories/destination/destination_repository.dart'; +import '../../../data/repositories/itinerary_config/itinerary_config_repository.dart'; +import '../../../domain/models/destination/destination.dart'; +import '../../../domain/models/itinerary_config/itinerary_config.dart'; +import '../../../utils/command.dart'; +import '../../../utils/result.dart'; + +/// Results screen view model +/// Based on https://docs.flutter.dev/get-started/fwe/state-management#using-mvvm-for-your-applications-architecture +class ResultsViewModel extends ChangeNotifier { + ResultsViewModel({ + required DestinationRepository destinationRepository, + required ItineraryConfigRepository itineraryConfigRepository, + }) : _destinationRepository = destinationRepository, + _itineraryConfigRepository = itineraryConfigRepository { + updateItineraryConfig = Command1(_updateItineraryConfig); + search = Command0(_search)..execute(); + } + + final _log = Logger('ResultsViewModel'); + + final DestinationRepository _destinationRepository; + + final ItineraryConfigRepository _itineraryConfigRepository; + + // Setters are private + List _destinations = []; + + /// List of destinations, may be empty but never null + List get destinations => _destinations; + + ItineraryConfig? _itineraryConfig; + + /// Filter options to display on search bar + ItineraryConfig get config => _itineraryConfig ?? const ItineraryConfig(); + + /// Perform search + late final Command0 search; + + /// Store ViewModel data into [ItineraryConfigRepository] before navigating. + late final Command1 updateItineraryConfig; + + Future> _search() async { + // Load current itinerary config + final resultConfig = await _itineraryConfigRepository.getItineraryConfig(); + switch (resultConfig) { + case Error(): + _log.warning( + 'Failed to load stored ItineraryConfig', + resultConfig.error, + ); + return resultConfig; + case Ok(): + } + _itineraryConfig = resultConfig.value; + notifyListeners(); + + final result = await _destinationRepository.getDestinations(); + switch (result) { + case Ok(): + { + // If the result is Ok, update the list of destinations + _destinations = result.value + .where( + (destination) => + destination.continent == _itineraryConfig!.continent, + ) + .toList(); + _log.fine('Destinations (${_destinations.length}) loaded'); + } + case Error(): + { + _log.warning('Failed to load destinations', result.error); + } + } + + // After finish loading results, notify the view + notifyListeners(); + return result; + } + + Future> _updateItineraryConfig(String destinationRef) async { + assert(destinationRef.isNotEmpty, "destinationRef should not be empty"); + + final resultConfig = await _itineraryConfigRepository.getItineraryConfig(); + switch (resultConfig) { + case Error(): + _log.warning( + 'Failed to load stored ItineraryConfig', + resultConfig.error, + ); + return resultConfig; + case Ok(): + } + + final itineraryConfig = resultConfig.value; + final result = await _itineraryConfigRepository.setItineraryConfig( + itineraryConfig.copyWith(destination: destinationRef, activities: []), + ); + if (result is Error) { + _log.warning('Failed to store ItineraryConfig', result.error); + } + return result; + } +} diff --git a/compass_app/app/lib/ui/results/widgets/result_card.dart b/compass_app/app/lib/ui/results/widgets/result_card.dart new file mode 100644 index 0000000..463358d --- /dev/null +++ b/compass_app/app/lib/ui/results/widgets/result_card.dart @@ -0,0 +1,76 @@ +// 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. + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import '../../../domain/models/destination/destination.dart'; +import '../../../utils/image_error_listener.dart'; +import '../../core/ui/tag_chip.dart'; + +class ResultCard extends StatelessWidget { + const ResultCard({super.key, required this.destination, required this.onTap}); + + final Destination destination; + final GestureTapCallback onTap; + + @override + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: BorderRadius.circular(10.0), + child: Stack( + fit: StackFit.expand, + children: [ + CachedNetworkImage( + imageUrl: destination.imageUrl, + fit: BoxFit.fitHeight, + errorWidget: (context, url, error) => const Icon(Icons.error), + errorListener: imageErrorListener, + ), + Positioned( + bottom: 12.0, + left: 12.0, + right: 12.0, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(destination.name.toUpperCase(), style: _cardTitleStyle), + const SizedBox(height: 6), + Wrap( + spacing: 4.0, + runSpacing: 4.0, + direction: Axis.horizontal, + children: destination.tags + .map((e) => TagChip(tag: e)) + .toList(), + ), + ], + ), + ), + // Handle taps + Positioned.fill( + child: Material( + color: Colors.transparent, + child: InkWell(onTap: onTap), + ), + ), + ], + ), + ); + } +} + +final _cardTitleStyle = GoogleFonts.rubik( + textStyle: const TextStyle( + fontWeight: FontWeight.w800, + fontSize: 15.0, + color: Colors.white, + letterSpacing: 1, + shadows: [ + // Helps to read the text a bit better + Shadow(blurRadius: 3.0, color: Colors.black), + ], + ), +); diff --git a/compass_app/app/lib/ui/results/widgets/results_screen.dart b/compass_app/app/lib/ui/results/widgets/results_screen.dart new file mode 100644 index 0000000..a2b675c --- /dev/null +++ b/compass_app/app/lib/ui/results/widgets/results_screen.dart @@ -0,0 +1,170 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../../../routing/routes.dart'; +import '../../core/localization/applocalization.dart'; +import '../../core/themes/dimens.dart'; +import '../../core/ui/error_indicator.dart'; +import '../../core/ui/search_bar.dart'; +import '../view_models/results_viewmodel.dart'; +import 'result_card.dart'; + +class ResultsScreen extends StatefulWidget { + const ResultsScreen({super.key, required this.viewModel}); + + final ResultsViewModel viewModel; + + @override + State createState() => _ResultsScreenState(); +} + +class _ResultsScreenState extends State { + @override + void initState() { + super.initState(); + widget.viewModel.updateItineraryConfig.addListener(_onResult); + } + + @override + void didUpdateWidget(covariant ResultsScreen oldWidget) { + super.didUpdateWidget(oldWidget); + oldWidget.viewModel.updateItineraryConfig.removeListener(_onResult); + widget.viewModel.updateItineraryConfig.addListener(_onResult); + } + + @override + void dispose() { + widget.viewModel.updateItineraryConfig.removeListener(_onResult); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, r) { + if (!didPop) context.go(Routes.search); + }, + child: Scaffold( + body: ListenableBuilder( + listenable: widget.viewModel.search, + builder: (context, child) { + if (widget.viewModel.search.completed) { + return child!; + } + return Column( + children: [ + _AppSearchBar(widget: widget), + if (widget.viewModel.search.running) + const Expanded( + child: Center(child: CircularProgressIndicator()), + ), + if (widget.viewModel.search.error) + Expanded( + child: Center( + child: ErrorIndicator( + title: AppLocalization.of( + context, + ).errorWhileLoadingDestinations, + label: AppLocalization.of(context).tryAgain, + onPressed: widget.viewModel.search.execute, + ), + ), + ), + ], + ); + }, + child: ListenableBuilder( + listenable: widget.viewModel, + builder: (context, child) { + return Padding( + padding: Dimens.of(context).edgeInsetsScreenHorizontal, + child: CustomScrollView( + slivers: [ + SliverToBoxAdapter(child: _AppSearchBar(widget: widget)), + _Grid(viewModel: widget.viewModel), + ], + ), + ); + }, + ), + ), + ), + ); + } + + void _onResult() { + if (widget.viewModel.updateItineraryConfig.completed) { + widget.viewModel.updateItineraryConfig.clearResult(); + context.go(Routes.activities); + } + + if (widget.viewModel.updateItineraryConfig.error) { + widget.viewModel.updateItineraryConfig.clearResult(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalization.of(context).errorWhileSavingItinerary), + ), + ); + } + } +} + +class _AppSearchBar extends StatelessWidget { + const _AppSearchBar({required this.widget}); + + final ResultsScreen widget; + + @override + Widget build(BuildContext context) { + return SafeArea( + top: true, + bottom: false, + child: Padding( + padding: EdgeInsets.only( + top: Dimens.of(context).paddingScreenVertical, + bottom: Dimens.mobile.paddingScreenVertical, + ), + child: AppSearchBar( + config: widget.viewModel.config, + onTap: () { + // Navigate to SearchFormScreen and edit search + context.pop(); + }, + ), + ), + ); + } +} + +class _Grid extends StatelessWidget { + const _Grid({required this.viewModel}); + + final ResultsViewModel viewModel; + + @override + Widget build(BuildContext context) { + return SliverGrid( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 8.0, + mainAxisSpacing: 8.0, + childAspectRatio: 182 / 222, + ), + delegate: SliverChildBuilderDelegate((context, index) { + final destination = viewModel.destinations[index]; + return ResultCard( + key: ValueKey(destination.ref), + destination: destination, + onTap: () { + viewModel.updateItineraryConfig.execute(destination.ref); + }, + ); + }, childCount: viewModel.destinations.length), + ); + } +} diff --git a/compass_app/app/lib/ui/search_form/view_models/search_form_viewmodel.dart b/compass_app/app/lib/ui/search_form/view_models/search_form_viewmodel.dart new file mode 100644 index 0000000..3093434 --- /dev/null +++ b/compass_app/app/lib/ui/search_form/view_models/search_form_viewmodel.dart @@ -0,0 +1,151 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:logging/logging.dart'; + +import '../../../data/repositories/continent/continent_repository.dart'; +import '../../../data/repositories/itinerary_config/itinerary_config_repository.dart'; +import '../../../domain/models/continent/continent.dart'; +import '../../../domain/models/itinerary_config/itinerary_config.dart'; +import '../../../utils/command.dart'; +import '../../../utils/result.dart'; + +/// View model for the search form. +/// +/// Contains the form selected options +/// and the logic to load the list of regions. +class SearchFormViewModel extends ChangeNotifier { + SearchFormViewModel({ + required ContinentRepository continentRepository, + required ItineraryConfigRepository itineraryConfigRepository, + }) : _continentRepository = continentRepository, + _itineraryConfigRepository = itineraryConfigRepository { + updateItineraryConfig = Command0(_updateItineraryConfig); + load = Command0(_load)..execute(); + } + + final _log = Logger('SearchFormViewModel'); + final ContinentRepository _continentRepository; + final ItineraryConfigRepository _itineraryConfigRepository; + List _continents = []; + String? _selectedContinent; + DateTimeRange? _dateRange; + int _guests = 0; + + /// True if the form is valid and can be submitted + bool get valid => + _guests > 0 && _selectedContinent != null && _dateRange != null; + + /// List of continents. + /// Loaded in [load] command. + List get continents => _continents; + + /// Selected continent. + /// Null means no continent is selected. + String? get selectedContinent => _selectedContinent; + + /// Set selected continent. + /// Set to null to clear the selection. + set selectedContinent(String? continent) { + _selectedContinent = continent; + _log.finest('Selected continent: $continent'); + notifyListeners(); + } + + /// Date range. + /// Null means no range is selected. + DateTimeRange? get dateRange => _dateRange; + + /// Set date range. + /// Can be set to null to clear selection. + set dateRange(DateTimeRange? dateRange) { + _dateRange = dateRange; + _log.finest('Selected date range: $dateRange'); + notifyListeners(); + } + + /// Number of guests + int get guests => _guests; + + /// Set number of guests + /// If the quantity is negative, it will be set to 0 + set guests(int quantity) { + if (quantity < 0) { + _guests = 0; + } else { + _guests = quantity; + } + _log.finest('Set guests number: $_guests'); + notifyListeners(); + } + + /// Load the list of continents and current itinerary config. + late final Command0 load; + + /// Store ViewModel data into [ItineraryConfigRepository] before navigating. + late final Command0 updateItineraryConfig; + + Future> _load() async { + final result = await _loadContinents(); + if (result is Error) { + return result; + } + return await _loadItineraryConfig(); + } + + Future> _loadContinents() async { + final result = await _continentRepository.getContinents(); + switch (result) { + case Ok(): + _continents = result.value; + _log.fine('Continents (${_continents.length}) loaded'); + case Error(): + _log.warning('Failed to load continents', result.error); + } + notifyListeners(); + return result; + } + + Future> _loadItineraryConfig() async { + final result = await _itineraryConfigRepository.getItineraryConfig(); + switch (result) { + case Ok(): + final itineraryConfig = result.value; + _selectedContinent = itineraryConfig.continent; + if (itineraryConfig.startDate != null && + itineraryConfig.endDate != null) { + _dateRange = DateTimeRange( + start: itineraryConfig.startDate!, + end: itineraryConfig.endDate!, + ); + } + _guests = itineraryConfig.guests ?? 0; + _log.fine('ItineraryConfig loaded'); + notifyListeners(); + case Error(): + _log.warning('Failed to load stored ItineraryConfig', result.error); + } + return result; + } + + Future> _updateItineraryConfig() async { + assert(valid, "called when valid was false"); + final result = await _itineraryConfigRepository.setItineraryConfig( + ItineraryConfig( + continent: _selectedContinent, + startDate: _dateRange!.start, + endDate: _dateRange!.end, + guests: _guests, + ), + ); + switch (result) { + case Ok(): + _log.fine('ItineraryConfig saved'); + case Error(): + _log.warning('Failed to store ItineraryConfig', result.error); + } + return result; + } +} diff --git a/compass_app/app/lib/ui/search_form/widgets/search_form_continent.dart b/compass_app/app/lib/ui/search_form/widgets/search_form_continent.dart new file mode 100644 index 0000000..d59b5eb --- /dev/null +++ b/compass_app/app/lib/ui/search_form/widgets/search_form_continent.dart @@ -0,0 +1,161 @@ +// 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. + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +import '../../../domain/models/continent/continent.dart'; +import '../../../utils/image_error_listener.dart'; +import '../../core/localization/applocalization.dart'; +import '../../core/themes/colors.dart'; +import '../../core/themes/dimens.dart'; +import '../../core/ui/error_indicator.dart'; +import '../view_models/search_form_viewmodel.dart'; + +/// Continent selection carousel +/// +/// Loads a list of continents in a horizontal carousel. +/// Users can tap one item to select it. +/// Tapping again the same item will deselect it. +class SearchFormContinent extends StatelessWidget { + const SearchFormContinent({super.key, required this.viewModel}); + + final SearchFormViewModel viewModel; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 140, + child: ListenableBuilder( + listenable: viewModel.load, + builder: (context, child) { + if (viewModel.load.running) { + return const Center(child: CircularProgressIndicator()); + } + if (viewModel.load.error) { + return Center( + child: ErrorIndicator( + title: AppLocalization.of(context).errorWhileLoadingContinents, + label: AppLocalization.of(context).tryAgain, + onPressed: viewModel.load.execute, + ), + ); + } + return child!; + }, + child: ListenableBuilder( + listenable: viewModel, + builder: (context, child) { + return ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: viewModel.continents.length, + padding: Dimens.of(context).edgeInsetsScreenHorizontal, + itemBuilder: (context, index) { + final Continent(:imageUrl, :name) = viewModel.continents[index]; + return _CarouselItem( + key: ValueKey(name), + imageUrl: imageUrl, + name: name, + viewModel: viewModel, + ); + }, + separatorBuilder: (context, index) { + return const SizedBox(width: 8); + }, + ); + }, + ), + ), + ); + } +} + +class _CarouselItem extends StatelessWidget { + const _CarouselItem({ + super.key, + required this.imageUrl, + required this.name, + required this.viewModel, + }); + + final String imageUrl; + final String name; + final SearchFormViewModel viewModel; + + bool _selected() => + viewModel.selectedContinent == null || + viewModel.selectedContinent == name; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 140, + height: 140, + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: Stack( + children: [ + CachedNetworkImage( + imageUrl: imageUrl, + fit: BoxFit.cover, + errorListener: imageErrorListener, + errorWidget: (context, url, error) { + // NOTE: Getting "invalid image data" error for some of the images + // e.g. https://rstr.in/google/tripedia/jlbgFDrSUVE + return const DecoratedBox( + decoration: BoxDecoration(color: AppColors.grey3), + child: SizedBox(width: 140, height: 140), + ); + }, + ), + Align( + alignment: Alignment.center, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + name, + textAlign: TextAlign.center, + style: GoogleFonts.openSans( + fontSize: 18, + fontWeight: FontWeight.w500, + color: AppColors.white1, + ), + ), + ), + ), + // Overlay when other continent is selected + Positioned.fill( + child: AnimatedOpacity( + opacity: _selected() ? 0 : 0.7, + duration: kThemeChangeDuration, + child: DecoratedBox( + decoration: BoxDecoration( + // Support dark-mode + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + ), + // Handle taps + Positioned.fill( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + if (viewModel.selectedContinent == name) { + viewModel.selectedContinent = null; + } else { + viewModel.selectedContinent = name; + } + }, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/compass_app/app/lib/ui/search_form/widgets/search_form_date.dart b/compass_app/app/lib/ui/search_form/widgets/search_form_date.dart new file mode 100644 index 0000000..e84eeb3 --- /dev/null +++ b/compass_app/app/lib/ui/search_form/widgets/search_form_date.dart @@ -0,0 +1,79 @@ +// 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. + +import 'package:flutter/material.dart'; + +import '../../core/localization/applocalization.dart'; +import '../../core/themes/colors.dart'; +import '../../core/themes/dimens.dart'; +import '../../core/ui/date_format_start_end.dart'; +import '../view_models/search_form_viewmodel.dart'; + +/// Date selection form field. +/// +/// Opens a date range picker dialog when tapped. +class SearchFormDate extends StatelessWidget { + const SearchFormDate({super.key, required this.viewModel}); + + final SearchFormViewModel viewModel; + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.only( + top: Dimens.paddingVertical, + left: Dimens.of(context).paddingScreenHorizontal, + right: Dimens.of(context).paddingScreenHorizontal, + ), + child: InkWell( + borderRadius: BorderRadius.circular(16.0), + onTap: () { + showDateRangePicker( + context: context, + firstDate: DateTime.now(), + lastDate: DateTime.now().add(const Duration(days: 365)), + ).then((dateRange) => viewModel.dateRange = dateRange); + }, + child: Container( + height: 64, + decoration: BoxDecoration( + border: Border.all(color: AppColors.grey1), + borderRadius: BorderRadius.circular(16.0), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: Dimens.paddingHorizontal, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalization.of(context).when, + style: Theme.of(context).textTheme.titleMedium, + ), + ListenableBuilder( + listenable: viewModel, + builder: (context, _) { + final dateRange = viewModel.dateRange; + if (dateRange != null) { + return Text( + dateFormatStartEnd(dateRange), + style: Theme.of(context).textTheme.bodyLarge, + ); + } else { + return Text( + AppLocalization.of(context).addDates, + style: Theme.of(context).inputDecorationTheme.hintStyle, + ); + } + }, + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/compass_app/app/lib/ui/search_form/widgets/search_form_guests.dart b/compass_app/app/lib/ui/search_form/widgets/search_form_guests.dart new file mode 100644 index 0000000..5c4abfa --- /dev/null +++ b/compass_app/app/lib/ui/search_form/widgets/search_form_guests.dart @@ -0,0 +1,96 @@ +// 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. + +import 'package:flutter/material.dart'; + +import '../../core/themes/colors.dart'; +import '../../core/themes/dimens.dart'; +import '../view_models/search_form_viewmodel.dart'; + +const String removeGuestsKey = 'remove-guests'; +const String addGuestsKey = 'add-guests'; + +/// Number of guests selection form +/// +/// Users can tap the Plus and Minus icons to increase or decrease +/// the number of guests. +class SearchFormGuests extends StatelessWidget { + const SearchFormGuests({super.key, required this.viewModel}); + + final SearchFormViewModel viewModel; + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.only( + top: Dimens.paddingVertical, + left: Dimens.of(context).paddingScreenHorizontal, + right: Dimens.of(context).paddingScreenHorizontal, + ), + child: Container( + height: 64, + decoration: BoxDecoration( + border: Border.all(color: AppColors.grey1), + borderRadius: BorderRadius.circular(16.0), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: Dimens.paddingHorizontal, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Who', style: Theme.of(context).textTheme.titleMedium), + _QuantitySelector(viewModel), + ], + ), + ), + ), + ); + } +} + +class _QuantitySelector extends StatelessWidget { + const _QuantitySelector(this.viewModel); + + final SearchFormViewModel viewModel; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 90, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + InkWell( + key: const ValueKey(removeGuestsKey), + onTap: () { + viewModel.guests--; + }, + child: const Icon( + Icons.remove_circle_outline, + color: AppColors.grey3, + ), + ), + ListenableBuilder( + listenable: viewModel, + builder: (context, _) => Text( + viewModel.guests.toString(), + style: viewModel.guests == 0 + ? Theme.of(context).inputDecorationTheme.hintStyle + : Theme.of(context).textTheme.bodyMedium, + ), + ), + InkWell( + key: const ValueKey(addGuestsKey), + onTap: () { + viewModel.guests++; + }, + child: const Icon(Icons.add_circle_outline, color: AppColors.grey3), + ), + ], + ), + ); + } +} diff --git a/compass_app/app/lib/ui/search_form/widgets/search_form_screen.dart b/compass_app/app/lib/ui/search_form/widgets/search_form_screen.dart new file mode 100644 index 0000000..cf4ce11 --- /dev/null +++ b/compass_app/app/lib/ui/search_form/widgets/search_form_screen.dart @@ -0,0 +1,62 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../../../routing/routes.dart'; +import '../../core/themes/dimens.dart'; +import '../../core/ui/search_bar.dart'; +import '../../results/widgets/results_screen.dart'; +import '../view_models/search_form_viewmodel.dart'; +import 'search_form_continent.dart'; +import 'search_form_date.dart'; +import 'search_form_guests.dart'; +import 'search_form_submit.dart'; + +/// Search form screen +/// +/// Displays a search form with continent, date and guests selection. +/// Tapping on the submit button opens the [ResultsScreen] screen +/// passing the search options as query parameters. +class SearchFormScreen extends StatelessWidget { + const SearchFormScreen({super.key, required this.viewModel}); + + final SearchFormViewModel viewModel; + + @override + Widget build(BuildContext context) { + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, r) { + if (!didPop) context.go(Routes.home); + }, + child: Scaffold( + body: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SafeArea( + top: true, + bottom: false, + child: Padding( + padding: EdgeInsets.only( + top: Dimens.of(context).paddingScreenVertical, + left: Dimens.of(context).paddingScreenHorizontal, + right: Dimens.of(context).paddingScreenHorizontal, + bottom: Dimens.paddingVertical, + ), + child: const AppSearchBar(), + ), + ), + SearchFormContinent(viewModel: viewModel), + SearchFormDate(viewModel: viewModel), + SearchFormGuests(viewModel: viewModel), + const Spacer(), + SearchFormSubmit(viewModel: viewModel), + ], + ), + ), + ); + } +} diff --git a/compass_app/app/lib/ui/search_form/widgets/search_form_submit.dart b/compass_app/app/lib/ui/search_form/widgets/search_form_submit.dart new file mode 100644 index 0000000..e6ce6a1 --- /dev/null +++ b/compass_app/app/lib/ui/search_form/widgets/search_form_submit.dart @@ -0,0 +1,97 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../../../routing/routes.dart'; +import '../../core/localization/applocalization.dart'; +import '../../core/themes/dimens.dart'; +import '../../results/widgets/results_screen.dart'; +import '../view_models/search_form_viewmodel.dart'; + +const String searchFormSubmitButtonKey = 'submit-button'; + +/// Search form submit button +/// +/// The button is disabled when the form is data is incomplete. +/// When tapped, it navigates to the [ResultsScreen] +/// passing the search options as query parameters. +class SearchFormSubmit extends StatefulWidget { + const SearchFormSubmit({super.key, required this.viewModel}); + + final SearchFormViewModel viewModel; + + @override + State createState() => _SearchFormSubmitState(); +} + +class _SearchFormSubmitState extends State { + @override + void initState() { + super.initState(); + widget.viewModel.updateItineraryConfig.addListener(_onResult); + } + + @override + void didUpdateWidget(covariant SearchFormSubmit oldWidget) { + oldWidget.viewModel.updateItineraryConfig.removeListener(_onResult); + widget.viewModel.updateItineraryConfig.addListener(_onResult); + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + widget.viewModel.updateItineraryConfig.removeListener(_onResult); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.only( + top: Dimens.paddingVertical, + left: Dimens.of(context).paddingScreenHorizontal, + right: Dimens.of(context).paddingScreenHorizontal, + bottom: Dimens.of(context).paddingScreenVertical, + ), + child: ListenableBuilder( + listenable: widget.viewModel, + child: SizedBox( + height: 52, + child: Center(child: Text(AppLocalization.of(context).search)), + ), + builder: (context, child) { + return FilledButton( + key: const ValueKey(searchFormSubmitButtonKey), + onPressed: widget.viewModel.valid + ? widget.viewModel.updateItineraryConfig.execute + : null, + child: child, + ); + }, + ), + ); + } + + void _onResult() { + if (widget.viewModel.updateItineraryConfig.completed) { + widget.viewModel.updateItineraryConfig.clearResult(); + context.go(Routes.results); + } + + if (widget.viewModel.updateItineraryConfig.error) { + widget.viewModel.updateItineraryConfig.clearResult(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalization.of(context).errorWhileSavingItinerary), + action: SnackBarAction( + label: AppLocalization.of(context).tryAgain, + onPressed: widget.viewModel.updateItineraryConfig.execute, + ), + ), + ); + } + } +} diff --git a/compass_app/app/lib/utils/command.dart b/compass_app/app/lib/utils/command.dart new file mode 100644 index 0000000..1df5962 --- /dev/null +++ b/compass_app/app/lib/utils/command.dart @@ -0,0 +1,97 @@ +// 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. + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; + +import 'result.dart'; + +typedef CommandAction0 = Future> Function(); +typedef CommandAction1 = Future> Function(A); + +/// Facilitates interaction with a ViewModel. +/// +/// Encapsulates an action, +/// exposes its running and error states, +/// and ensures that it can't be launched again until it finishes. +/// +/// Use [Command0] for actions without arguments. +/// Use [Command1] for actions with one argument. +/// +/// Actions must return a [Result]. +/// +/// Consume the action result by listening to changes, +/// then call to [clearResult] when the state is consumed. +abstract class Command extends ChangeNotifier { + Command(); + + bool _running = false; + + /// True when the action is running. + bool get running => _running; + + Result? _result; + + /// true if action completed with error + bool get error => _result is Error; + + /// true if action completed successfully + bool get completed => _result is Ok; + + /// Get last action result + Result? get result => _result; + + /// Clear last action result + void clearResult() { + _result = null; + notifyListeners(); + } + + /// Internal execute implementation + Future _execute(CommandAction0 action) async { + // Ensure the action can't launch multiple times. + // e.g. avoid multiple taps on button + if (_running) return; + + // Notify listeners. + // e.g. button shows loading state + _running = true; + _result = null; + notifyListeners(); + + try { + _result = await action(); + } finally { + _running = false; + notifyListeners(); + } + } +} + +/// [Command] without arguments. +/// Takes a [CommandAction0] as action. +class Command0 extends Command { + Command0(this._action); + + final CommandAction0 _action; + + /// Executes the action. + Future execute() async { + await _execute(_action); + } +} + +/// [Command] with one argument. +/// Takes a [CommandAction1] as action. +class Command1 extends Command { + Command1(this._action); + + final CommandAction1 _action; + + /// Executes the action with the argument. + Future execute(A argument) async { + await _execute(() => _action(argument)); + } +} diff --git a/compass_app/app/lib/utils/image_error_listener.dart b/compass_app/app/lib/utils/image_error_listener.dart new file mode 100644 index 0000000..79c2092 --- /dev/null +++ b/compass_app/app/lib/utils/image_error_listener.dart @@ -0,0 +1,11 @@ +// 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. + +import 'package:logging/logging.dart'; + +final _log = Logger('ImageErrorListener'); + +void imageErrorListener(Object error) { + _log.warning('Failed to load image', error); +} diff --git a/compass_app/app/lib/utils/result.dart b/compass_app/app/lib/utils/result.dart new file mode 100644 index 0000000..407b11c --- /dev/null +++ b/compass_app/app/lib/utils/result.dart @@ -0,0 +1,48 @@ +// 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. + +/// Utility class to wrap result data +/// +/// Evaluate the result using a switch statement: +/// ```dart +/// switch (result) { +/// case Ok(): { +/// print(result.value); +/// } +/// case Error(): { +/// print(result.error); +/// } +/// } +/// ``` +sealed class Result { + const Result(); + + /// Creates a successful [Result], completed with the specified [value]. + const factory Result.ok(T value) = Ok._; + + /// Creates an error [Result], completed with the specified [error]. + const factory Result.error(Exception error) = Error._; +} + +/// Subclass of Result for values +final class Ok extends Result { + const Ok._(this.value); + + /// Returned value in result + final T value; + + @override + String toString() => 'Result<$T>.ok($value)'; +} + +/// Subclass of Result for errors +final class Error extends Result { + const Error._(this.error); + + /// Returned error in result + final Exception error; + + @override + String toString() => 'Result<$T>.error($error)'; +} diff --git a/compass_app/app/linux/.gitignore b/compass_app/app/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/compass_app/app/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/compass_app/app/linux/CMakeLists.txt b/compass_app/app/linux/CMakeLists.txt new file mode 100644 index 0000000..35574dd --- /dev/null +++ b/compass_app/app/linux/CMakeLists.txt @@ -0,0 +1,145 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "compass_app") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.compass_app") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/compass_app/app/linux/flutter/CMakeLists.txt b/compass_app/app/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/compass_app/app/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/compass_app/app/linux/flutter/generated_plugin_registrant.cc b/compass_app/app/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..f6f23bf --- /dev/null +++ b/compass_app/app/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/compass_app/app/linux/flutter/generated_plugin_registrant.h b/compass_app/app/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/compass_app/app/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/compass_app/app/linux/flutter/generated_plugins.cmake b/compass_app/app/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..f16b4c3 --- /dev/null +++ b/compass_app/app/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/compass_app/app/linux/main.cc b/compass_app/app/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/compass_app/app/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/compass_app/app/linux/my_application.cc b/compass_app/app/linux/my_application.cc new file mode 100644 index 0000000..5a36529 --- /dev/null +++ b/compass_app/app/linux/my_application.cc @@ -0,0 +1,124 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "compass_app"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "compass_app"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/compass_app/app/linux/my_application.h b/compass_app/app/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/compass_app/app/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/compass_app/app/macos/.gitignore b/compass_app/app/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/compass_app/app/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/compass_app/app/macos/Flutter/Flutter-Debug.xcconfig b/compass_app/app/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/compass_app/app/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/compass_app/app/macos/Flutter/Flutter-Release.xcconfig b/compass_app/app/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/compass_app/app/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/compass_app/app/macos/Flutter/GeneratedPluginRegistrant.swift b/compass_app/app/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..8255a0b --- /dev/null +++ b/compass_app/app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,16 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import share_plus +import shared_preferences_foundation +import sqflite_darwin + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) +} diff --git a/compass_app/app/macos/Podfile b/compass_app/app/macos/Podfile new file mode 100644 index 0000000..c795730 --- /dev/null +++ b/compass_app/app/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/compass_app/app/macos/Runner.xcodeproj/project.pbxproj b/compass_app/app/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..79cdc23 --- /dev/null +++ b/compass_app/app/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,801 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + C203960636D4F782F1839CCD /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 624CAC5D902868ED5B3541A6 /* Pods_Runner.framework */; }; + D9B813DBC6B840FC789B8BC8 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3F947B2B2872CA43E01D9D3 /* Pods_RunnerTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 2A6019B907567C19CDA56577 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* compass_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = compass_app.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 4E966B71DA7CDEDDA1AC2EDF /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 614F996EF4C7B207997C69FB /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 624CAC5D902868ED5B3541A6 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 8094AA7203951A1D4ED31304 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + 9F59A18A4C705DE2978D2023 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + D3F947B2B2872CA43E01D9D3 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + ECE434935E45147DB18A8135 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D9B813DBC6B840FC789B8BC8 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C203960636D4F782F1839CCD /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1D6CC27981A89B68E6FDD92F /* Pods */ = { + isa = PBXGroup; + children = ( + 2A6019B907567C19CDA56577 /* Pods-Runner.debug.xcconfig */, + 614F996EF4C7B207997C69FB /* Pods-Runner.release.xcconfig */, + ECE434935E45147DB18A8135 /* Pods-Runner.profile.xcconfig */, + 9F59A18A4C705DE2978D2023 /* Pods-RunnerTests.debug.xcconfig */, + 4E966B71DA7CDEDDA1AC2EDF /* Pods-RunnerTests.release.xcconfig */, + 8094AA7203951A1D4ED31304 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 1D6CC27981A89B68E6FDD92F /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* compass_app.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 624CAC5D902868ED5B3541A6 /* Pods_Runner.framework */, + D3F947B2B2872CA43E01D9D3 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 06AAD07EFF5D1E8FAB2A6247 /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 0DDEBBC387E6CED7A7451269 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + 0C8C541B6AD305AF898FAB29 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* compass_app.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 06AAD07EFF5D1E8FAB2A6247 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 0C8C541B6AD305AF898FAB29 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 0DDEBBC387E6CED7A7451269 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9F59A18A4C705DE2978D2023 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.compassApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/compass_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/compass_app"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4E966B71DA7CDEDDA1AC2EDF /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.compassApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/compass_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/compass_app"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8094AA7203951A1D4ED31304 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.compassApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/compass_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/compass_app"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/compass_app/app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/compass_app/app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/compass_app/app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/compass_app/app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/compass_app/app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..2da2d9a --- /dev/null +++ b/compass_app/app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/compass_app/app/macos/Runner.xcworkspace/contents.xcworkspacedata b/compass_app/app/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/compass_app/app/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/compass_app/app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/compass_app/app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/compass_app/app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/compass_app/app/macos/Runner/AppDelegate.swift b/compass_app/app/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..8e02df2 --- /dev/null +++ b/compass_app/app/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/compass_app/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/compass_app/app/macos/Runner/Base.lproj/MainMenu.xib b/compass_app/app/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/compass_app/app/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/compass_app/app/macos/Runner/Configs/AppInfo.xcconfig b/compass_app/app/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..f56aaa1 --- /dev/null +++ b/compass_app/app/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = compass_app + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.compassApp + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright Β© 2024 com.example. All rights reserved. diff --git a/compass_app/app/macos/Runner/Configs/Debug.xcconfig b/compass_app/app/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/compass_app/app/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/compass_app/app/macos/Runner/Configs/Release.xcconfig b/compass_app/app/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/compass_app/app/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/compass_app/app/macos/Runner/Configs/Warnings.xcconfig b/compass_app/app/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/compass_app/app/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/compass_app/app/macos/Runner/DebugProfile.entitlements b/compass_app/app/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..c946719 --- /dev/null +++ b/compass_app/app/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + com.apple.security.network.client + + + diff --git a/compass_app/app/macos/Runner/Info.plist b/compass_app/app/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/compass_app/app/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/compass_app/app/macos/Runner/MainFlutterWindow.swift b/compass_app/app/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/compass_app/app/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/compass_app/app/macos/Runner/Release.entitlements b/compass_app/app/macos/Runner/Release.entitlements new file mode 100644 index 0000000..48271ac --- /dev/null +++ b/compass_app/app/macos/Runner/Release.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/compass_app/app/macos/RunnerTests/RunnerTests.swift b/compass_app/app/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/compass_app/app/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/compass_app/app/pubspec.yaml b/compass_app/app/pubspec.yaml new file mode 100644 index 0000000..cbdd3ff --- /dev/null +++ b/compass_app/app/pubspec.yaml @@ -0,0 +1,47 @@ +name: compass_app +description: >- + A sample app that helps users build and book itineraries for trips. +publish_to: none +version: 0.1.0 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + cached_network_image: ^3.4.1 + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + flutter_svg: ^2.0.16 + freezed_annotation: ^3.0.0 + go_router: ^16.0.0 + google_fonts: ^6.2.1 + intl: any + json_annotation: ^4.9.0 + logging: ^1.3.0 + provider: ^6.1.2 + share_plus: ^10.1.3 + shared_preferences: ^2.3.5 + +dev_dependencies: + analysis_defaults: + path: ../../analysis_defaults + flutter_test: + sdk: flutter + mocktail_image_network: ^1.2.0 + mocktail: ^1.0.4 + integration_test: + sdk: flutter + build_runner: ^2.4.14 + freezed: ^3.0.0 + json_serializable: ^6.9.0 + +flutter: + uses-material-design: true + assets: + - assets/activities.json + - assets/destinations.json + - assets/logo.svg + - assets/user.jpg diff --git a/compass_app/app/test/data/repositories/activity/activity_repository_local_test.dart b/compass_app/app/test/data/repositories/activity/activity_repository_local_test.dart new file mode 100644 index 0000000..a49702f --- /dev/null +++ b/compass_app/app/test/data/repositories/activity/activity_repository_local_test.dart @@ -0,0 +1,32 @@ +// 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. + +import 'package:compass_app/data/repositories/activity/activity_repository_local.dart'; +import 'package:compass_app/data/services/local/local_data_service.dart'; +import 'package:compass_app/utils/result.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../testing/utils/result.dart'; + +void main() { + group('ActivityRepositoryLocal tests', () { + // To load assets + TestWidgetsFlutterBinding.ensureInitialized(); + + final repository = ActivityRepositoryLocal( + localDataService: LocalDataService(), + ); + + test('should get by destination ref', () async { + final result = await repository.getByDestination('alaska'); + expect(result, isA()); + + final list = result.asOk.value; + expect(list.length, 20); + + final activity = list.first; + expect(activity.name, 'Glacier Trekking and Ice Climbing'); + }); + }); +} diff --git a/compass_app/app/test/data/repositories/activity/activity_repository_remote_test.dart b/compass_app/app/test/data/repositories/activity/activity_repository_remote_test.dart new file mode 100644 index 0000000..18cb5a5 --- /dev/null +++ b/compass_app/app/test/data/repositories/activity/activity_repository_remote_test.dart @@ -0,0 +1,50 @@ +// 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. + +import 'package:compass_app/data/repositories/activity/activity_repository.dart'; +import 'package:compass_app/data/repositories/activity/activity_repository_remote.dart'; +import 'package:compass_app/utils/result.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../testing/fakes/services/fake_api_client.dart'; +import '../../../../testing/utils/result.dart'; + +void main() { + group('ActivityRepositoryRemote tests', () { + late FakeApiClient apiClient; + late ActivityRepository repository; + + setUp(() { + apiClient = FakeApiClient(); + repository = ActivityRepositoryRemote(apiClient: apiClient); + }); + + test('should get activities for destination', () async { + final result = await repository.getByDestination('alaska'); + expect(result, isA()); + + final list = result.asOk.value; + expect(list.length, 1); + + final destination = list.first; + expect(destination.name, 'Glacier Trekking and Ice Climbing'); + + // Only one request happened + expect(apiClient.requestCount, 1); + }); + + test('should get destinations from cache', () async { + // Request destination once + var result = await repository.getByDestination('alaska'); + expect(result, isA()); + + // Request destination another time + result = await repository.getByDestination('alaska'); + expect(result, isA()); + + // Only one request happened + expect(apiClient.requestCount, 1); + }); + }); +} diff --git a/compass_app/app/test/data/repositories/auth/auth_repository_remote_test.dart b/compass_app/app/test/data/repositories/auth/auth_repository_remote_test.dart new file mode 100644 index 0000000..5d6934f --- /dev/null +++ b/compass_app/app/test/data/repositories/auth/auth_repository_remote_test.dart @@ -0,0 +1,102 @@ +// 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. + +import 'package:compass_app/data/repositories/auth/auth_repository_remote.dart'; +import 'package:compass_app/utils/result.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../testing/fakes/services/fake_api_client.dart'; +import '../../../../testing/fakes/services/fake_auth_api_client.dart'; +import '../../../../testing/fakes/services/fake_shared_preferences_service.dart'; + +void main() { + group('AuthRepositoryRemote tests', () { + late FakeApiClient apiClient; + late FakeAuthApiClient authApiClient; + late FakeSharedPreferencesService sharedPreferencesService; + late AuthRepositoryRemote repository; + + setUp(() { + apiClient = FakeApiClient(); + authApiClient = FakeAuthApiClient(); + sharedPreferencesService = FakeSharedPreferencesService(); + repository = AuthRepositoryRemote( + apiClient: apiClient, + authApiClient: authApiClient, + sharedPreferencesService: sharedPreferencesService, + ); + }); + + test('fetch on start, has token', () async { + // Stored token in shared preferences + sharedPreferencesService.token = 'TOKEN'; + + // Create an AuthRepository, should perform initial fetch + final repository = AuthRepositoryRemote( + apiClient: apiClient, + authApiClient: authApiClient, + sharedPreferencesService: sharedPreferencesService, + ); + + final isAuthenticated = await repository.isAuthenticated; + + // True because Token is SharedPreferences + expect(isAuthenticated, isTrue); + + // Check auth token + await expectAuthHeader(apiClient, 'Bearer TOKEN'); + }); + + test('fetch on start, no token', () async { + // Stored token in shared preferences + sharedPreferencesService.token = null; + + // Create an AuthRepository, should perform initial fetch + final repository = AuthRepositoryRemote( + apiClient: apiClient, + authApiClient: authApiClient, + sharedPreferencesService: sharedPreferencesService, + ); + + final isAuthenticated = await repository.isAuthenticated; + + // True because Token is SharedPreferences + expect(isAuthenticated, isFalse); + + // Check auth token + await expectAuthHeader(apiClient, null); + }); + + test('perform login', () async { + final result = await repository.login( + email: 'EMAIL', + password: 'PASSWORD', + ); + expect(result, isA()); + expect(await repository.isAuthenticated, isTrue); + expect(sharedPreferencesService.token, 'TOKEN'); + + // Check auth token + await expectAuthHeader(apiClient, 'Bearer TOKEN'); + }); + + test('perform logout', () async { + // logged in status + sharedPreferencesService.token = 'TOKEN'; + + final result = await repository.logout(); + expect(result, isA()); + expect(await repository.isAuthenticated, isFalse); + expect(sharedPreferencesService.token, null); + + // Check auth token + await expectAuthHeader(apiClient, null); + }); + }); +} + +Future expectAuthHeader(FakeApiClient apiClient, String? header) async { + final header = apiClient.authHeaderProvider?.call(); + expect(header, header); +} diff --git a/compass_app/app/test/data/repositories/booking/booking_repository_remote_test.dart b/compass_app/app/test/data/repositories/booking/booking_repository_remote_test.dart new file mode 100644 index 0000000..94987b6 --- /dev/null +++ b/compass_app/app/test/data/repositories/booking/booking_repository_remote_test.dart @@ -0,0 +1,59 @@ +// 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. + +import 'package:compass_app/data/repositories/booking/booking_repository.dart'; +import 'package:compass_app/data/repositories/booking/booking_repository_remote.dart'; +import 'package:compass_app/utils/result.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../testing/fakes/services/fake_api_client.dart'; +import '../../../../testing/models/booking.dart'; +import '../../../../testing/utils/result.dart'; + +void main() { + group('BookingRepositoryRemote tests', () { + late BookingRepository bookingRepository; + late FakeApiClient fakeApiClient; + + setUp(() { + fakeApiClient = FakeApiClient(); + bookingRepository = BookingRepositoryRemote(apiClient: fakeApiClient); + }); + + test('should get booking', () async { + final result = await bookingRepository.getBooking(0); + final booking = result.asOk.value; + expect(booking, kBooking.copyWith(id: 0)); + }); + + test('should create booking', () async { + expect(fakeApiClient.bookings, isEmpty); + final result = await bookingRepository.createBooking(kBooking); + expect(result, isA>()); + expect(fakeApiClient.bookings.first, kBookingApiModel); + }); + + test('should get list of booking', () async { + final result = await bookingRepository.getBookingsList(); + final list = result.asOk.value; + expect(list, [kBookingSummary]); + }); + + test('should delete booking', () async { + // Ensure no bookings exist + expect(fakeApiClient.bookings, isEmpty); + + // Add a booking + var result = await bookingRepository.createBooking(kBooking); + expect(result, isA>()); + + // Delete the booking + result = await bookingRepository.delete(0); + expect(result, isA>()); + + // Check if the booking was deleted from the server + expect(fakeApiClient.bookings, isEmpty); + }); + }); +} diff --git a/compass_app/app/test/data/repositories/continent/continent_repository_remote_test.dart b/compass_app/app/test/data/repositories/continent/continent_repository_remote_test.dart new file mode 100644 index 0000000..37ae24b --- /dev/null +++ b/compass_app/app/test/data/repositories/continent/continent_repository_remote_test.dart @@ -0,0 +1,50 @@ +// 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. + +import 'package:compass_app/data/repositories/continent/continent_repository.dart'; +import 'package:compass_app/data/repositories/continent/continent_repository_remote.dart'; +import 'package:compass_app/utils/result.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../testing/fakes/services/fake_api_client.dart'; +import '../../../../testing/utils/result.dart'; + +void main() { + group('ContinentRepositoryRemote tests', () { + late FakeApiClient apiClient; + late ContinentRepository repository; + + setUp(() { + apiClient = FakeApiClient(); + repository = ContinentRepositoryRemote(apiClient: apiClient); + }); + + test('should get continents', () async { + final result = await repository.getContinents(); + expect(result, isA()); + + final list = result.asOk.value; + expect(list.length, 3); + + final destination = list.first; + expect(destination.name, 'CONTINENT'); + + // Only one request happened + expect(apiClient.requestCount, 1); + }); + + test('should get continents from cache', () async { + // Request continents once + var result = await repository.getContinents(); + expect(result, isA()); + + // Request continents another time + result = await repository.getContinents(); + expect(result, isA()); + + // Only one request happened + expect(apiClient.requestCount, 1); + }); + }); +} diff --git a/compass_app/app/test/data/repositories/destination/destination_repository_local_test.dart b/compass_app/app/test/data/repositories/destination/destination_repository_local_test.dart new file mode 100644 index 0000000..8965348 --- /dev/null +++ b/compass_app/app/test/data/repositories/destination/destination_repository_local_test.dart @@ -0,0 +1,35 @@ +// 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. + +import 'package:compass_app/data/repositories/destination/destination_repository_local.dart'; +import 'package:compass_app/data/services/local/local_data_service.dart'; +import 'package:compass_app/utils/result.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../testing/utils/result.dart'; + +void main() { + group('DestinationRepositoryLocal tests', () { + // To load assets + TestWidgetsFlutterBinding.ensureInitialized(); + + final repository = DestinationRepositoryLocal( + localDataService: LocalDataService(), + ); + + test('should load and parse', () async { + // Should load the json and parse it + final result = await repository.getDestinations(); + expect(result, isA()); + + // Check that the list is complete + final list = result.asOk.value; + expect(list.length, 137); + + // Check first item + final destination = list.first; + expect(destination.name, 'Alaska'); + }); + }); +} diff --git a/compass_app/app/test/data/repositories/destination/destination_repository_remote_test.dart b/compass_app/app/test/data/repositories/destination/destination_repository_remote_test.dart new file mode 100644 index 0000000..d918f87 --- /dev/null +++ b/compass_app/app/test/data/repositories/destination/destination_repository_remote_test.dart @@ -0,0 +1,50 @@ +// 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. + +import 'package:compass_app/data/repositories/destination/destination_repository.dart'; +import 'package:compass_app/data/repositories/destination/destination_repository_remote.dart'; +import 'package:compass_app/utils/result.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../testing/fakes/services/fake_api_client.dart'; +import '../../../../testing/utils/result.dart'; + +void main() { + group('DestinationRepositoryRemote tests', () { + late FakeApiClient apiClient; + late DestinationRepository repository; + + setUp(() { + apiClient = FakeApiClient(); + repository = DestinationRepositoryRemote(apiClient: apiClient); + }); + + test('should get destinations', () async { + final result = await repository.getDestinations(); + expect(result, isA()); + + final list = result.asOk.value; + expect(list.length, 2); + + final destination = list.first; + expect(destination.name, 'name1'); + + // Only one request happened + expect(apiClient.requestCount, 1); + }); + + test('should get destinations from cache', () async { + // Request destination once + var result = await repository.getDestinations(); + expect(result, isA()); + + // Request destination another time + result = await repository.getDestinations(); + expect(result, isA()); + + // Only one request happened + expect(apiClient.requestCount, 1); + }); + }); +} diff --git a/compass_app/app/test/data/services/api/api_client_test.dart b/compass_app/app/test/data/services/api/api_client_test.dart new file mode 100644 index 0000000..ae76522 --- /dev/null +++ b/compass_app/app/test/data/services/api/api_client_test.dart @@ -0,0 +1,85 @@ +// 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. + +import 'package:compass_app/data/services/api/api_client.dart'; +import 'package:compass_app/domain/models/continent/continent.dart'; +import 'package:compass_app/utils/result.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../testing/mocks.dart'; +import '../../../../testing/models/activity.dart'; +import '../../../../testing/models/booking.dart'; +import '../../../../testing/models/destination.dart'; +import '../../../../testing/models/user.dart'; +import '../../../../testing/utils/result.dart'; + +void main() { + group('ApiClient', () { + late MockHttpClient mockHttpClient; + late ApiClient apiClient; + + setUp(() { + mockHttpClient = MockHttpClient(); + apiClient = ApiClient(clientFactory: () => mockHttpClient); + }); + + test('should get continents', () async { + final continents = [const Continent(name: 'NAME', imageUrl: 'URL')]; + mockHttpClient.mockGet('/continent', continents); + final result = await apiClient.getContinents(); + expect(result.asOk.value, continents); + }); + + test('should get activities by destination', () async { + final activites = [kActivity]; + mockHttpClient.mockGet( + '/destination/${kDestination1.ref}/activity', + activites, + ); + final result = await apiClient.getActivityByDestination( + kDestination1.ref, + ); + expect(result.asOk.value, activites); + }); + + test('should get booking', () async { + mockHttpClient.mockGet( + '/booking/${kBookingApiModel.id}', + kBookingApiModel, + ); + final result = await apiClient.getBooking(kBookingApiModel.id!); + expect(result.asOk.value, kBookingApiModel); + }); + + test('should get bookings', () async { + mockHttpClient.mockGet('/booking', [kBookingApiModel]); + final result = await apiClient.getBookings(); + expect(result.asOk.value, [kBookingApiModel]); + }); + + test('should get destinations', () async { + mockHttpClient.mockGet('/destination', [kDestination1]); + final result = await apiClient.getDestinations(); + expect(result.asOk.value, [kDestination1]); + }); + + test('should get user', () async { + mockHttpClient.mockGet('/user', userApiModel); + final result = await apiClient.getUser(); + expect(result.asOk.value, userApiModel); + }); + + test('should post booking', () async { + mockHttpClient.mockPost('/booking', kBookingApiModel); + final result = await apiClient.postBooking(kBookingApiModel); + expect(result.asOk.value, kBookingApiModel); + }); + + test('should delete booking', () async { + mockHttpClient.mockDelete('/booking/0'); + final result = await apiClient.deleteBooking(0); + expect(result, isA>()); + }); + }); +} diff --git a/compass_app/app/test/data/services/api/auth_api_client_test.dart b/compass_app/app/test/data/services/api/auth_api_client_test.dart new file mode 100644 index 0000000..a9bcd7e --- /dev/null +++ b/compass_app/app/test/data/services/api/auth_api_client_test.dart @@ -0,0 +1,32 @@ +// 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. + +import 'package:compass_app/data/services/api/auth_api_client.dart'; +import 'package:compass_app/data/services/api/model/login_request/login_request.dart'; +import 'package:compass_app/data/services/api/model/login_response/login_response.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../testing/mocks.dart'; +import '../../../../testing/utils/result.dart'; + +void main() { + group('AuthApiClient', () { + late MockHttpClient mockHttpClient; + late AuthApiClient apiClient; + + setUp(() { + mockHttpClient = MockHttpClient(); + apiClient = AuthApiClient(clientFactory: () => mockHttpClient); + }); + + test('should post login', () async { + const loginResponse = LoginResponse(token: 'TOKEN', userId: '123'); + mockHttpClient.mockPost('/login', loginResponse, 200); + final result = await apiClient.login( + const LoginRequest(email: 'EMAIL', password: 'PASSWORD'), + ); + expect(result.asOk.value, loginResponse); + }); + }); +} diff --git a/compass_app/app/test/domain/use_cases/booking/booking_create_use_case_test.dart b/compass_app/app/test/domain/use_cases/booking/booking_create_use_case_test.dart new file mode 100644 index 0000000..28e06a7 --- /dev/null +++ b/compass_app/app/test/domain/use_cases/booking/booking_create_use_case_test.dart @@ -0,0 +1,38 @@ +// 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. + +import 'package:compass_app/domain/models/itinerary_config/itinerary_config.dart'; +import 'package:compass_app/domain/use_cases/booking/booking_create_use_case.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../testing/fakes/repositories/fake_activities_repository.dart'; +import '../../../../testing/fakes/repositories/fake_booking_repository.dart'; +import '../../../../testing/fakes/repositories/fake_destination_repository.dart'; +import '../../../../testing/models/activity.dart'; +import '../../../../testing/models/booking.dart'; +import '../../../../testing/models/destination.dart'; +import '../../../../testing/utils/result.dart'; + +void main() { + group('BookingCreateUseCase tests', () { + test('Create booking', () async { + final useCase = BookingCreateUseCase( + activityRepository: FakeActivityRepository(), + destinationRepository: FakeDestinationRepository(), + bookingRepository: FakeBookingRepository(), + ); + + final booking = await useCase.createFrom( + ItineraryConfig( + startDate: DateTime(2024, 01, 01), + endDate: DateTime(2024, 02, 12), + destination: kDestination1.ref, + activities: [kActivity.ref], + ), + ); + + expect(booking.asOk.value, kBooking); + }); + }); +} diff --git a/compass_app/app/test/domain/use_cases/booking/booking_share_use_case_test.dart b/compass_app/app/test/domain/use_cases/booking/booking_share_use_case_test.dart new file mode 100644 index 0000000..946bb6e --- /dev/null +++ b/compass_app/app/test/domain/use_cases/booking/booking_share_use_case_test.dart @@ -0,0 +1,35 @@ +// 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. + +import 'package:compass_app/domain/models/booking/booking.dart'; +import 'package:compass_app/domain/use_cases/booking/booking_share_use_case.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../testing/models/activity.dart'; +import '../../../../testing/models/destination.dart'; + +void main() { + group('BookingShareUseCase tests', () { + test('Share booking', () async { + String? sharedText; + final useCase = BookingShareUseCase.custom((text) async { + sharedText = text; + }); + final booking = Booking( + startDate: DateTime(2024, 01, 01), + endDate: DateTime(2024, 02, 12), + destination: kDestination1, + activity: [kActivity], + ); + await useCase.shareBooking(booking); + expect( + sharedText, + 'Trip to name1\n' + 'on 1 Jan - 12 Feb\n' + 'Activities:\n' + ' - NAME.', + ); + }); + }); +} diff --git a/compass_app/app/test/ui/activities/activities_screen_test.dart b/compass_app/app/test/ui/activities/activities_screen_test.dart new file mode 100644 index 0000000..1ed1a4c --- /dev/null +++ b/compass_app/app/test/ui/activities/activities_screen_test.dart @@ -0,0 +1,82 @@ +// 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. + +import 'package:compass_app/domain/models/itinerary_config/itinerary_config.dart'; +import 'package:compass_app/ui/activities/view_models/activities_viewmodel.dart'; +import 'package:compass_app/ui/activities/widgets/activities_screen.dart'; +import 'package:compass_app/ui/activities/widgets/activity_entry.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:mocktail_image_network/mocktail_image_network.dart'; + +import '../../../testing/app.dart'; +import '../../../testing/fakes/repositories/fake_activities_repository.dart'; +import '../../../testing/fakes/repositories/fake_itinerary_config_repository.dart'; +import '../../../testing/mocks.dart'; + +void main() { + group('ResultsScreen widget tests', () { + late ActivitiesViewModel viewModel; + late MockGoRouter goRouter; + + setUp(() { + viewModel = ActivitiesViewModel( + activityRepository: FakeActivityRepository(), + itineraryConfigRepository: FakeItineraryConfigRepository( + itineraryConfig: ItineraryConfig( + continent: 'Europe', + startDate: DateTime(2024, 01, 01), + endDate: DateTime(2024, 01, 31), + guests: 2, + destination: 'DESTINATION', + ), + ), + ); + goRouter = MockGoRouter(); + }); + + Future loadScreen(WidgetTester tester) async { + await testApp( + tester, + ActivitiesScreen(viewModel: viewModel), + goRouter: goRouter, + ); + } + + testWidgets('should load screen', (tester) async { + await mockNetworkImages(() async { + await loadScreen(tester); + expect(find.byType(ActivitiesScreen), findsOneWidget); + }); + }); + + testWidgets('should list activity', (tester) async { + await mockNetworkImages(() async { + await loadScreen(tester); + expect(find.byType(ActivityEntry), findsOneWidget); + expect(find.text('NAME'), findsOneWidget); + }); + }); + + testWidgets('should select activity and confirm', (tester) async { + await mockNetworkImages(() async { + await loadScreen(tester); + // Select one activity + await tester.tap(find.byKey(const ValueKey('REF-checkbox'))); + expect(viewModel.selectedActivities, contains('REF')); + + // Text 1 selected should appear + await tester.pumpAndSettle(); + expect(find.text('1 selected'), findsOneWidget); + + // Submit selection + await tester.tap(find.byKey(const ValueKey('confirm-button'))); + + // Should navigate to results screen + verify(() => goRouter.go('/booking')).called(1); + }); + }); + }); +} diff --git a/compass_app/app/test/ui/auth/login_screen_test.dart b/compass_app/app/test/ui/auth/login_screen_test.dart new file mode 100644 index 0000000..80eb45e --- /dev/null +++ b/compass_app/app/test/ui/auth/login_screen_test.dart @@ -0,0 +1,61 @@ +// 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. + +import 'package:compass_app/ui/auth/login/view_models/login_viewmodel.dart'; +import 'package:compass_app/ui/auth/login/widgets/login_screen.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:mocktail_image_network/mocktail_image_network.dart'; + +import '../../../testing/app.dart'; +import '../../../testing/fakes/repositories/fake_auth_repository.dart'; +import '../../../testing/mocks.dart'; + +void main() { + group('LoginScreen test', () { + late LoginViewModel viewModel; + late MockGoRouter goRouter; + late FakeAuthRepository fakeAuthRepository; + + setUp(() { + fakeAuthRepository = FakeAuthRepository(); + viewModel = LoginViewModel(authRepository: fakeAuthRepository); + goRouter = MockGoRouter(); + }); + + Future loadScreen(WidgetTester tester) async { + await testApp( + tester, + LoginScreen(viewModel: viewModel), + goRouter: goRouter, + ); + } + + testWidgets('should load screen', (tester) async { + await mockNetworkImages(() async { + await loadScreen(tester); + expect(find.byType(LoginScreen), findsOneWidget); + }); + }); + + testWidgets('should perform login', (tester) async { + await mockNetworkImages(() async { + await loadScreen(tester); + + // Repo should have no key + expect(fakeAuthRepository.token, null); + + // Perform login + await tester.tap(find.text('Login')); + await tester.pumpAndSettle(); + + // Repo should have key + expect(fakeAuthRepository.token, 'TOKEN'); + + // Should navigate to home screen + verify(() => goRouter.go('/')).called(1); + }); + }); + }); +} diff --git a/compass_app/app/test/ui/auth/logout_button_test.dart b/compass_app/app/test/ui/auth/logout_button_test.dart new file mode 100644 index 0000000..52397db --- /dev/null +++ b/compass_app/app/test/ui/auth/logout_button_test.dart @@ -0,0 +1,79 @@ +// 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. + +import 'package:compass_app/domain/models/itinerary_config/itinerary_config.dart'; +import 'package:compass_app/ui/auth/logout/view_models/logout_viewmodel.dart'; +import 'package:compass_app/ui/auth/logout/widgets/logout_button.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail_image_network/mocktail_image_network.dart'; + +import '../../../testing/app.dart'; +import '../../../testing/fakes/repositories/fake_auth_repository.dart'; +import '../../../testing/fakes/repositories/fake_itinerary_config_repository.dart'; +import '../../../testing/mocks.dart'; + +void main() { + group('LogoutButton test', () { + late MockGoRouter goRouter; + late FakeAuthRepository fakeAuthRepository; + late FakeItineraryConfigRepository fakeItineraryConfigRepository; + late LogoutViewModel viewModel; + + setUp(() { + goRouter = MockGoRouter(); + fakeAuthRepository = FakeAuthRepository(); + // Setup a token, should be cleared after logout + fakeAuthRepository.token = 'TOKEN'; + // Setup an ItineraryConfig with some data, should be cleared after logout + fakeItineraryConfigRepository = FakeItineraryConfigRepository( + itineraryConfig: const ItineraryConfig(continent: 'CONTINENT'), + ); + viewModel = LogoutViewModel( + authRepository: fakeAuthRepository, + itineraryConfigRepository: fakeItineraryConfigRepository, + ); + }); + + Future loadScreen(WidgetTester tester) async { + await testApp( + tester, + LogoutButton(viewModel: viewModel), + goRouter: goRouter, + ); + } + + testWidgets('should load widget', (tester) async { + await mockNetworkImages(() async { + await loadScreen(tester); + expect(find.byType(LogoutButton), findsOneWidget); + }); + }); + + testWidgets('should perform logout', (tester) async { + await mockNetworkImages(() async { + await loadScreen(tester); + + // Repo should have a key + expect(fakeAuthRepository.token, 'TOKEN'); + // Itinerary config should have data + expect( + fakeItineraryConfigRepository.itineraryConfig, + const ItineraryConfig(continent: 'CONTINENT'), + ); + + // // Perform logout + await tester.tap(find.byType(LogoutButton)); + await tester.pumpAndSettle(); + + // Repo should have no key + expect(fakeAuthRepository.token, null); + // Itinerary config should be cleared + expect( + fakeItineraryConfigRepository.itineraryConfig, + const ItineraryConfig(), + ); + }); + }); + }); +} diff --git a/compass_app/app/test/ui/booking/booking_screen_test.dart b/compass_app/app/test/ui/booking/booking_screen_test.dart new file mode 100644 index 0000000..5499dc9 --- /dev/null +++ b/compass_app/app/test/ui/booking/booking_screen_test.dart @@ -0,0 +1,114 @@ +// 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. + +import 'dart:async'; + +import 'package:compass_app/domain/models/itinerary_config/itinerary_config.dart'; +import 'package:compass_app/domain/use_cases/booking/booking_create_use_case.dart'; +import 'package:compass_app/domain/use_cases/booking/booking_share_use_case.dart'; +import 'package:compass_app/ui/booking/view_models/booking_viewmodel.dart'; +import 'package:compass_app/ui/booking/widgets/booking_screen.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../testing/app.dart'; +import '../../../testing/fakes/repositories/fake_activities_repository.dart'; +import '../../../testing/fakes/repositories/fake_booking_repository.dart'; +import '../../../testing/fakes/repositories/fake_destination_repository.dart'; +import '../../../testing/fakes/repositories/fake_itinerary_config_repository.dart'; +import '../../../testing/mocks.dart'; +import '../../../testing/models/activity.dart'; +import '../../../testing/models/booking.dart'; +import '../../../testing/models/destination.dart'; + +void main() { + group('BookingScreen widget tests', () { + late MockGoRouter goRouter; + late BookingViewModel viewModel; + late bool shared; + late FakeBookingRepository bookingRepository; + + setUp(() { + shared = false; + bookingRepository = FakeBookingRepository(); + viewModel = BookingViewModel( + itineraryConfigRepository: FakeItineraryConfigRepository( + itineraryConfig: ItineraryConfig( + continent: 'Europe', + startDate: DateTime(2024, 01, 01), + endDate: DateTime(2024, 01, 31), + guests: 2, + destination: kDestination1.ref, + activities: [kActivity.ref], + ), + ), + createBookingUseCase: BookingCreateUseCase( + activityRepository: FakeActivityRepository(), + destinationRepository: FakeDestinationRepository(), + bookingRepository: bookingRepository, + ), + shareBookingUseCase: BookingShareUseCase.custom((text) async { + shared = true; + }), + bookingRepository: bookingRepository, + ); + goRouter = MockGoRouter(); + }); + + Future loadScreen(WidgetTester tester) async { + await testApp( + tester, + BookingScreen(viewModel: viewModel), + goRouter: goRouter, + ); + } + + testWidgets('should load screen', (tester) async { + await loadScreen(tester); + expect(find.byType(BookingScreen), findsOneWidget); + }); + + testWidgets('should display booking from ID', (tester) async { + // Add a booking to repository + unawaited(bookingRepository.createBooking(kBooking)); + + // Load screen + await loadScreen(tester); + + // Load booking with ID 0 + unawaited(viewModel.loadBooking.execute(0)); + + // Wait for booking to load + await tester.pumpAndSettle(); + + expect(find.text(kBooking.destination.name), findsOneWidget); + expect(find.text(kBooking.destination.tags.first), findsOneWidget); + }); + + testWidgets('should create booking from itinerary config', (tester) async { + await loadScreen(tester); + + // Create a new booking from stored itinerary config + unawaited(viewModel.createBooking.execute()); + + // Wait for booking to load + await tester.pumpAndSettle(); + + expect(find.text('name1'), findsOneWidget); + expect(find.text('tags1'), findsOneWidget); + + // Booking is saved + expect(bookingRepository.bookings.length, 1); + }); + + testWidgets('should share booking', (tester) async { + unawaited(bookingRepository.createBooking(kBooking)); + await loadScreen(tester); + unawaited(viewModel.loadBooking.execute(0)); + await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key('share-button'))); + expect(shared, true); + }); + }); +} diff --git a/compass_app/app/test/ui/home/widgets/home_screen_test.dart b/compass_app/app/test/ui/home/widgets/home_screen_test.dart new file mode 100644 index 0000000..4464f53 --- /dev/null +++ b/compass_app/app/test/ui/home/widgets/home_screen_test.dart @@ -0,0 +1,128 @@ +// 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. + +import 'package:compass_app/routing/routes.dart'; +import 'package:compass_app/ui/auth/logout/view_models/logout_viewmodel.dart'; +import 'package:compass_app/ui/home/view_models/home_viewmodel.dart'; +import 'package:compass_app/ui/home/widgets/home_screen.dart'; +import 'package:compass_app/utils/result.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../../../testing/app.dart'; +import '../../../../testing/fakes/repositories/fake_auth_repository.dart'; +import '../../../../testing/fakes/repositories/fake_booking_repository.dart'; +import '../../../../testing/fakes/repositories/fake_itinerary_config_repository.dart'; +import '../../../../testing/fakes/repositories/fake_user_repository.dart'; +import '../../../../testing/mocks.dart'; +import '../../../../testing/models/booking.dart'; + +void main() { + group('HomeScreen tests', () { + late HomeViewModel viewModel; + late LogoutViewModel logoutViewModel; + late MockGoRouter goRouter; + late FakeBookingRepository bookingRepository; + + setUp(() { + bookingRepository = FakeBookingRepository()..createBooking(kBooking); + viewModel = HomeViewModel( + bookingRepository: bookingRepository, + userRepository: FakeUserRepository(), + ); + logoutViewModel = LogoutViewModel( + authRepository: FakeAuthRepository(), + itineraryConfigRepository: FakeItineraryConfigRepository(), + ); + goRouter = MockGoRouter(); + when(() => goRouter.push(any())).thenAnswer((_) => Future.value(null)); + }); + + Future loadWidget(WidgetTester tester) async { + await testApp( + tester, + HomeScreen(viewModel: viewModel, logoutViewModel: logoutViewModel), + goRouter: goRouter, + ); + } + + testWidgets('should load screen', (tester) async { + await loadWidget(tester); + await tester.pumpAndSettle(); + + expect(find.byType(HomeScreen), findsOneWidget); + }); + + testWidgets('should show user name', (tester) async { + await loadWidget(tester); + await tester.pumpAndSettle(); + + expect(find.text('NAME\'s Trips'), findsOneWidget); + }); + + testWidgets('should navigate to search', (tester) async { + await loadWidget(tester); + await tester.pumpAndSettle(); + + // Tap on create a booking FAB + await tester.tap(find.byKey(const ValueKey('booking-button'))); + await tester.pumpAndSettle(); + + // Should navigate to results screen + verify(() => goRouter.go(Routes.search)).called(1); + }); + + testWidgets('should open existing booking', (tester) async { + await loadWidget(tester); + await tester.pumpAndSettle(); + + // Tap on booking (created from kBooking) + await tester.tap(find.text('name1, Europe')); + await tester.pumpAndSettle(); + + // Should navigate to results screen + verify(() => goRouter.push(Routes.bookingWithId(0))).called(1); + }); + + testWidgets('should delete booking', (tester) async { + await loadWidget(tester); + await tester.pumpAndSettle(); + + // Swipe on booking (created from kBooking) + await tester.drag(find.text('name1, Europe'), const Offset(-1000, 0)); + await tester.pumpAndSettle(); + + // Existing booking should be gone + expect(find.text('name1, Europe'), findsNothing); + + // Booking should be deleted from repository + expect(bookingRepository.bookings, isEmpty); + }); + + testWidgets('fail to delete booking', (tester) async { + // Create a ViewModel with a repository that will fail to delete + viewModel = HomeViewModel( + bookingRepository: _BadFakeBookingRepository()..createBooking(kBooking), + userRepository: FakeUserRepository(), + ); + await loadWidget(tester); + await tester.pumpAndSettle(); + + // Swipe on booking (created from kBooking) + await tester.drag(find.text('name1, Europe'), const Offset(-1000, 0)); + await tester.pumpAndSettle(); + + // Existing booking should be there + expect(find.text('name1, Europe'), findsOneWidget); + }); + }); +} + +class _BadFakeBookingRepository extends FakeBookingRepository { + @override + Future> delete(int id) async { + return Result.error(Exception('Failed to delete booking')); + } +} diff --git a/compass_app/app/test/ui/results/results_screen_test.dart b/compass_app/app/test/ui/results/results_screen_test.dart new file mode 100644 index 0000000..f7197a9 --- /dev/null +++ b/compass_app/app/test/ui/results/results_screen_test.dart @@ -0,0 +1,79 @@ +// 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. + +import 'package:compass_app/domain/models/itinerary_config/itinerary_config.dart'; +import 'package:compass_app/ui/results/view_models/results_viewmodel.dart'; +import 'package:compass_app/ui/results/widgets/results_screen.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:mocktail_image_network/mocktail_image_network.dart'; + +import '../../../testing/app.dart'; +import '../../../testing/fakes/repositories/fake_destination_repository.dart'; +import '../../../testing/fakes/repositories/fake_itinerary_config_repository.dart'; +import '../../../testing/mocks.dart'; + +void main() { + group('ResultsScreen widget tests', () { + late MockGoRouter goRouter; + late ResultsViewModel viewModel; + + setUp(() { + viewModel = ResultsViewModel( + destinationRepository: FakeDestinationRepository(), + itineraryConfigRepository: FakeItineraryConfigRepository( + itineraryConfig: ItineraryConfig( + continent: 'Europe', + startDate: DateTime(2024, 01, 01), + endDate: DateTime(2024, 01, 31), + guests: 2, + ), + ), + ); + goRouter = MockGoRouter(); + }); + + Future loadScreen(WidgetTester tester) async { + await testApp( + tester, + ResultsScreen(viewModel: viewModel), + goRouter: goRouter, + ); + } + + testWidgets('should load screen', (tester) async { + await mockNetworkImages(() async { + await loadScreen(tester); + expect(find.byType(ResultsScreen), findsOneWidget); + }); + }); + + testWidgets('should display destination', (tester) async { + await mockNetworkImages(() async { + await loadScreen(tester); + + // Wait for list to load + await tester.pumpAndSettle(); + + // Note: Name is converted to uppercase + expect(find.text('NAME1'), findsOneWidget); + expect(find.text('tags1'), findsOneWidget); + }); + }); + + testWidgets('should tap and navigate to activities', (tester) async { + await mockNetworkImages(() async { + await loadScreen(tester); + + // Wait for list to load + await tester.pumpAndSettle(); + + // warnIfMissed false because false negative + await tester.tap(find.text('NAME1'), warnIfMissed: false); + + verify(() => goRouter.go('/activities')).called(1); + }); + }); + }); +} diff --git a/compass_app/app/test/ui/results/results_viewmodel_test.dart b/compass_app/app/test/ui/results/results_viewmodel_test.dart new file mode 100644 index 0000000..4031cc7 --- /dev/null +++ b/compass_app/app/test/ui/results/results_viewmodel_test.dart @@ -0,0 +1,32 @@ +// 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. + +import 'package:compass_app/domain/models/itinerary_config/itinerary_config.dart'; +import 'package:compass_app/ui/results/view_models/results_viewmodel.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../testing/fakes/repositories/fake_destination_repository.dart'; +import '../../../testing/fakes/repositories/fake_itinerary_config_repository.dart'; + +void main() { + group('ResultsViewModel tests', () { + final viewModel = ResultsViewModel( + destinationRepository: FakeDestinationRepository(), + itineraryConfigRepository: FakeItineraryConfigRepository( + itineraryConfig: ItineraryConfig( + continent: 'Europe', + startDate: DateTime(2024, 01, 01), + endDate: DateTime(2024, 01, 31), + guests: 2, + ), + ), + ); + + // perform a simple test + // verifies that the list of items is properly loaded + test('should load items', () async { + expect(viewModel.destinations.length, 2); + }); + }); +} diff --git a/compass_app/app/test/ui/search_form/view_models/search_form_viewmodel_test.dart b/compass_app/app/test/ui/search_form/view_models/search_form_viewmodel_test.dart new file mode 100644 index 0000000..87da6df --- /dev/null +++ b/compass_app/app/test/ui/search_form/view_models/search_form_viewmodel_test.dart @@ -0,0 +1,73 @@ +// 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. + +import 'package:compass_app/ui/search_form/view_models/search_form_viewmodel.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../testing/fakes/repositories/fake_continent_repository.dart'; +import '../../../../testing/fakes/repositories/fake_itinerary_config_repository.dart'; + +void main() { + group('SearchFormViewModel Tests', () { + late SearchFormViewModel viewModel; + + setUp(() { + viewModel = SearchFormViewModel( + continentRepository: FakeContinentRepository(), + itineraryConfigRepository: FakeItineraryConfigRepository(), + ); + }); + + test('Initial values are correct', () { + expect(viewModel.valid, false); + expect(viewModel.selectedContinent, null); + expect(viewModel.dateRange, null); + expect(viewModel.guests, 0); + }); + + test('Setting dateRange updates correctly', () { + final newDateRange = DateTimeRange( + start: DateTime(2024, 1, 1), + end: DateTime(2024, 1, 31), + ); + viewModel.dateRange = newDateRange; + expect(viewModel.dateRange, newDateRange); + }); + + test('Setting selectedContinent updates correctly', () { + viewModel.selectedContinent = 'CONTINENT'; + expect(viewModel.selectedContinent, 'CONTINENT'); + + // Setting null should work + viewModel.selectedContinent = null; + expect(viewModel.selectedContinent, null); + }); + + test('Setting guests updates correctly', () { + viewModel.guests = 2; + expect(viewModel.guests, 2); + + // Guests number should not be negative + viewModel.guests = -1; + expect(viewModel.guests, 0); + }); + + test('Set all values and save', () async { + expect(viewModel.valid, false); + + viewModel.guests = 2; + viewModel.selectedContinent = 'CONTINENT'; + final newDateRange = DateTimeRange( + start: DateTime(2024, 1, 1), + end: DateTime(2024, 1, 31), + ); + viewModel.dateRange = newDateRange; + + expect(viewModel.valid, true); + await viewModel.updateItineraryConfig.execute(); + expect(viewModel.updateItineraryConfig.completed, true); + }); + }); +} diff --git a/compass_app/app/test/ui/search_form/widgets/search_form_continent_test.dart b/compass_app/app/test/ui/search_form/widgets/search_form_continent_test.dart new file mode 100644 index 0000000..a009d35 --- /dev/null +++ b/compass_app/app/test/ui/search_form/widgets/search_form_continent_test.dart @@ -0,0 +1,38 @@ +// 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. + +import 'package:compass_app/ui/search_form/view_models/search_form_viewmodel.dart'; +import 'package:compass_app/ui/search_form/widgets/search_form_continent.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../testing/app.dart'; +import '../../../../testing/fakes/repositories/fake_continent_repository.dart'; +import '../../../../testing/fakes/repositories/fake_itinerary_config_repository.dart'; + +void main() { + group('SearchFormContinent widget tests', () { + late SearchFormViewModel viewModel; + + setUp(() { + viewModel = SearchFormViewModel( + continentRepository: FakeContinentRepository(), + itineraryConfigRepository: FakeItineraryConfigRepository(), + ); + }); + + loadWidget(WidgetTester tester) async { + await testApp(tester, SearchFormContinent(viewModel: viewModel)); + } + + testWidgets('Should load and select continent', (tester) async { + await loadWidget(tester); + expect(find.byType(SearchFormContinent), findsOneWidget); + + // Select continent + await tester.tap(find.text('CONTINENT'), warnIfMissed: false); + + expect(viewModel.selectedContinent, 'CONTINENT'); + }); + }); +} diff --git a/compass_app/app/test/ui/search_form/widgets/search_form_date_test.dart b/compass_app/app/test/ui/search_form/widgets/search_form_date_test.dart new file mode 100644 index 0000000..d9cb5e2 --- /dev/null +++ b/compass_app/app/test/ui/search_form/widgets/search_form_date_test.dart @@ -0,0 +1,63 @@ +// 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. + +import 'package:compass_app/ui/search_form/view_models/search_form_viewmodel.dart'; +import 'package:compass_app/ui/search_form/widgets/search_form_date.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../testing/app.dart'; +import '../../../../testing/fakes/repositories/fake_continent_repository.dart'; +import '../../../../testing/fakes/repositories/fake_itinerary_config_repository.dart'; + +void main() { + group('SearchFormDate widget tests', () { + late SearchFormViewModel viewModel; + + setUp(() { + viewModel = SearchFormViewModel( + continentRepository: FakeContinentRepository(), + itineraryConfigRepository: FakeItineraryConfigRepository(), + ); + }); + + loadWidget(WidgetTester tester) async { + await testApp(tester, SearchFormDate(viewModel: viewModel)); + } + + testWidgets('should display date in different month', (tester) async { + await loadWidget(tester); + expect(find.byType(SearchFormDate), findsOneWidget); + + // Initial state + expect(find.text('Add Dates'), findsOneWidget); + + // Simulate date picker input: + viewModel.dateRange = DateTimeRange( + start: DateTime(2024, 6, 12), + end: DateTime(2024, 7, 23), + ); + await tester.pumpAndSettle(); + + expect(find.text('12 Jun - 23 Jul'), findsOneWidget); + }); + + testWidgets('should display date in same month', (tester) async { + await loadWidget(tester); + expect(find.byType(SearchFormDate), findsOneWidget); + + // Initial state + expect(find.text('Add Dates'), findsOneWidget); + + // Simulate date picker input: + viewModel.dateRange = DateTimeRange( + start: DateTime(2024, 6, 12), + end: DateTime(2024, 6, 23), + ); + await tester.pumpAndSettle(); + + expect(find.text('12 - 23 Jun'), findsOneWidget); + }); + }); +} diff --git a/compass_app/app/test/ui/search_form/widgets/search_form_guests_test.dart b/compass_app/app/test/ui/search_form/widgets/search_form_guests_test.dart new file mode 100644 index 0000000..5aff3a9 --- /dev/null +++ b/compass_app/app/test/ui/search_form/widgets/search_form_guests_test.dart @@ -0,0 +1,68 @@ +// 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. + +import 'package:compass_app/ui/search_form/view_models/search_form_viewmodel.dart'; +import 'package:compass_app/ui/search_form/widgets/search_form_guests.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../../testing/app.dart'; +import '../../../../testing/fakes/repositories/fake_continent_repository.dart'; +import '../../../../testing/fakes/repositories/fake_itinerary_config_repository.dart'; + +void main() { + group('SearchFormGuests widget tests', () { + late SearchFormViewModel viewModel; + + setUp(() { + viewModel = SearchFormViewModel( + continentRepository: FakeContinentRepository(), + itineraryConfigRepository: FakeItineraryConfigRepository(), + ); + }); + + loadWidget(WidgetTester tester) async { + await testApp(tester, SearchFormGuests(viewModel: viewModel)); + } + + testWidgets('Increase number of guests', (tester) async { + await loadWidget(tester); + expect(find.byType(SearchFormGuests), findsOneWidget); + + // Initial state + expect(find.text('0'), findsOneWidget); + + await tester.tap(find.byKey(const ValueKey(addGuestsKey))); + await tester.pumpAndSettle(); + + expect(find.text('1'), findsOneWidget); + }); + + testWidgets('Decrease number of guests', (tester) async { + await loadWidget(tester); + expect(find.byType(SearchFormGuests), findsOneWidget); + + // Initial state + expect(find.text('0'), findsOneWidget); + + await tester.tap(find.byKey(const ValueKey(removeGuestsKey))); + await tester.pumpAndSettle(); + + // Should remain at 0 + expect(find.text('0'), findsOneWidget); + + await tester.tap(find.byKey(const ValueKey(addGuestsKey))); + await tester.pumpAndSettle(); + + // Increase to 1 + expect(find.text('1'), findsOneWidget); + + await tester.tap(find.byKey(const ValueKey(removeGuestsKey))); + await tester.pumpAndSettle(); + + // Back to 0 + expect(find.text('0'), findsOneWidget); + }); + }); +} diff --git a/compass_app/app/test/ui/search_form/widgets/search_form_screen_test.dart b/compass_app/app/test/ui/search_form/widgets/search_form_screen_test.dart new file mode 100644 index 0000000..9d22a12 --- /dev/null +++ b/compass_app/app/test/ui/search_form/widgets/search_form_screen_test.dart @@ -0,0 +1,75 @@ +// 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. + +import 'package:compass_app/data/repositories/auth/auth_repository.dart'; +import 'package:compass_app/data/repositories/itinerary_config/itinerary_config_repository.dart'; +import 'package:compass_app/ui/search_form/view_models/search_form_viewmodel.dart'; +import 'package:compass_app/ui/search_form/widgets/search_form_guests.dart'; +import 'package:compass_app/ui/search_form/widgets/search_form_screen.dart'; +import 'package:compass_app/ui/search_form/widgets/search_form_submit.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:provider/provider.dart'; + +import '../../../../testing/app.dart'; +import '../../../../testing/fakes/repositories/fake_auth_repository.dart'; +import '../../../../testing/fakes/repositories/fake_continent_repository.dart'; +import '../../../../testing/fakes/repositories/fake_itinerary_config_repository.dart'; +import '../../../../testing/mocks.dart'; + +void main() { + group('SearchFormScreen widget tests', () { + late SearchFormViewModel viewModel; + late MockGoRouter goRouter; + + setUp(() { + viewModel = SearchFormViewModel( + continentRepository: FakeContinentRepository(), + itineraryConfigRepository: FakeItineraryConfigRepository(), + ); + goRouter = MockGoRouter(); + }); + + loadWidget(WidgetTester tester) async { + await testApp( + tester, + ChangeNotifierProvider.value( + value: FakeAuthRepository() as AuthRepository, + child: Provider.value( + value: FakeItineraryConfigRepository() as ItineraryConfigRepository, + child: SearchFormScreen(viewModel: viewModel), + ), + ), + goRouter: goRouter, + ); + } + + testWidgets('Should fill form and perform search', (tester) async { + await loadWidget(tester); + expect(find.byType(SearchFormScreen), findsOneWidget); + + // Select continent + await tester.tap(find.text('CONTINENT'), warnIfMissed: false); + + // Select date + viewModel.dateRange = DateTimeRange( + start: DateTime(2024, 6, 12), + end: DateTime(2024, 7, 23), + ); + + // Select guests + await tester.tap(find.byKey(const ValueKey(addGuestsKey))); + + // Refresh screen state + await tester.pumpAndSettle(); + + // Perform search + await tester.tap(find.byKey(const ValueKey(searchFormSubmitButtonKey))); + + // Should navigate to results screen + verify(() => goRouter.go('/results')).called(1); + }); + }); +} diff --git a/compass_app/app/test/ui/search_form/widgets/search_form_submit_test.dart b/compass_app/app/test/ui/search_form/widgets/search_form_submit_test.dart new file mode 100644 index 0000000..4fa99b5 --- /dev/null +++ b/compass_app/app/test/ui/search_form/widgets/search_form_submit_test.dart @@ -0,0 +1,62 @@ +// 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. + +import 'package:compass_app/ui/search_form/view_models/search_form_viewmodel.dart'; +import 'package:compass_app/ui/search_form/widgets/search_form_submit.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../../../testing/app.dart'; +import '../../../../testing/fakes/repositories/fake_continent_repository.dart'; +import '../../../../testing/fakes/repositories/fake_itinerary_config_repository.dart'; +import '../../../../testing/mocks.dart'; + +void main() { + group('SearchFormSubmit widget tests', () { + late SearchFormViewModel viewModel; + late MockGoRouter goRouter; + + setUp(() { + viewModel = SearchFormViewModel( + continentRepository: FakeContinentRepository(), + itineraryConfigRepository: FakeItineraryConfigRepository(), + ); + goRouter = MockGoRouter(); + }); + + loadWidget(WidgetTester tester) async { + await testApp( + tester, + SearchFormSubmit(viewModel: viewModel), + goRouter: goRouter, + ); + } + + testWidgets('Should be enabled and allow tap', (tester) async { + await loadWidget(tester); + expect(find.byType(SearchFormSubmit), findsOneWidget); + + // Tap should not navigate + await tester.tap(find.byKey(const ValueKey(searchFormSubmitButtonKey))); + verifyNever(() => goRouter.go(any())); + + // Fill in data + viewModel.guests = 2; + viewModel.selectedContinent = 'CONTINENT'; + final newDateRange = DateTimeRange( + start: DateTime(2024, 1, 1), + end: DateTime(2024, 1, 31), + ); + viewModel.dateRange = newDateRange; + await tester.pumpAndSettle(); + + // Perform search + await tester.tap(find.byKey(const ValueKey(searchFormSubmitButtonKey))); + + // Should navigate to results screen + verify(() => goRouter.go('/results')).called(1); + }); + }); +} diff --git a/compass_app/app/test/utils/command_test.dart b/compass_app/app/test/utils/command_test.dart new file mode 100644 index 0000000..733f383 --- /dev/null +++ b/compass_app/app/test/utils/command_test.dart @@ -0,0 +1,108 @@ +// 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. + +import 'dart:async'; + +import 'package:compass_app/utils/command.dart'; +import 'package:compass_app/utils/result.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../testing/utils/result.dart'; + +void main() { + group('Command0 tests', () { + test('should complete void command', () async { + // Void action + final command = Command0(() => Future.value(Result.ok(null))); + + // Run void action + await command.execute(); + + // Action completed + expect(command.completed, true); + }); + + test('should complete bool command', () async { + // Action that returns bool + final command = Command0(() => Future.value(Result.ok(true))); + + // Run action with result + await command.execute(); + + // Action completed + expect(command.completed, true); + expect(command.result!.asOk.value, true); + }); + + test('running should be true', () async { + final command = Command0(() => Future.value(Result.ok(null))); + final future = command.execute(); + + // Action is running + expect(command.running, true); + + // Await execution + await future; + + // Action finished running + expect(command.running, false); + }); + + test('should only run once', () async { + var count = 0; + final command = Command0(() => Future.value(Result.ok(count++))); + final future = command.execute(); + + // Run multiple times + unawaited(command.execute()); + unawaited(command.execute()); + unawaited(command.execute()); + unawaited(command.execute()); + + // Await execution + await future; + + // Action is called once + expect(count, 1); + }); + + test('should handle errors', () async { + final command = Command0( + () => Future.value(Result.error(Exception('ERROR!'))), + ); + await command.execute(); + expect(command.error, true); + expect(command.result, isA()); + }); + }); + + group('Command1 tests', () { + test('should complete void command, bool argument', () async { + // Void action with bool argument + final command = Command1((a) { + expect(a, true); + return Future.value(Result.ok(null)); + }); + + // Run void action, ignore void return + await command.execute(true); + + expect(command.completed, true); + }); + + test('should complete bool command, bool argument', () async { + // Action that returns bool argument + final command = Command1( + (a) => Future.value(const Result.ok(true)), + ); + + // Run action with result and argument + await command.execute(true); + + // Argument was passed to onComplete + expect(command.completed, true); + expect(command.result!.asOk.value, true); + }); + }); +} diff --git a/compass_app/app/testing/app.dart b/compass_app/app/testing/app.dart new file mode 100644 index 0000000..48ff313 --- /dev/null +++ b/compass_app/app/testing/app.dart @@ -0,0 +1,38 @@ +// 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. + +import 'package:compass_app/ui/core/localization/applocalization.dart'; +import 'package:compass_app/ui/core/themes/theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mocktail_image_network/mocktail_image_network.dart'; + +import 'mocks.dart'; + +Future testApp( + WidgetTester tester, + Widget body, { + GoRouter? goRouter, +}) async { + tester.view.devicePixelRatio = 1.0; + await tester.binding.setSurfaceSize(const Size(1200, 800)); + await mockNetworkImages(() async { + await tester.pumpWidget( + MaterialApp( + localizationsDelegates: [ + GlobalWidgetsLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + AppLocalizationDelegate(), + ], + theme: AppTheme.lightTheme, + home: InheritedGoRouter( + goRouter: goRouter ?? MockGoRouter(), + child: Scaffold(body: body), + ), + ), + ); + }); +} diff --git a/compass_app/app/testing/fakes/repositories/fake_activities_repository.dart b/compass_app/app/testing/fakes/repositories/fake_activities_repository.dart new file mode 100644 index 0000000..84c8173 --- /dev/null +++ b/compass_app/app/testing/fakes/repositories/fake_activities_repository.dart @@ -0,0 +1,23 @@ +// 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. + +import 'package:compass_app/data/repositories/activity/activity_repository.dart'; +import 'package:compass_app/domain/models/activity/activity.dart'; +import 'package:compass_app/utils/result.dart'; +import 'package:flutter/foundation.dart'; + +import '../../models/activity.dart'; +import '../../models/destination.dart'; + +class FakeActivityRepository implements ActivityRepository { + Map> activities = { + "DESTINATION": [kActivity], + kDestination1.ref: [kActivity], + }; + + @override + Future>> getByDestination(String ref) { + return SynchronousFuture(Result.ok(activities[ref]!)); + } +} diff --git a/compass_app/app/testing/fakes/repositories/fake_auth_repository.dart b/compass_app/app/testing/fakes/repositories/fake_auth_repository.dart new file mode 100644 index 0000000..55b8d1a --- /dev/null +++ b/compass_app/app/testing/fakes/repositories/fake_auth_repository.dart @@ -0,0 +1,30 @@ +// 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. + +import 'package:compass_app/data/repositories/auth/auth_repository.dart'; +import 'package:compass_app/utils/result.dart'; + +class FakeAuthRepository extends AuthRepository { + String? token; + + @override + Future get isAuthenticated async => token != null; + + @override + Future> login({ + required String email, + required String password, + }) async { + token = 'TOKEN'; + notifyListeners(); + return Result.ok(null); + } + + @override + Future> logout() async { + token = null; + notifyListeners(); + return Result.ok(null); + } +} diff --git a/compass_app/app/testing/fakes/repositories/fake_booking_repository.dart b/compass_app/app/testing/fakes/repositories/fake_booking_repository.dart new file mode 100644 index 0000000..d6c3302 --- /dev/null +++ b/compass_app/app/testing/fakes/repositories/fake_booking_repository.dart @@ -0,0 +1,50 @@ +// 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. + +import 'package:compass_app/data/repositories/booking/booking_repository.dart'; +import 'package:compass_app/domain/models/booking/booking.dart'; +import 'package:compass_app/domain/models/booking/booking_summary.dart'; +import 'package:compass_app/utils/result.dart'; + +class FakeBookingRepository implements BookingRepository { + List bookings = List.empty(growable: true); + int sequentialId = 0; + + @override + Future> createBooking(Booking booking) async { + final bookingWithId = booking.copyWith(id: sequentialId++); + bookings.add(bookingWithId); + return Result.ok(null); + } + + @override + Future> getBooking(int id) async { + return Result.ok(bookings[id]); + } + + @override + Future>> getBookingsList() async { + return Result.ok(_createSummaries()); + } + + List _createSummaries() { + return bookings + .map( + (booking) => BookingSummary( + id: booking.id!, + name: + '${booking.destination.name}, ${booking.destination.continent}', + startDate: booking.startDate, + endDate: booking.endDate, + ), + ) + .toList(); + } + + @override + Future> delete(int id) async { + bookings.removeWhere((booking) => booking.id == id); + return Result.ok(null); + } +} diff --git a/compass_app/app/testing/fakes/repositories/fake_continent_repository.dart b/compass_app/app/testing/fakes/repositories/fake_continent_repository.dart new file mode 100644 index 0000000..12a3b50 --- /dev/null +++ b/compass_app/app/testing/fakes/repositories/fake_continent_repository.dart @@ -0,0 +1,21 @@ +// 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. + +import 'package:compass_app/data/repositories/continent/continent_repository.dart'; +import 'package:compass_app/domain/models/continent/continent.dart'; +import 'package:compass_app/utils/result.dart'; +import 'package:flutter/foundation.dart'; + +class FakeContinentRepository implements ContinentRepository { + @override + Future>> getContinents() { + return SynchronousFuture( + Result.ok([ + const Continent(name: 'CONTINENT', imageUrl: 'URL'), + const Continent(name: 'CONTINENT2', imageUrl: 'URL'), + const Continent(name: 'CONTINENT3', imageUrl: 'URL'), + ]), + ); + } +} diff --git a/compass_app/app/testing/fakes/repositories/fake_destination_repository.dart b/compass_app/app/testing/fakes/repositories/fake_destination_repository.dart new file mode 100644 index 0000000..65ec4d5 --- /dev/null +++ b/compass_app/app/testing/fakes/repositories/fake_destination_repository.dart @@ -0,0 +1,17 @@ +// 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. + +import 'package:compass_app/data/repositories/destination/destination_repository.dart'; +import 'package:compass_app/domain/models/destination/destination.dart'; +import 'package:compass_app/utils/result.dart'; +import 'package:flutter/foundation.dart'; + +import '../../models/destination.dart'; + +class FakeDestinationRepository implements DestinationRepository { + @override + Future>> getDestinations() { + return SynchronousFuture(Result.ok([kDestination1, kDestination2])); + } +} diff --git a/compass_app/app/testing/fakes/repositories/fake_itinerary_config_repository.dart b/compass_app/app/testing/fakes/repositories/fake_itinerary_config_repository.dart new file mode 100644 index 0000000..2260e8b --- /dev/null +++ b/compass_app/app/testing/fakes/repositories/fake_itinerary_config_repository.dart @@ -0,0 +1,27 @@ +// 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. + +import 'package:compass_app/data/repositories/itinerary_config/itinerary_config_repository.dart'; +import 'package:compass_app/domain/models/itinerary_config/itinerary_config.dart'; +import 'package:compass_app/utils/result.dart'; +import 'package:flutter/foundation.dart'; + +class FakeItineraryConfigRepository implements ItineraryConfigRepository { + FakeItineraryConfigRepository({this.itineraryConfig}); + + ItineraryConfig? itineraryConfig; + + @override + Future> getItineraryConfig() { + return SynchronousFuture( + Result.ok(itineraryConfig ?? const ItineraryConfig()), + ); + } + + @override + Future> setItineraryConfig(ItineraryConfig itineraryConfig) { + this.itineraryConfig = itineraryConfig; + return SynchronousFuture(Result.ok(null)); + } +} diff --git a/compass_app/app/testing/fakes/repositories/fake_user_repository.dart b/compass_app/app/testing/fakes/repositories/fake_user_repository.dart new file mode 100644 index 0000000..ef354d0 --- /dev/null +++ b/compass_app/app/testing/fakes/repositories/fake_user_repository.dart @@ -0,0 +1,16 @@ +// 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. + +import 'package:compass_app/data/repositories/user/user_repository.dart'; +import 'package:compass_app/domain/models/user/user.dart'; +import 'package:compass_app/utils/result.dart'; + +import '../../models/user.dart'; + +class FakeUserRepository implements UserRepository { + @override + Future> getUser() async { + return Result.ok(user); + } +} diff --git a/compass_app/app/testing/fakes/services/fake_api_client.dart b/compass_app/app/testing/fakes/services/fake_api_client.dart new file mode 100644 index 0000000..e2e1459 --- /dev/null +++ b/compass_app/app/testing/fakes/services/fake_api_client.dart @@ -0,0 +1,118 @@ +// 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. + +import 'package:compass_app/data/services/api/api_client.dart'; +import 'package:compass_app/data/services/api/model/booking/booking_api_model.dart'; +import 'package:compass_app/data/services/api/model/user/user_api_model.dart'; +import 'package:compass_app/domain/models/activity/activity.dart'; +import 'package:compass_app/domain/models/continent/continent.dart'; +import 'package:compass_app/domain/models/destination/destination.dart'; +import 'package:compass_app/utils/result.dart'; + +import '../../models/activity.dart'; +import '../../models/booking.dart'; +import '../../models/user.dart'; + +class FakeApiClient implements ApiClient { + // Should not increase when using cached data + int requestCount = 0; + + @override + Future>> getContinents() async { + requestCount++; + return Result.ok([ + const Continent(name: 'CONTINENT', imageUrl: 'URL'), + const Continent(name: 'CONTINENT2', imageUrl: 'URL'), + const Continent(name: 'CONTINENT3', imageUrl: 'URL'), + ]); + } + + @override + Future>> getDestinations() async { + requestCount++; + return Result.ok([ + const Destination( + ref: 'ref1', + name: 'name1', + country: 'country1', + continent: 'Europe', + knownFor: 'knownFor1', + tags: ['tags1'], + imageUrl: 'imageUrl1', + ), + const Destination( + ref: 'ref2', + name: 'name2', + country: 'country2', + continent: 'Europe', + knownFor: 'knownFor2', + tags: ['tags2'], + imageUrl: 'imageUrl2', + ), + ]); + } + + @override + Future>> getActivityByDestination(String ref) async { + requestCount++; + + if (ref == 'alaska') { + return Result.ok([ + const Activity( + name: 'Glacier Trekking and Ice Climbing', + description: + 'Embark on a thrilling adventure exploring the awe-inspiring glaciers of Alaska. Hike across the icy terrain, marvel at the deep blue crevasses, and even try your hand at ice climbing for an unforgettable experience.', + locationName: 'Matanuska Glacier or Mendenhall Glacier', + duration: 8, + timeOfDay: TimeOfDay.morning, + familyFriendly: false, + price: 4, + destinationRef: 'alaska', + ref: 'glacier-trekking-and-ice-climbing', + imageUrl: + 'https://storage.googleapis.com/tripedia-images/activities/alaska_glacier-trekking-and-ice-climbing.jpg', + ), + ]); + } + + if (ref == kBooking.destination.ref) { + return Result.ok([kActivity]); + } + + return Result.ok([]); + } + + @override + AuthHeaderProvider? authHeaderProvider; + + @override + Future> getBooking(int id) async { + return Result.ok(kBookingApiModel); + } + + @override + Future>> getBookings() async { + return Result.ok([kBookingApiModel]); + } + + List bookings = []; + + @override + Future> postBooking(BookingApiModel booking) async { + final bookingWithId = booking.copyWith(id: bookings.length); + bookings.add(bookingWithId); + return Result.ok(bookingWithId); + } + + @override + Future> getUser() async { + return Result.ok(userApiModel); + } + + @override + Future> deleteBooking(int id) async { + bookings.removeWhere((booking) => booking.id == id); + return Result.ok(null); + } +} diff --git a/compass_app/app/testing/fakes/services/fake_auth_api_client.dart b/compass_app/app/testing/fakes/services/fake_auth_api_client.dart new file mode 100644 index 0000000..af4ea34 --- /dev/null +++ b/compass_app/app/testing/fakes/services/fake_auth_api_client.dart @@ -0,0 +1,18 @@ +// 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. + +import 'package:compass_app/data/services/api/auth_api_client.dart'; +import 'package:compass_app/data/services/api/model/login_request/login_request.dart'; +import 'package:compass_app/data/services/api/model/login_response/login_response.dart'; +import 'package:compass_app/utils/result.dart'; + +class FakeAuthApiClient implements AuthApiClient { + @override + Future> login(LoginRequest loginRequest) async { + if (loginRequest.email == 'EMAIL' && loginRequest.password == 'PASSWORD') { + return const Result.ok(LoginResponse(token: 'TOKEN', userId: '123')); + } + return Result.error(Exception('ERROR!')); + } +} diff --git a/compass_app/app/testing/fakes/services/fake_shared_preferences_service.dart b/compass_app/app/testing/fakes/services/fake_shared_preferences_service.dart new file mode 100644 index 0000000..2778692 --- /dev/null +++ b/compass_app/app/testing/fakes/services/fake_shared_preferences_service.dart @@ -0,0 +1,21 @@ +// 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. + +import 'package:compass_app/data/services/shared_preferences_service.dart'; +import 'package:compass_app/utils/result.dart'; + +class FakeSharedPreferencesService implements SharedPreferencesService { + String? token; + + @override + Future> fetchToken() async { + return Result.ok(token); + } + + @override + Future> saveToken(String? token) async { + this.token = token; + return Result.ok(null); + } +} diff --git a/compass_app/app/testing/mocks.dart b/compass_app/app/testing/mocks.dart new file mode 100644 index 0000000..d8e78c6 --- /dev/null +++ b/compass_app/app/testing/mocks.dart @@ -0,0 +1,60 @@ +// 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. + +import 'dart:convert'; +import 'dart:io'; + +import 'package:go_router/go_router.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockGoRouter extends Mock implements GoRouter {} + +class MockHttpClient extends Mock implements HttpClient {} + +class MockHttpHeaders extends Mock implements HttpHeaders {} + +class MockHttpClientRequest extends Mock implements HttpClientRequest {} + +class MockHttpClientResponse extends Mock implements HttpClientResponse {} + +extension HttpMethodMocks on MockHttpClient { + void mockGet(String path, Object object) { + when(() => get(any(), any(), path)).thenAnswer((invocation) { + final request = MockHttpClientRequest(); + final response = MockHttpClientResponse(); + when(() => request.close()).thenAnswer((_) => Future.value(response)); + when(() => request.headers).thenReturn(MockHttpHeaders()); + when(() => response.statusCode).thenReturn(200); + when( + () => response.transform(utf8.decoder), + ).thenAnswer((_) => Stream.value(jsonEncode(object))); + return Future.value(request); + }); + } + + void mockPost(String path, Object object, [int statusCode = 201]) { + when(() => post(any(), any(), path)).thenAnswer((invocation) { + final request = MockHttpClientRequest(); + final response = MockHttpClientResponse(); + when(() => request.close()).thenAnswer((_) => Future.value(response)); + when(() => request.headers).thenReturn(MockHttpHeaders()); + when(() => response.statusCode).thenReturn(statusCode); + when( + () => response.transform(utf8.decoder), + ).thenAnswer((_) => Stream.value(jsonEncode(object))); + return Future.value(request); + }); + } + + void mockDelete(String path) { + when(() => delete(any(), any(), path)).thenAnswer((invocation) { + final request = MockHttpClientRequest(); + final response = MockHttpClientResponse(); + when(() => request.close()).thenAnswer((_) => Future.value(response)); + when(() => request.headers).thenReturn(MockHttpHeaders()); + when(() => response.statusCode).thenReturn(204); + return Future.value(request); + }); + } +} diff --git a/compass_app/app/testing/models/activity.dart b/compass_app/app/testing/models/activity.dart new file mode 100644 index 0000000..a8e482a --- /dev/null +++ b/compass_app/app/testing/models/activity.dart @@ -0,0 +1,18 @@ +// 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. + +import 'package:compass_app/domain/models/activity/activity.dart'; + +const kActivity = Activity( + description: 'DESCRIPTION', + destinationRef: 'DESTINATION', + duration: 3, + familyFriendly: true, + imageUrl: 'http://example.com/img.png', + locationName: 'LOCATION NAME', + name: 'NAME', + price: 3, + ref: 'REF', + timeOfDay: TimeOfDay.afternoon, +); diff --git a/compass_app/app/testing/models/booking.dart b/compass_app/app/testing/models/booking.dart new file mode 100644 index 0000000..9e3a34b --- /dev/null +++ b/compass_app/app/testing/models/booking.dart @@ -0,0 +1,33 @@ +// 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. + +import 'package:compass_app/data/services/api/model/booking/booking_api_model.dart'; +import 'package:compass_app/domain/models/booking/booking.dart'; +import 'package:compass_app/domain/models/booking/booking_summary.dart'; + +import 'activity.dart'; +import 'destination.dart'; + +final kBooking = Booking( + startDate: DateTime(2024, 01, 01), + endDate: DateTime(2024, 02, 12), + destination: kDestination1, + activity: [kActivity], +); + +final kBookingSummary = BookingSummary( + id: 0, + startDate: kBooking.startDate, + endDate: kBooking.endDate, + name: '${kDestination1.name}, ${kDestination1.continent}', +); + +final kBookingApiModel = BookingApiModel( + id: 0, + startDate: kBooking.startDate, + endDate: kBooking.endDate, + name: '${kDestination1.name}, ${kDestination1.continent}', + destinationRef: kDestination1.ref, + activitiesRef: [kActivity.ref], +); diff --git a/compass_app/app/testing/models/destination.dart b/compass_app/app/testing/models/destination.dart new file mode 100644 index 0000000..9844cb0 --- /dev/null +++ b/compass_app/app/testing/models/destination.dart @@ -0,0 +1,25 @@ +// 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. + +import 'package:compass_app/domain/models/destination/destination.dart'; + +const kDestination1 = Destination( + ref: 'ref1', + name: 'name1', + country: 'country1', + continent: 'Europe', + knownFor: 'knownFor1', + tags: ['tags1'], + imageUrl: 'imageUrl1', +); + +const kDestination2 = Destination( + ref: 'ref2', + name: 'name2', + country: 'country2', + continent: 'Europe', + knownFor: 'knownFor2', + tags: ['tags2'], + imageUrl: 'imageUrl2', +); diff --git a/compass_app/app/testing/models/user.dart b/compass_app/app/testing/models/user.dart new file mode 100644 index 0000000..60d9858 --- /dev/null +++ b/compass_app/app/testing/models/user.dart @@ -0,0 +1,15 @@ +// 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. + +import 'package:compass_app/data/services/api/model/user/user_api_model.dart'; +import 'package:compass_app/domain/models/user/user.dart'; + +const userApiModel = UserApiModel( + id: 'ID', + name: 'NAME', + email: 'EMAIL', + picture: 'assets/user.jpg', +); + +const user = User(name: 'NAME', picture: 'assets/user.jpg'); diff --git a/compass_app/app/testing/utils/result.dart b/compass_app/app/testing/utils/result.dart new file mode 100644 index 0000000..457003c --- /dev/null +++ b/compass_app/app/testing/utils/result.dart @@ -0,0 +1,9 @@ +import 'package:compass_app/utils/result.dart'; + +extension ResultCast on Result { + /// Convenience method to cast to Ok + Ok get asOk => this as Ok; + + /// Convenience method to cast to Error + Error get asError => this as Error; +} diff --git a/compass_app/app/web/favicon.png b/compass_app/app/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/compass_app/app/web/favicon.png differ diff --git a/compass_app/app/web/icons/Icon-192.png b/compass_app/app/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/compass_app/app/web/icons/Icon-192.png differ diff --git a/compass_app/app/web/icons/Icon-512.png b/compass_app/app/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/compass_app/app/web/icons/Icon-512.png differ diff --git a/compass_app/app/web/icons/Icon-maskable-192.png b/compass_app/app/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/compass_app/app/web/icons/Icon-maskable-192.png differ diff --git a/compass_app/app/web/icons/Icon-maskable-512.png b/compass_app/app/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/compass_app/app/web/icons/Icon-maskable-512.png differ diff --git a/compass_app/app/web/index.html b/compass_app/app/web/index.html new file mode 100644 index 0000000..39a66ce --- /dev/null +++ b/compass_app/app/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + compass_app + + + + + + diff --git a/compass_app/app/web/manifest.json b/compass_app/app/web/manifest.json new file mode 100644 index 0000000..36e3af0 --- /dev/null +++ b/compass_app/app/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "compass_app", + "short_name": "compass_app", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/compass_app/app/windows/.gitignore b/compass_app/app/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/compass_app/app/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/compass_app/app/windows/CMakeLists.txt b/compass_app/app/windows/CMakeLists.txt new file mode 100644 index 0000000..492d7ff --- /dev/null +++ b/compass_app/app/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(compass_app LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "compass_app") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/compass_app/app/windows/flutter/CMakeLists.txt b/compass_app/app/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/compass_app/app/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/compass_app/app/windows/flutter/generated_plugin_registrant.cc b/compass_app/app/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..c3384ec --- /dev/null +++ b/compass_app/app/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,17 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + SharePlusWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/compass_app/app/windows/flutter/generated_plugin_registrant.h b/compass_app/app/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/compass_app/app/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/compass_app/app/windows/flutter/generated_plugins.cmake b/compass_app/app/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..01d3836 --- /dev/null +++ b/compass_app/app/windows/flutter/generated_plugins.cmake @@ -0,0 +1,25 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + share_plus + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/compass_app/app/windows/runner/CMakeLists.txt b/compass_app/app/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/compass_app/app/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/compass_app/app/windows/runner/Runner.rc b/compass_app/app/windows/runner/Runner.rc new file mode 100644 index 0000000..7eb0a40 --- /dev/null +++ b/compass_app/app/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "compass_app" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "compass_app" "\0" + VALUE "LegalCopyright", "Copyright (C) 2024 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "compass_app.exe" "\0" + VALUE "ProductName", "compass_app" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/compass_app/app/windows/runner/flutter_window.cpp b/compass_app/app/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/compass_app/app/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/compass_app/app/windows/runner/flutter_window.h b/compass_app/app/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/compass_app/app/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/compass_app/app/windows/runner/main.cpp b/compass_app/app/windows/runner/main.cpp new file mode 100644 index 0000000..6f64c30 --- /dev/null +++ b/compass_app/app/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"compass_app", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/compass_app/app/windows/runner/resource.h b/compass_app/app/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/compass_app/app/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/compass_app/app/windows/runner/resources/app_icon.ico b/compass_app/app/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/compass_app/app/windows/runner/resources/app_icon.ico differ diff --git a/compass_app/app/windows/runner/runner.exe.manifest b/compass_app/app/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..a42ea76 --- /dev/null +++ b/compass_app/app/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/compass_app/app/windows/runner/utils.cpp b/compass_app/app/windows/runner/utils.cpp new file mode 100644 index 0000000..3a0b465 --- /dev/null +++ b/compass_app/app/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/compass_app/app/windows/runner/utils.h b/compass_app/app/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/compass_app/app/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/compass_app/app/windows/runner/win32_window.cpp b/compass_app/app/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/compass_app/app/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/compass_app/app/windows/runner/win32_window.h b/compass_app/app/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/compass_app/app/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/compass_app/server/.gitignore b/compass_app/server/.gitignore new file mode 100644 index 0000000..3a85790 --- /dev/null +++ b/compass_app/server/.gitignore @@ -0,0 +1,3 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ diff --git a/compass_app/server/analysis_options.yaml b/compass_app/server/analysis_options.yaml new file mode 100644 index 0000000..d2c523a --- /dev/null +++ b/compass_app/server/analysis_options.yaml @@ -0,0 +1,19 @@ +linter: + rules: + - combinators_ordering + - directives_ordering + - omit_local_variable_types + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_relative_imports + +analyzer: + exclude: + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** diff --git a/compass_app/server/bin/compass_server.dart b/compass_app/server/bin/compass_server.dart new file mode 100644 index 0000000..5dc7c62 --- /dev/null +++ b/compass_app/server/bin/compass_server.dart @@ -0,0 +1,40 @@ +// 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. + +import 'dart:io'; + +import 'package:compass_server/middleware/auth.dart'; +import 'package:compass_server/routes/booking.dart'; +import 'package:compass_server/routes/continent.dart'; +import 'package:compass_server/routes/destination.dart'; +import 'package:compass_server/routes/login.dart'; +import 'package:compass_server/routes/user.dart'; +import 'package:shelf/shelf.dart'; +import 'package:shelf/shelf_io.dart'; +import 'package:shelf_router/shelf_router.dart'; + +// Configure routes. +final _router = Router() + ..get('/continent', continentHandler) + ..mount('/destination', DestinationApi().router.call) + ..mount('/booking', BookingApi().router.call) + ..mount('/user', UserApi().router.call) + ..mount('/login', LoginApi().router.call); + +void main(List args) async { + // Use any available host or container IP (usually `0.0.0.0`). + final ip = InternetAddress.anyIPv4; + + // Configure a pipeline that logs requests. + final handler = Pipeline() + .addMiddleware(logRequests()) + .addMiddleware(authRequests()) + .addHandler(_router.call); + + // For running in containers, we respect the PORT environment variable. + final port = int.parse(Platform.environment['PORT'] ?? '8080'); + final server = await serve(handler, ip, port); + // ignore: avoid_print + print('Server listening on port ${server.port}'); +} diff --git a/compass_app/server/lib/config/assets.dart b/compass_app/server/lib/config/assets.dart new file mode 100644 index 0000000..e2bec53 --- /dev/null +++ b/compass_app/server/lib/config/assets.dart @@ -0,0 +1,23 @@ +// 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. + +import 'dart:convert'; +import 'dart:io'; + +import '../model/activity/activity.dart'; +import '../model/destination/destination.dart'; + +abstract final class Assets { + static const _activities = '../app/assets/activities.json'; + static const _destinations = '../app/assets/destinations.json'; + + static final List destinations = + (json.decode(File(Assets._destinations).readAsStringSync()) as List) + .map((element) => Destination.fromJson(element)) + .toList(); + static final List activities = + (json.decode(File(Assets._activities).readAsStringSync()) as List) + .map((element) => Activity.fromJson(element)) + .toList(); +} diff --git a/compass_app/server/lib/config/constants.dart b/compass_app/server/lib/config/constants.dart new file mode 100644 index 0000000..a3cc6d6 --- /dev/null +++ b/compass_app/server/lib/config/constants.dart @@ -0,0 +1,34 @@ +// 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. + +import '../model/user/user.dart'; + +abstract final class Constants { + /// Email for the hardcoded login. + static const email = 'email@example.com'; + + /// Password for the hardcoded login. + static const password = 'password'; + + /// Token to be returned on successful login. + static const token = + ' e1c37dfd973353b78bb71df050e2c6e72d53034e148920383968ae49b96f1fd2'; + + /// User id to be returned on successful login. + static const userId = '123'; + + /// User name for the hardcoded user. + static const name = 'Sofie'; + + /// For demo purposes we use a local asset. + static const picture = 'assets/user.jpg'; + + /// Hardcoded user. + static const user = User( + id: Constants.userId, + name: Constants.name, + email: Constants.email, + picture: Constants.picture, + ); +} diff --git a/compass_app/server/lib/middleware/auth.dart b/compass_app/server/lib/middleware/auth.dart new file mode 100644 index 0000000..e9108ab --- /dev/null +++ b/compass_app/server/lib/middleware/auth.dart @@ -0,0 +1,30 @@ +// 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. + +import 'package:shelf/shelf.dart'; + +import '../config/constants.dart'; + +/// Implements a simple auth Middleware. +/// +/// This is provided as an example for Flutter architectural purposes only +/// and shouldn't be used as example on how to implement authentication +/// in production. +/// +/// This Middleware checks if the token is present in the request headers, +/// otherwise returns a 401 Unauthorized response. +/// +/// This token does not expire and is not secure. +Middleware authRequests() => (innerHandler) { + return (Request request) async { + if (request.url.path != 'login' && + request.headers['Authorization'] != 'Bearer ${Constants.token}') { + // If the request is not a login request and the token is not present, + // return a 401 Unauthorized response. + return Response.unauthorized('Unauthorized'); + } + + return innerHandler(request); + }; +}; diff --git a/compass_app/server/lib/model/activity/activity.dart b/compass_app/server/lib/model/activity/activity.dart new file mode 100644 index 0000000..654861a --- /dev/null +++ b/compass_app/server/lib/model/activity/activity.dart @@ -0,0 +1,50 @@ +// 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. + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'activity.freezed.dart'; + +part 'activity.g.dart'; + +enum TimeOfDay { any, morning, afternoon, evening, night } + +@freezed +class Activity with _$Activity { + const factory Activity({ + /// e.g. 'Glacier Trekking and Ice Climbing' + required String name, + + /// e.g. 'Embark on a thrilling adventure exploring the awe-inspiring glaciers of Alaska. Hike across the icy terrain, marvel at the deep blue crevasses, and even try your hand at ice climbing for an unforgettable experience.' + required String description, + + /// e.g. 'Matanuska Glacier or Mendenhall Glacier' + required String locationName, + + /// Duration in days. + /// e.g. 8 + required int duration, + + /// e.g. 'morning' + required TimeOfDay timeOfDay, + + /// e.g. false + required bool familyFriendly, + + /// e.g. 4 + required int price, + + /// e.g. 'alaska' + required String destinationRef, + + /// e.g. 'glacier-trekking-and-ice-climbing' + required String ref, + + /// e.g. 'https://storage.googleapis.com/tripedia-images/activities/alaska_glacier-trekking-and-ice-climbing.jpg' + required String imageUrl, + }) = _Activity; + + factory Activity.fromJson(Map json) => + _$ActivityFromJson(json); +} diff --git a/compass_app/server/lib/model/activity/activity.freezed.dart b/compass_app/server/lib/model/activity/activity.freezed.dart new file mode 100644 index 0000000..0c89f88 --- /dev/null +++ b/compass_app/server/lib/model/activity/activity.freezed.dart @@ -0,0 +1,436 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'activity.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +Activity _$ActivityFromJson(Map json) { + return _Activity.fromJson(json); +} + +/// @nodoc +mixin _$Activity { + /// e.g. 'Glacier Trekking and Ice Climbing' + String get name => throw _privateConstructorUsedError; + + /// e.g. 'Embark on a thrilling adventure exploring the awe-inspiring glaciers of Alaska. Hike across the icy terrain, marvel at the deep blue crevasses, and even try your hand at ice climbing for an unforgettable experience.' + String get description => throw _privateConstructorUsedError; + + /// e.g. 'Matanuska Glacier or Mendenhall Glacier' + String get locationName => throw _privateConstructorUsedError; + + /// Duration in days. + /// e.g. 8 + int get duration => throw _privateConstructorUsedError; + + /// e.g. 'morning' + TimeOfDay get timeOfDay => throw _privateConstructorUsedError; + + /// e.g. false + bool get familyFriendly => throw _privateConstructorUsedError; + + /// e.g. 4 + int get price => throw _privateConstructorUsedError; + + /// e.g. 'alaska' + String get destinationRef => throw _privateConstructorUsedError; + + /// e.g. 'glacier-trekking-and-ice-climbing' + String get ref => throw _privateConstructorUsedError; + + /// e.g. 'https://storage.googleapis.com/tripedia-images/activities/alaska_glacier-trekking-and-ice-climbing.jpg' + String get imageUrl => throw _privateConstructorUsedError; + + /// Serializes this Activity to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of Activity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ActivityCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ActivityCopyWith<$Res> { + factory $ActivityCopyWith(Activity value, $Res Function(Activity) then) = + _$ActivityCopyWithImpl<$Res, Activity>; + @useResult + $Res call({ + String name, + String description, + String locationName, + int duration, + TimeOfDay timeOfDay, + bool familyFriendly, + int price, + String destinationRef, + String ref, + String imageUrl, + }); +} + +/// @nodoc +class _$ActivityCopyWithImpl<$Res, $Val extends Activity> + implements $ActivityCopyWith<$Res> { + _$ActivityCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Activity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? description = null, + Object? locationName = null, + Object? duration = null, + Object? timeOfDay = null, + Object? familyFriendly = null, + Object? price = null, + Object? destinationRef = null, + Object? ref = null, + Object? imageUrl = null, + }) { + return _then( + _value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + locationName: null == locationName + ? _value.locationName + : locationName // ignore: cast_nullable_to_non_nullable + as String, + duration: null == duration + ? _value.duration + : duration // ignore: cast_nullable_to_non_nullable + as int, + timeOfDay: null == timeOfDay + ? _value.timeOfDay + : timeOfDay // ignore: cast_nullable_to_non_nullable + as TimeOfDay, + familyFriendly: null == familyFriendly + ? _value.familyFriendly + : familyFriendly // ignore: cast_nullable_to_non_nullable + as bool, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as int, + destinationRef: null == destinationRef + ? _value.destinationRef + : destinationRef // ignore: cast_nullable_to_non_nullable + as String, + ref: null == ref + ? _value.ref + : ref // ignore: cast_nullable_to_non_nullable + as String, + imageUrl: null == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ActivityImplCopyWith<$Res> + implements $ActivityCopyWith<$Res> { + factory _$$ActivityImplCopyWith( + _$ActivityImpl value, + $Res Function(_$ActivityImpl) then, + ) = __$$ActivityImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String name, + String description, + String locationName, + int duration, + TimeOfDay timeOfDay, + bool familyFriendly, + int price, + String destinationRef, + String ref, + String imageUrl, + }); +} + +/// @nodoc +class __$$ActivityImplCopyWithImpl<$Res> + extends _$ActivityCopyWithImpl<$Res, _$ActivityImpl> + implements _$$ActivityImplCopyWith<$Res> { + __$$ActivityImplCopyWithImpl( + _$ActivityImpl _value, + $Res Function(_$ActivityImpl) _then, + ) : super(_value, _then); + + /// Create a copy of Activity + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? description = null, + Object? locationName = null, + Object? duration = null, + Object? timeOfDay = null, + Object? familyFriendly = null, + Object? price = null, + Object? destinationRef = null, + Object? ref = null, + Object? imageUrl = null, + }) { + return _then( + _$ActivityImpl( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + locationName: null == locationName + ? _value.locationName + : locationName // ignore: cast_nullable_to_non_nullable + as String, + duration: null == duration + ? _value.duration + : duration // ignore: cast_nullable_to_non_nullable + as int, + timeOfDay: null == timeOfDay + ? _value.timeOfDay + : timeOfDay // ignore: cast_nullable_to_non_nullable + as TimeOfDay, + familyFriendly: null == familyFriendly + ? _value.familyFriendly + : familyFriendly // ignore: cast_nullable_to_non_nullable + as bool, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as int, + destinationRef: null == destinationRef + ? _value.destinationRef + : destinationRef // ignore: cast_nullable_to_non_nullable + as String, + ref: null == ref + ? _value.ref + : ref // ignore: cast_nullable_to_non_nullable + as String, + imageUrl: null == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$ActivityImpl implements _Activity { + const _$ActivityImpl({ + required this.name, + required this.description, + required this.locationName, + required this.duration, + required this.timeOfDay, + required this.familyFriendly, + required this.price, + required this.destinationRef, + required this.ref, + required this.imageUrl, + }); + + factory _$ActivityImpl.fromJson(Map json) => + _$$ActivityImplFromJson(json); + + /// e.g. 'Glacier Trekking and Ice Climbing' + @override + final String name; + + /// e.g. 'Embark on a thrilling adventure exploring the awe-inspiring glaciers of Alaska. Hike across the icy terrain, marvel at the deep blue crevasses, and even try your hand at ice climbing for an unforgettable experience.' + @override + final String description; + + /// e.g. 'Matanuska Glacier or Mendenhall Glacier' + @override + final String locationName; + + /// Duration in days. + /// e.g. 8 + @override + final int duration; + + /// e.g. 'morning' + @override + final TimeOfDay timeOfDay; + + /// e.g. false + @override + final bool familyFriendly; + + /// e.g. 4 + @override + final int price; + + /// e.g. 'alaska' + @override + final String destinationRef; + + /// e.g. 'glacier-trekking-and-ice-climbing' + @override + final String ref; + + /// e.g. 'https://storage.googleapis.com/tripedia-images/activities/alaska_glacier-trekking-and-ice-climbing.jpg' + @override + final String imageUrl; + + @override + String toString() { + return 'Activity(name: $name, description: $description, locationName: $locationName, duration: $duration, timeOfDay: $timeOfDay, familyFriendly: $familyFriendly, price: $price, destinationRef: $destinationRef, ref: $ref, imageUrl: $imageUrl)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ActivityImpl && + (identical(other.name, name) || other.name == name) && + (identical(other.description, description) || + other.description == description) && + (identical(other.locationName, locationName) || + other.locationName == locationName) && + (identical(other.duration, duration) || + other.duration == duration) && + (identical(other.timeOfDay, timeOfDay) || + other.timeOfDay == timeOfDay) && + (identical(other.familyFriendly, familyFriendly) || + other.familyFriendly == familyFriendly) && + (identical(other.price, price) || other.price == price) && + (identical(other.destinationRef, destinationRef) || + other.destinationRef == destinationRef) && + (identical(other.ref, ref) || other.ref == ref) && + (identical(other.imageUrl, imageUrl) || + other.imageUrl == imageUrl)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + name, + description, + locationName, + duration, + timeOfDay, + familyFriendly, + price, + destinationRef, + ref, + imageUrl, + ); + + /// Create a copy of Activity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ActivityImplCopyWith<_$ActivityImpl> get copyWith => + __$$ActivityImplCopyWithImpl<_$ActivityImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ActivityImplToJson(this); + } +} + +abstract class _Activity implements Activity { + const factory _Activity({ + required final String name, + required final String description, + required final String locationName, + required final int duration, + required final TimeOfDay timeOfDay, + required final bool familyFriendly, + required final int price, + required final String destinationRef, + required final String ref, + required final String imageUrl, + }) = _$ActivityImpl; + + factory _Activity.fromJson(Map json) = + _$ActivityImpl.fromJson; + + /// e.g. 'Glacier Trekking and Ice Climbing' + @override + String get name; + + /// e.g. 'Embark on a thrilling adventure exploring the awe-inspiring glaciers of Alaska. Hike across the icy terrain, marvel at the deep blue crevasses, and even try your hand at ice climbing for an unforgettable experience.' + @override + String get description; + + /// e.g. 'Matanuska Glacier or Mendenhall Glacier' + @override + String get locationName; + + /// Duration in days. + /// e.g. 8 + @override + int get duration; + + /// e.g. 'morning' + @override + TimeOfDay get timeOfDay; + + /// e.g. false + @override + bool get familyFriendly; + + /// e.g. 4 + @override + int get price; + + /// e.g. 'alaska' + @override + String get destinationRef; + + /// e.g. 'glacier-trekking-and-ice-climbing' + @override + String get ref; + + /// e.g. 'https://storage.googleapis.com/tripedia-images/activities/alaska_glacier-trekking-and-ice-climbing.jpg' + @override + String get imageUrl; + + /// Create a copy of Activity + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ActivityImplCopyWith<_$ActivityImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/compass_app/server/lib/model/activity/activity.g.dart b/compass_app/server/lib/model/activity/activity.g.dart new file mode 100644 index 0000000..8ad67d6 --- /dev/null +++ b/compass_app/server/lib/model/activity/activity.g.dart @@ -0,0 +1,43 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'activity.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ActivityImpl _$$ActivityImplFromJson(Map json) => + _$ActivityImpl( + name: json['name'] as String, + description: json['description'] as String, + locationName: json['locationName'] as String, + duration: (json['duration'] as num).toInt(), + timeOfDay: $enumDecode(_$TimeOfDayEnumMap, json['timeOfDay']), + familyFriendly: json['familyFriendly'] as bool, + price: (json['price'] as num).toInt(), + destinationRef: json['destinationRef'] as String, + ref: json['ref'] as String, + imageUrl: json['imageUrl'] as String, + ); + +Map _$$ActivityImplToJson(_$ActivityImpl instance) => + { + 'name': instance.name, + 'description': instance.description, + 'locationName': instance.locationName, + 'duration': instance.duration, + 'timeOfDay': _$TimeOfDayEnumMap[instance.timeOfDay]!, + 'familyFriendly': instance.familyFriendly, + 'price': instance.price, + 'destinationRef': instance.destinationRef, + 'ref': instance.ref, + 'imageUrl': instance.imageUrl, + }; + +const _$TimeOfDayEnumMap = { + TimeOfDay.any: 'any', + TimeOfDay.morning: 'morning', + TimeOfDay.afternoon: 'afternoon', + TimeOfDay.evening: 'evening', + TimeOfDay.night: 'night', +}; diff --git a/compass_app/server/lib/model/booking/booking.dart b/compass_app/server/lib/model/booking/booking.dart new file mode 100644 index 0000000..faf6b02 --- /dev/null +++ b/compass_app/server/lib/model/booking/booking.dart @@ -0,0 +1,35 @@ +// 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. + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'booking.freezed.dart'; +part 'booking.g.dart'; + +@freezed +class Booking with _$Booking { + const factory Booking({ + /// Booking ID. Generated when stored in server. + int? id, + + /// Start date of the trip + required DateTime startDate, + + /// End date of the trip + required DateTime endDate, + + /// Booking name + /// Should be "Destination, Continent" + required String name, + + /// Destination of the trip + required String destinationRef, + + /// List of chosen activities + required List activitiesRef, + }) = _Booking; + + factory Booking.fromJson(Map json) => + _$BookingFromJson(json); +} diff --git a/compass_app/server/lib/model/booking/booking.freezed.dart b/compass_app/server/lib/model/booking/booking.freezed.dart new file mode 100644 index 0000000..5ce4f6c --- /dev/null +++ b/compass_app/server/lib/model/booking/booking.freezed.dart @@ -0,0 +1,330 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'booking.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +Booking _$BookingFromJson(Map json) { + return _Booking.fromJson(json); +} + +/// @nodoc +mixin _$Booking { + /// Booking ID. Generated when stored in server. + int? get id => throw _privateConstructorUsedError; + + /// Start date of the trip + DateTime get startDate => throw _privateConstructorUsedError; + + /// End date of the trip + DateTime get endDate => throw _privateConstructorUsedError; + + /// Booking name + /// Should be "Destination, Continent" + String get name => throw _privateConstructorUsedError; + + /// Destination of the trip + String get destinationRef => throw _privateConstructorUsedError; + + /// List of chosen activities + List get activitiesRef => throw _privateConstructorUsedError; + + /// Serializes this Booking to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of Booking + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $BookingCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $BookingCopyWith<$Res> { + factory $BookingCopyWith(Booking value, $Res Function(Booking) then) = + _$BookingCopyWithImpl<$Res, Booking>; + @useResult + $Res call({ + int? id, + DateTime startDate, + DateTime endDate, + String name, + String destinationRef, + List activitiesRef, + }); +} + +/// @nodoc +class _$BookingCopyWithImpl<$Res, $Val extends Booking> + implements $BookingCopyWith<$Res> { + _$BookingCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Booking + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? startDate = null, + Object? endDate = null, + Object? name = null, + Object? destinationRef = null, + Object? activitiesRef = null, + }) { + return _then( + _value.copyWith( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int?, + startDate: null == startDate + ? _value.startDate + : startDate // ignore: cast_nullable_to_non_nullable + as DateTime, + endDate: null == endDate + ? _value.endDate + : endDate // ignore: cast_nullable_to_non_nullable + as DateTime, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + destinationRef: null == destinationRef + ? _value.destinationRef + : destinationRef // ignore: cast_nullable_to_non_nullable + as String, + activitiesRef: null == activitiesRef + ? _value.activitiesRef + : activitiesRef // ignore: cast_nullable_to_non_nullable + as List, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$BookingImplCopyWith<$Res> implements $BookingCopyWith<$Res> { + factory _$$BookingImplCopyWith( + _$BookingImpl value, + $Res Function(_$BookingImpl) then, + ) = __$$BookingImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + int? id, + DateTime startDate, + DateTime endDate, + String name, + String destinationRef, + List activitiesRef, + }); +} + +/// @nodoc +class __$$BookingImplCopyWithImpl<$Res> + extends _$BookingCopyWithImpl<$Res, _$BookingImpl> + implements _$$BookingImplCopyWith<$Res> { + __$$BookingImplCopyWithImpl( + _$BookingImpl _value, + $Res Function(_$BookingImpl) _then, + ) : super(_value, _then); + + /// Create a copy of Booking + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? startDate = null, + Object? endDate = null, + Object? name = null, + Object? destinationRef = null, + Object? activitiesRef = null, + }) { + return _then( + _$BookingImpl( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int?, + startDate: null == startDate + ? _value.startDate + : startDate // ignore: cast_nullable_to_non_nullable + as DateTime, + endDate: null == endDate + ? _value.endDate + : endDate // ignore: cast_nullable_to_non_nullable + as DateTime, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + destinationRef: null == destinationRef + ? _value.destinationRef + : destinationRef // ignore: cast_nullable_to_non_nullable + as String, + activitiesRef: null == activitiesRef + ? _value._activitiesRef + : activitiesRef // ignore: cast_nullable_to_non_nullable + as List, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$BookingImpl implements _Booking { + const _$BookingImpl({ + this.id, + required this.startDate, + required this.endDate, + required this.name, + required this.destinationRef, + required final List activitiesRef, + }) : _activitiesRef = activitiesRef; + + factory _$BookingImpl.fromJson(Map json) => + _$$BookingImplFromJson(json); + + /// Booking ID. Generated when stored in server. + @override + final int? id; + + /// Start date of the trip + @override + final DateTime startDate; + + /// End date of the trip + @override + final DateTime endDate; + + /// Booking name + /// Should be "Destination, Continent" + @override + final String name; + + /// Destination of the trip + @override + final String destinationRef; + + /// List of chosen activities + final List _activitiesRef; + + /// List of chosen activities + @override + List get activitiesRef { + if (_activitiesRef is EqualUnmodifiableListView) return _activitiesRef; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_activitiesRef); + } + + @override + String toString() { + return 'Booking(id: $id, startDate: $startDate, endDate: $endDate, name: $name, destinationRef: $destinationRef, activitiesRef: $activitiesRef)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BookingImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.startDate, startDate) || + other.startDate == startDate) && + (identical(other.endDate, endDate) || other.endDate == endDate) && + (identical(other.name, name) || other.name == name) && + (identical(other.destinationRef, destinationRef) || + other.destinationRef == destinationRef) && + const DeepCollectionEquality().equals( + other._activitiesRef, + _activitiesRef, + )); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + startDate, + endDate, + name, + destinationRef, + const DeepCollectionEquality().hash(_activitiesRef), + ); + + /// Create a copy of Booking + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$BookingImplCopyWith<_$BookingImpl> get copyWith => + __$$BookingImplCopyWithImpl<_$BookingImpl>(this, _$identity); + + @override + Map toJson() { + return _$$BookingImplToJson(this); + } +} + +abstract class _Booking implements Booking { + const factory _Booking({ + final int? id, + required final DateTime startDate, + required final DateTime endDate, + required final String name, + required final String destinationRef, + required final List activitiesRef, + }) = _$BookingImpl; + + factory _Booking.fromJson(Map json) = _$BookingImpl.fromJson; + + /// Booking ID. Generated when stored in server. + @override + int? get id; + + /// Start date of the trip + @override + DateTime get startDate; + + /// End date of the trip + @override + DateTime get endDate; + + /// Booking name + /// Should be "Destination, Continent" + @override + String get name; + + /// Destination of the trip + @override + String get destinationRef; + + /// List of chosen activities + @override + List get activitiesRef; + + /// Create a copy of Booking + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$BookingImplCopyWith<_$BookingImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/compass_app/server/lib/model/booking/booking.g.dart b/compass_app/server/lib/model/booking/booking.g.dart new file mode 100644 index 0000000..829d766 --- /dev/null +++ b/compass_app/server/lib/model/booking/booking.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'booking.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$BookingImpl _$$BookingImplFromJson(Map json) => + _$BookingImpl( + id: (json['id'] as num?)?.toInt(), + startDate: DateTime.parse(json['startDate'] as String), + endDate: DateTime.parse(json['endDate'] as String), + name: json['name'] as String, + destinationRef: json['destinationRef'] as String, + activitiesRef: (json['activitiesRef'] as List) + .map((e) => e as String) + .toList(), + ); + +Map _$$BookingImplToJson(_$BookingImpl instance) => + { + 'id': instance.id, + 'startDate': instance.startDate.toIso8601String(), + 'endDate': instance.endDate.toIso8601String(), + 'name': instance.name, + 'destinationRef': instance.destinationRef, + 'activitiesRef': instance.activitiesRef, + }; diff --git a/compass_app/server/lib/model/continent/continent.dart b/compass_app/server/lib/model/continent/continent.dart new file mode 100644 index 0000000..e0a309d --- /dev/null +++ b/compass_app/server/lib/model/continent/continent.dart @@ -0,0 +1,23 @@ +// 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. + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'continent.freezed.dart'; + +part 'continent.g.dart'; + +@freezed +class Continent with _$Continent { + const factory Continent({ + /// e.g. 'Europe' + required String name, + + /// e.g. 'https://rstr.in/google/tripedia/TmR12QdlVTT' + required String imageUrl, + }) = _Continent; + + factory Continent.fromJson(Map json) => + _$ContinentFromJson(json); +} diff --git a/compass_app/server/lib/model/continent/continent.freezed.dart b/compass_app/server/lib/model/continent/continent.freezed.dart new file mode 100644 index 0000000..6f13817 --- /dev/null +++ b/compass_app/server/lib/model/continent/continent.freezed.dart @@ -0,0 +1,192 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'continent.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +Continent _$ContinentFromJson(Map json) { + return _Continent.fromJson(json); +} + +/// @nodoc +mixin _$Continent { + /// e.g. 'Europe' + String get name => throw _privateConstructorUsedError; + + /// e.g. 'https://rstr.in/google/tripedia/TmR12QdlVTT' + String get imageUrl => throw _privateConstructorUsedError; + + /// Serializes this Continent to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of Continent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ContinentCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ContinentCopyWith<$Res> { + factory $ContinentCopyWith(Continent value, $Res Function(Continent) then) = + _$ContinentCopyWithImpl<$Res, Continent>; + @useResult + $Res call({String name, String imageUrl}); +} + +/// @nodoc +class _$ContinentCopyWithImpl<$Res, $Val extends Continent> + implements $ContinentCopyWith<$Res> { + _$ContinentCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Continent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? name = null, Object? imageUrl = null}) { + return _then( + _value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + imageUrl: null == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ContinentImplCopyWith<$Res> + implements $ContinentCopyWith<$Res> { + factory _$$ContinentImplCopyWith( + _$ContinentImpl value, + $Res Function(_$ContinentImpl) then, + ) = __$$ContinentImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String name, String imageUrl}); +} + +/// @nodoc +class __$$ContinentImplCopyWithImpl<$Res> + extends _$ContinentCopyWithImpl<$Res, _$ContinentImpl> + implements _$$ContinentImplCopyWith<$Res> { + __$$ContinentImplCopyWithImpl( + _$ContinentImpl _value, + $Res Function(_$ContinentImpl) _then, + ) : super(_value, _then); + + /// Create a copy of Continent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? name = null, Object? imageUrl = null}) { + return _then( + _$ContinentImpl( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + imageUrl: null == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$ContinentImpl implements _Continent { + const _$ContinentImpl({required this.name, required this.imageUrl}); + + factory _$ContinentImpl.fromJson(Map json) => + _$$ContinentImplFromJson(json); + + /// e.g. 'Europe' + @override + final String name; + + /// e.g. 'https://rstr.in/google/tripedia/TmR12QdlVTT' + @override + final String imageUrl; + + @override + String toString() { + return 'Continent(name: $name, imageUrl: $imageUrl)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ContinentImpl && + (identical(other.name, name) || other.name == name) && + (identical(other.imageUrl, imageUrl) || + other.imageUrl == imageUrl)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, name, imageUrl); + + /// Create a copy of Continent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ContinentImplCopyWith<_$ContinentImpl> get copyWith => + __$$ContinentImplCopyWithImpl<_$ContinentImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ContinentImplToJson(this); + } +} + +abstract class _Continent implements Continent { + const factory _Continent({ + required final String name, + required final String imageUrl, + }) = _$ContinentImpl; + + factory _Continent.fromJson(Map json) = + _$ContinentImpl.fromJson; + + /// e.g. 'Europe' + @override + String get name; + + /// e.g. 'https://rstr.in/google/tripedia/TmR12QdlVTT' + @override + String get imageUrl; + + /// Create a copy of Continent + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ContinentImplCopyWith<_$ContinentImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/compass_app/server/lib/model/continent/continent.g.dart b/compass_app/server/lib/model/continent/continent.g.dart new file mode 100644 index 0000000..8b34752 --- /dev/null +++ b/compass_app/server/lib/model/continent/continent.g.dart @@ -0,0 +1,16 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'continent.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ContinentImpl _$$ContinentImplFromJson(Map json) => + _$ContinentImpl( + name: json['name'] as String, + imageUrl: json['imageUrl'] as String, + ); + +Map _$$ContinentImplToJson(_$ContinentImpl instance) => + {'name': instance.name, 'imageUrl': instance.imageUrl}; diff --git a/compass_app/server/lib/model/destination/destination.dart b/compass_app/server/lib/model/destination/destination.dart new file mode 100644 index 0000000..457a125 --- /dev/null +++ b/compass_app/server/lib/model/destination/destination.dart @@ -0,0 +1,38 @@ +// 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. + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'destination.freezed.dart'; + +part 'destination.g.dart'; + +@freezed +class Destination with _$Destination { + const factory Destination({ + /// e.g. 'alaska' + required String ref, + + /// e.g. 'Alaska' + required String name, + + /// e.g. 'United States' + required String country, + + /// e.g. 'North America' + required String continent, + + /// e.g. 'Alaska is a haven for outdoor enthusiasts ...' + required String knownFor, + + /// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching'] + required List tags, + + /// e.g. 'https://storage.googleapis.com/tripedia-images/destinations/alaska.jpg' + required String imageUrl, + }) = _Destination; + + factory Destination.fromJson(Map json) => + _$DestinationFromJson(json); +} diff --git a/compass_app/server/lib/model/destination/destination.freezed.dart b/compass_app/server/lib/model/destination/destination.freezed.dart new file mode 100644 index 0000000..1babb93 --- /dev/null +++ b/compass_app/server/lib/model/destination/destination.freezed.dart @@ -0,0 +1,357 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'destination.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +Destination _$DestinationFromJson(Map json) { + return _Destination.fromJson(json); +} + +/// @nodoc +mixin _$Destination { + /// e.g. 'alaska' + String get ref => throw _privateConstructorUsedError; + + /// e.g. 'Alaska' + String get name => throw _privateConstructorUsedError; + + /// e.g. 'United States' + String get country => throw _privateConstructorUsedError; + + /// e.g. 'North America' + String get continent => throw _privateConstructorUsedError; + + /// e.g. 'Alaska is a haven for outdoor enthusiasts ...' + String get knownFor => throw _privateConstructorUsedError; + + /// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching'] + List get tags => throw _privateConstructorUsedError; + + /// e.g. 'https://storage.googleapis.com/tripedia-images/destinations/alaska.jpg' + String get imageUrl => throw _privateConstructorUsedError; + + /// Serializes this Destination to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of Destination + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $DestinationCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $DestinationCopyWith<$Res> { + factory $DestinationCopyWith( + Destination value, + $Res Function(Destination) then, + ) = _$DestinationCopyWithImpl<$Res, Destination>; + @useResult + $Res call({ + String ref, + String name, + String country, + String continent, + String knownFor, + List tags, + String imageUrl, + }); +} + +/// @nodoc +class _$DestinationCopyWithImpl<$Res, $Val extends Destination> + implements $DestinationCopyWith<$Res> { + _$DestinationCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Destination + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? ref = null, + Object? name = null, + Object? country = null, + Object? continent = null, + Object? knownFor = null, + Object? tags = null, + Object? imageUrl = null, + }) { + return _then( + _value.copyWith( + ref: null == ref + ? _value.ref + : ref // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + country: null == country + ? _value.country + : country // ignore: cast_nullable_to_non_nullable + as String, + continent: null == continent + ? _value.continent + : continent // ignore: cast_nullable_to_non_nullable + as String, + knownFor: null == knownFor + ? _value.knownFor + : knownFor // ignore: cast_nullable_to_non_nullable + as String, + tags: null == tags + ? _value.tags + : tags // ignore: cast_nullable_to_non_nullable + as List, + imageUrl: null == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$DestinationImplCopyWith<$Res> + implements $DestinationCopyWith<$Res> { + factory _$$DestinationImplCopyWith( + _$DestinationImpl value, + $Res Function(_$DestinationImpl) then, + ) = __$$DestinationImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String ref, + String name, + String country, + String continent, + String knownFor, + List tags, + String imageUrl, + }); +} + +/// @nodoc +class __$$DestinationImplCopyWithImpl<$Res> + extends _$DestinationCopyWithImpl<$Res, _$DestinationImpl> + implements _$$DestinationImplCopyWith<$Res> { + __$$DestinationImplCopyWithImpl( + _$DestinationImpl _value, + $Res Function(_$DestinationImpl) _then, + ) : super(_value, _then); + + /// Create a copy of Destination + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? ref = null, + Object? name = null, + Object? country = null, + Object? continent = null, + Object? knownFor = null, + Object? tags = null, + Object? imageUrl = null, + }) { + return _then( + _$DestinationImpl( + ref: null == ref + ? _value.ref + : ref // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + country: null == country + ? _value.country + : country // ignore: cast_nullable_to_non_nullable + as String, + continent: null == continent + ? _value.continent + : continent // ignore: cast_nullable_to_non_nullable + as String, + knownFor: null == knownFor + ? _value.knownFor + : knownFor // ignore: cast_nullable_to_non_nullable + as String, + tags: null == tags + ? _value._tags + : tags // ignore: cast_nullable_to_non_nullable + as List, + imageUrl: null == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$DestinationImpl implements _Destination { + const _$DestinationImpl({ + required this.ref, + required this.name, + required this.country, + required this.continent, + required this.knownFor, + required final List tags, + required this.imageUrl, + }) : _tags = tags; + + factory _$DestinationImpl.fromJson(Map json) => + _$$DestinationImplFromJson(json); + + /// e.g. 'alaska' + @override + final String ref; + + /// e.g. 'Alaska' + @override + final String name; + + /// e.g. 'United States' + @override + final String country; + + /// e.g. 'North America' + @override + final String continent; + + /// e.g. 'Alaska is a haven for outdoor enthusiasts ...' + @override + final String knownFor; + + /// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching'] + final List _tags; + + /// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching'] + @override + List get tags { + if (_tags is EqualUnmodifiableListView) return _tags; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_tags); + } + + /// e.g. 'https://storage.googleapis.com/tripedia-images/destinations/alaska.jpg' + @override + final String imageUrl; + + @override + String toString() { + return 'Destination(ref: $ref, name: $name, country: $country, continent: $continent, knownFor: $knownFor, tags: $tags, imageUrl: $imageUrl)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$DestinationImpl && + (identical(other.ref, ref) || other.ref == ref) && + (identical(other.name, name) || other.name == name) && + (identical(other.country, country) || other.country == country) && + (identical(other.continent, continent) || + other.continent == continent) && + (identical(other.knownFor, knownFor) || + other.knownFor == knownFor) && + const DeepCollectionEquality().equals(other._tags, _tags) && + (identical(other.imageUrl, imageUrl) || + other.imageUrl == imageUrl)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + ref, + name, + country, + continent, + knownFor, + const DeepCollectionEquality().hash(_tags), + imageUrl, + ); + + /// Create a copy of Destination + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$DestinationImplCopyWith<_$DestinationImpl> get copyWith => + __$$DestinationImplCopyWithImpl<_$DestinationImpl>(this, _$identity); + + @override + Map toJson() { + return _$$DestinationImplToJson(this); + } +} + +abstract class _Destination implements Destination { + const factory _Destination({ + required final String ref, + required final String name, + required final String country, + required final String continent, + required final String knownFor, + required final List tags, + required final String imageUrl, + }) = _$DestinationImpl; + + factory _Destination.fromJson(Map json) = + _$DestinationImpl.fromJson; + + /// e.g. 'alaska' + @override + String get ref; + + /// e.g. 'Alaska' + @override + String get name; + + /// e.g. 'United States' + @override + String get country; + + /// e.g. 'North America' + @override + String get continent; + + /// e.g. 'Alaska is a haven for outdoor enthusiasts ...' + @override + String get knownFor; + + /// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching'] + @override + List get tags; + + /// e.g. 'https://storage.googleapis.com/tripedia-images/destinations/alaska.jpg' + @override + String get imageUrl; + + /// Create a copy of Destination + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$DestinationImplCopyWith<_$DestinationImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/compass_app/server/lib/model/destination/destination.g.dart b/compass_app/server/lib/model/destination/destination.g.dart new file mode 100644 index 0000000..796e2bb --- /dev/null +++ b/compass_app/server/lib/model/destination/destination.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'destination.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$DestinationImpl _$$DestinationImplFromJson(Map json) => + _$DestinationImpl( + ref: json['ref'] as String, + name: json['name'] as String, + country: json['country'] as String, + continent: json['continent'] as String, + knownFor: json['knownFor'] as String, + tags: (json['tags'] as List).map((e) => e as String).toList(), + imageUrl: json['imageUrl'] as String, + ); + +Map _$$DestinationImplToJson(_$DestinationImpl instance) => + { + 'ref': instance.ref, + 'name': instance.name, + 'country': instance.country, + 'continent': instance.continent, + 'knownFor': instance.knownFor, + 'tags': instance.tags, + 'imageUrl': instance.imageUrl, + }; diff --git a/compass_app/server/lib/model/login_request/login_request.dart b/compass_app/server/lib/model/login_request/login_request.dart new file mode 100644 index 0000000..e30ec7c --- /dev/null +++ b/compass_app/server/lib/model/login_request/login_request.dart @@ -0,0 +1,24 @@ +// 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. + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'login_request.freezed.dart'; + +part 'login_request.g.dart'; + +/// Simple data class to hold login request data. +@freezed +class LoginRequest with _$LoginRequest { + const factory LoginRequest({ + /// Email address. + required String email, + + /// Plain text password. + required String password, + }) = _LoginRequest; + + factory LoginRequest.fromJson(Map json) => + _$LoginRequestFromJson(json); +} diff --git a/compass_app/server/lib/model/login_request/login_request.freezed.dart b/compass_app/server/lib/model/login_request/login_request.freezed.dart new file mode 100644 index 0000000..db4394e --- /dev/null +++ b/compass_app/server/lib/model/login_request/login_request.freezed.dart @@ -0,0 +1,194 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'login_request.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +LoginRequest _$LoginRequestFromJson(Map json) { + return _LoginRequest.fromJson(json); +} + +/// @nodoc +mixin _$LoginRequest { + /// Email address. + String get email => throw _privateConstructorUsedError; + + /// Plain text password. + String get password => throw _privateConstructorUsedError; + + /// Serializes this LoginRequest to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of LoginRequest + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $LoginRequestCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LoginRequestCopyWith<$Res> { + factory $LoginRequestCopyWith( + LoginRequest value, + $Res Function(LoginRequest) then, + ) = _$LoginRequestCopyWithImpl<$Res, LoginRequest>; + @useResult + $Res call({String email, String password}); +} + +/// @nodoc +class _$LoginRequestCopyWithImpl<$Res, $Val extends LoginRequest> + implements $LoginRequestCopyWith<$Res> { + _$LoginRequestCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of LoginRequest + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? email = null, Object? password = null}) { + return _then( + _value.copyWith( + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + password: null == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$LoginRequestImplCopyWith<$Res> + implements $LoginRequestCopyWith<$Res> { + factory _$$LoginRequestImplCopyWith( + _$LoginRequestImpl value, + $Res Function(_$LoginRequestImpl) then, + ) = __$$LoginRequestImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String email, String password}); +} + +/// @nodoc +class __$$LoginRequestImplCopyWithImpl<$Res> + extends _$LoginRequestCopyWithImpl<$Res, _$LoginRequestImpl> + implements _$$LoginRequestImplCopyWith<$Res> { + __$$LoginRequestImplCopyWithImpl( + _$LoginRequestImpl _value, + $Res Function(_$LoginRequestImpl) _then, + ) : super(_value, _then); + + /// Create a copy of LoginRequest + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? email = null, Object? password = null}) { + return _then( + _$LoginRequestImpl( + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + password: null == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$LoginRequestImpl implements _LoginRequest { + const _$LoginRequestImpl({required this.email, required this.password}); + + factory _$LoginRequestImpl.fromJson(Map json) => + _$$LoginRequestImplFromJson(json); + + /// Email address. + @override + final String email; + + /// Plain text password. + @override + final String password; + + @override + String toString() { + return 'LoginRequest(email: $email, password: $password)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoginRequestImpl && + (identical(other.email, email) || other.email == email) && + (identical(other.password, password) || + other.password == password)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, email, password); + + /// Create a copy of LoginRequest + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LoginRequestImplCopyWith<_$LoginRequestImpl> get copyWith => + __$$LoginRequestImplCopyWithImpl<_$LoginRequestImpl>(this, _$identity); + + @override + Map toJson() { + return _$$LoginRequestImplToJson(this); + } +} + +abstract class _LoginRequest implements LoginRequest { + const factory _LoginRequest({ + required final String email, + required final String password, + }) = _$LoginRequestImpl; + + factory _LoginRequest.fromJson(Map json) = + _$LoginRequestImpl.fromJson; + + /// Email address. + @override + String get email; + + /// Plain text password. + @override + String get password; + + /// Create a copy of LoginRequest + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LoginRequestImplCopyWith<_$LoginRequestImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/compass_app/server/lib/model/login_request/login_request.g.dart b/compass_app/server/lib/model/login_request/login_request.g.dart new file mode 100644 index 0000000..0b7e89c --- /dev/null +++ b/compass_app/server/lib/model/login_request/login_request.g.dart @@ -0,0 +1,16 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'login_request.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$LoginRequestImpl _$$LoginRequestImplFromJson(Map json) => + _$LoginRequestImpl( + email: json['email'] as String, + password: json['password'] as String, + ); + +Map _$$LoginRequestImplToJson(_$LoginRequestImpl instance) => + {'email': instance.email, 'password': instance.password}; diff --git a/compass_app/server/lib/model/login_response/login_response.dart b/compass_app/server/lib/model/login_response/login_response.dart new file mode 100644 index 0000000..bc17d6a --- /dev/null +++ b/compass_app/server/lib/model/login_response/login_response.dart @@ -0,0 +1,24 @@ +// 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. + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'login_response.freezed.dart'; + +part 'login_response.g.dart'; + +/// LoginResponse model. +@freezed +class LoginResponse with _$LoginResponse { + const factory LoginResponse({ + /// The token to be used for authentication. + required String token, + + /// The user id. + required String userId, + }) = _LoginResponse; + + factory LoginResponse.fromJson(Map json) => + _$LoginResponseFromJson(json); +} diff --git a/compass_app/server/lib/model/login_response/login_response.freezed.dart b/compass_app/server/lib/model/login_response/login_response.freezed.dart new file mode 100644 index 0000000..dfaf762 --- /dev/null +++ b/compass_app/server/lib/model/login_response/login_response.freezed.dart @@ -0,0 +1,193 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'login_response.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +LoginResponse _$LoginResponseFromJson(Map json) { + return _LoginResponse.fromJson(json); +} + +/// @nodoc +mixin _$LoginResponse { + /// The token to be used for authentication. + String get token => throw _privateConstructorUsedError; + + /// The user id. + String get userId => throw _privateConstructorUsedError; + + /// Serializes this LoginResponse to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of LoginResponse + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $LoginResponseCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LoginResponseCopyWith<$Res> { + factory $LoginResponseCopyWith( + LoginResponse value, + $Res Function(LoginResponse) then, + ) = _$LoginResponseCopyWithImpl<$Res, LoginResponse>; + @useResult + $Res call({String token, String userId}); +} + +/// @nodoc +class _$LoginResponseCopyWithImpl<$Res, $Val extends LoginResponse> + implements $LoginResponseCopyWith<$Res> { + _$LoginResponseCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of LoginResponse + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? token = null, Object? userId = null}) { + return _then( + _value.copyWith( + token: null == token + ? _value.token + : token // ignore: cast_nullable_to_non_nullable + as String, + userId: null == userId + ? _value.userId + : userId // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$LoginResponseImplCopyWith<$Res> + implements $LoginResponseCopyWith<$Res> { + factory _$$LoginResponseImplCopyWith( + _$LoginResponseImpl value, + $Res Function(_$LoginResponseImpl) then, + ) = __$$LoginResponseImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String token, String userId}); +} + +/// @nodoc +class __$$LoginResponseImplCopyWithImpl<$Res> + extends _$LoginResponseCopyWithImpl<$Res, _$LoginResponseImpl> + implements _$$LoginResponseImplCopyWith<$Res> { + __$$LoginResponseImplCopyWithImpl( + _$LoginResponseImpl _value, + $Res Function(_$LoginResponseImpl) _then, + ) : super(_value, _then); + + /// Create a copy of LoginResponse + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? token = null, Object? userId = null}) { + return _then( + _$LoginResponseImpl( + token: null == token + ? _value.token + : token // ignore: cast_nullable_to_non_nullable + as String, + userId: null == userId + ? _value.userId + : userId // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$LoginResponseImpl implements _LoginResponse { + const _$LoginResponseImpl({required this.token, required this.userId}); + + factory _$LoginResponseImpl.fromJson(Map json) => + _$$LoginResponseImplFromJson(json); + + /// The token to be used for authentication. + @override + final String token; + + /// The user id. + @override + final String userId; + + @override + String toString() { + return 'LoginResponse(token: $token, userId: $userId)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoginResponseImpl && + (identical(other.token, token) || other.token == token) && + (identical(other.userId, userId) || other.userId == userId)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, token, userId); + + /// Create a copy of LoginResponse + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LoginResponseImplCopyWith<_$LoginResponseImpl> get copyWith => + __$$LoginResponseImplCopyWithImpl<_$LoginResponseImpl>(this, _$identity); + + @override + Map toJson() { + return _$$LoginResponseImplToJson(this); + } +} + +abstract class _LoginResponse implements LoginResponse { + const factory _LoginResponse({ + required final String token, + required final String userId, + }) = _$LoginResponseImpl; + + factory _LoginResponse.fromJson(Map json) = + _$LoginResponseImpl.fromJson; + + /// The token to be used for authentication. + @override + String get token; + + /// The user id. + @override + String get userId; + + /// Create a copy of LoginResponse + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LoginResponseImplCopyWith<_$LoginResponseImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/compass_app/server/lib/model/login_response/login_response.g.dart b/compass_app/server/lib/model/login_response/login_response.g.dart new file mode 100644 index 0000000..c044a4d --- /dev/null +++ b/compass_app/server/lib/model/login_response/login_response.g.dart @@ -0,0 +1,16 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'login_response.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$LoginResponseImpl _$$LoginResponseImplFromJson(Map json) => + _$LoginResponseImpl( + token: json['token'] as String, + userId: json['userId'] as String, + ); + +Map _$$LoginResponseImplToJson(_$LoginResponseImpl instance) => + {'token': instance.token, 'userId': instance.userId}; diff --git a/compass_app/server/lib/model/user/user.dart b/compass_app/server/lib/model/user/user.dart new file mode 100644 index 0000000..26ad338 --- /dev/null +++ b/compass_app/server/lib/model/user/user.dart @@ -0,0 +1,27 @@ +// 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. + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'user.freezed.dart'; +part 'user.g.dart'; + +@freezed +abstract class User with _$User { + const factory User({ + /// The user's ID. + required String id, + + /// The user's name. + required String name, + + /// The user's email. + required String email, + + /// The user's picture URL. + required String picture, + }) = _User; + + factory User.fromJson(Map json) => _$UserFromJson(json); +} diff --git a/compass_app/server/lib/model/user/user.freezed.dart b/compass_app/server/lib/model/user/user.freezed.dart new file mode 100644 index 0000000..74e26c7 --- /dev/null +++ b/compass_app/server/lib/model/user/user.freezed.dart @@ -0,0 +1,243 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'user.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +User _$UserFromJson(Map json) { + return _User.fromJson(json); +} + +/// @nodoc +mixin _$User { + /// The user's ID. + String get id => throw _privateConstructorUsedError; + + /// The user's name. + String get name => throw _privateConstructorUsedError; + + /// The user's email. + String get email => throw _privateConstructorUsedError; + + /// The user's picture URL. + String get picture => throw _privateConstructorUsedError; + + /// Serializes this User to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of User + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $UserCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $UserCopyWith<$Res> { + factory $UserCopyWith(User value, $Res Function(User) then) = + _$UserCopyWithImpl<$Res, User>; + @useResult + $Res call({String id, String name, String email, String picture}); +} + +/// @nodoc +class _$UserCopyWithImpl<$Res, $Val extends User> + implements $UserCopyWith<$Res> { + _$UserCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of User + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? email = null, + Object? picture = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + picture: null == picture + ? _value.picture + : picture // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$UserImplCopyWith<$Res> implements $UserCopyWith<$Res> { + factory _$$UserImplCopyWith( + _$UserImpl value, + $Res Function(_$UserImpl) then, + ) = __$$UserImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String id, String name, String email, String picture}); +} + +/// @nodoc +class __$$UserImplCopyWithImpl<$Res> + extends _$UserCopyWithImpl<$Res, _$UserImpl> + implements _$$UserImplCopyWith<$Res> { + __$$UserImplCopyWithImpl(_$UserImpl _value, $Res Function(_$UserImpl) _then) + : super(_value, _then); + + /// Create a copy of User + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? email = null, + Object? picture = null, + }) { + return _then( + _$UserImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + picture: null == picture + ? _value.picture + : picture // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$UserImpl implements _User { + const _$UserImpl({ + required this.id, + required this.name, + required this.email, + required this.picture, + }); + + factory _$UserImpl.fromJson(Map json) => + _$$UserImplFromJson(json); + + /// The user's ID. + @override + final String id; + + /// The user's name. + @override + final String name; + + /// The user's email. + @override + final String email; + + /// The user's picture URL. + @override + final String picture; + + @override + String toString() { + return 'User(id: $id, name: $name, email: $email, picture: $picture)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$UserImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.email, email) || other.email == email) && + (identical(other.picture, picture) || other.picture == picture)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, id, name, email, picture); + + /// Create a copy of User + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$UserImplCopyWith<_$UserImpl> get copyWith => + __$$UserImplCopyWithImpl<_$UserImpl>(this, _$identity); + + @override + Map toJson() { + return _$$UserImplToJson(this); + } +} + +abstract class _User implements User { + const factory _User({ + required final String id, + required final String name, + required final String email, + required final String picture, + }) = _$UserImpl; + + factory _User.fromJson(Map json) = _$UserImpl.fromJson; + + /// The user's ID. + @override + String get id; + + /// The user's name. + @override + String get name; + + /// The user's email. + @override + String get email; + + /// The user's picture URL. + @override + String get picture; + + /// Create a copy of User + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$UserImplCopyWith<_$UserImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/compass_app/server/lib/model/user/user.g.dart b/compass_app/server/lib/model/user/user.g.dart new file mode 100644 index 0000000..34a78e6 --- /dev/null +++ b/compass_app/server/lib/model/user/user.g.dart @@ -0,0 +1,22 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$UserImpl _$$UserImplFromJson(Map json) => _$UserImpl( + id: json['id'] as String, + name: json['name'] as String, + email: json['email'] as String, + picture: json['picture'] as String, +); + +Map _$$UserImplToJson(_$UserImpl instance) => + { + 'id': instance.id, + 'name': instance.name, + 'email': instance.email, + 'picture': instance.picture, + }; diff --git a/compass_app/server/lib/routes/booking.dart b/compass_app/server/lib/routes/booking.dart new file mode 100644 index 0000000..adffc89 --- /dev/null +++ b/compass_app/server/lib/routes/booking.dart @@ -0,0 +1,118 @@ +// 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. + +import 'dart:convert'; + +import 'package:shelf/shelf.dart'; +import 'package:shelf_router/shelf_router.dart'; + +import '../config/assets.dart'; +import '../model/booking/booking.dart'; + +/// Allows the user to save and restore bookings. +/// +/// Bookings are stored in memory for demo purposes, +/// so they are lost when the server stops. +/// +/// For demo purposes, this API includes a default pre-generated booking. +/// +/// The [Booking.id] is also the index in the bookings list. +class BookingApi { + BookingApi() { + // Create a default booking + final destination = Assets.destinations.first; + final activitiesRef = Assets.activities + .where((activity) => activity.destinationRef == destination.ref) + .map((activity) => activity.ref) + .toList(); + _bookings.add( + Booking( + id: _sequentialId++, + name: '${destination.name}, ${destination.continent}', + startDate: DateTime(2024, 7, 20), + endDate: DateTime(2024, 8, 15), + destinationRef: destination.ref, + activitiesRef: activitiesRef, + ), + ); + } + + // Bookings are kept in memory for demo purposes. + // To keep things simple, the id is also the index in the list. + final List _bookings = List.empty(growable: true); + + // Used to generate IDs for bookings + int _sequentialId = 0; + + Router get router { + final router = Router(); + + // Get User bookings + router.get('/', (Request request) { + return Response.ok( + json.encode(_bookings), + headers: {'Content-Type': 'application/json'}, + ); + }); + + // Get a booking by id + router.get('/', (Request request, String id) { + final bookingId = int.parse(id); + final booking = _bookings + .where((booking) => booking.id == bookingId) + .firstOrNull; + + if (booking == null) { + return Response.notFound('Invalid id'); + } + + return Response.ok( + json.encode(booking), + headers: {'Content-Type': 'application/json'}, + ); + }); + + // Save a new booking + router.post('/', (Request request) async { + final body = await request.readAsString(); + final booking = Booking.fromJson(json.decode(body)); + + if (booking.id != null) { + // POST endpoint only allows newly created bookings + return Response.badRequest( + body: 'Booking already has id, use PUT instead.', + ); + } + + // Add ID to new booking + final bookingWithId = booking.copyWith(id: _sequentialId++); + + // Store booking + _bookings.add(bookingWithId); + + // Respond with newly created booking + return Response( + 201, // created + body: json.encode(bookingWithId), + headers: {'Content-Type': 'application/json'}, + ); + }); + + // Delete booking + router.delete('/', (Request request, String id) async { + final bookingId = int.parse(id); + final booking = _bookings + .where((booking) => booking.id == bookingId) + .firstOrNull; + if (booking == null) { + return Response.notFound('Invalid id'); + } + _bookings.remove(booking); + // 240: no content + return Response(204); + }); + + return router; + } +} diff --git a/compass_app/server/lib/routes/continent.dart b/compass_app/server/lib/routes/continent.dart new file mode 100644 index 0000000..2a5ebd7 --- /dev/null +++ b/compass_app/server/lib/routes/continent.dart @@ -0,0 +1,44 @@ +// 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. + +import 'dart:convert'; + +import 'package:shelf/shelf.dart'; + +import '../model/continent/continent.dart'; + +final _continents = [ + Continent( + name: 'Europe', + imageUrl: 'https://rstr.in/google/tripedia/TmR12QdlVTT', + ), + Continent( + name: 'Asia', + imageUrl: 'https://rstr.in/google/tripedia/VJ8BXlQg8O1', + ), + Continent( + name: 'South America', + imageUrl: 'https://rstr.in/google/tripedia/flm_-o1aI8e', + ), + Continent( + name: 'Africa', + imageUrl: 'https://rstr.in/google/tripedia/-nzi8yFOBpF', + ), + Continent( + name: 'North America', + imageUrl: 'https://rstr.in/google/tripedia/jlbgFDrSUVE', + ), + Continent( + name: 'Oceania', + imageUrl: 'https://rstr.in/google/tripedia/vxyrDE-fZVL', + ), + Continent( + name: 'Australia', + imageUrl: 'https://rstr.in/google/tripedia/z6vy6HeRyvZ', + ), +]; + +Response continentHandler(Request req) { + return Response.ok(jsonEncode(_continents)); +} diff --git a/compass_app/server/lib/routes/destination.dart b/compass_app/server/lib/routes/destination.dart new file mode 100644 index 0000000..7d63ca5 --- /dev/null +++ b/compass_app/server/lib/routes/destination.dart @@ -0,0 +1,35 @@ +// 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. + +import 'dart:convert'; + +import 'package:shelf/shelf.dart'; +import 'package:shelf_router/shelf_router.dart'; + +import '../config/assets.dart'; + +class DestinationApi { + Router get router { + final router = Router(); + + router.get('/', (Request request) { + return Response.ok( + json.encode(Assets.destinations), + headers: {'Content-Type': 'application/json'}, + ); + }); + + router.get('//activity', (Request request, String id) { + final list = Assets.activities + .where((activity) => activity.destinationRef == id) + .toList(); + return Response.ok( + json.encode(list), + headers: {'Content-Type': 'application/json'}, + ); + }); + + return router; + } +} diff --git a/compass_app/server/lib/routes/login.dart b/compass_app/server/lib/routes/login.dart new file mode 100644 index 0000000..dd4c544 --- /dev/null +++ b/compass_app/server/lib/routes/login.dart @@ -0,0 +1,47 @@ +// 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. + +import 'dart:convert'; + +import 'package:shelf/shelf.dart'; +import 'package:shelf_router/shelf_router.dart'; + +import '../config/constants.dart'; +import '../model/login_request/login_request.dart'; +import '../model/login_response/login_response.dart'; + +/// Implements a simple login API. +/// +/// This is provided as an example for Flutter architectural purposes only +/// and shouldn't be used as example on how to implement authentication +/// in production. +/// +/// This API only accepts a fixed email and password for demonstration purposes, +/// then returns a hardcoded token and a user id. +/// +/// This token does not expire and is not secure. +class LoginApi { + Router get router { + final router = Router(); + + router.post('/', (Request request) async { + final body = await request.readAsString(); + final loginRequest = LoginRequest.fromJson(json.decode(body)); + + if (loginRequest.email == Constants.email && + loginRequest.password == Constants.password) { + return Response.ok( + json.encode( + LoginResponse(token: Constants.token, userId: Constants.userId), + ), + headers: {'Content-Type': 'application/json'}, + ); + } + + return Response.unauthorized('Invalid credentials'); + }); + + return router; + } +} diff --git a/compass_app/server/lib/routes/user.dart b/compass_app/server/lib/routes/user.dart new file mode 100644 index 0000000..045442e --- /dev/null +++ b/compass_app/server/lib/routes/user.dart @@ -0,0 +1,28 @@ +// 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. + +import 'dart:convert'; + +import 'package:shelf/shelf.dart'; +import 'package:shelf_router/shelf_router.dart'; + +import '../config/constants.dart'; + +/// Implements a simple user API. +/// +/// This API only returns a hardcoded user for demonstration purposes. +class UserApi { + Router get router { + final router = Router(); + + router.get('/', (Request request) async { + return Response.ok( + json.encode(Constants.user), + headers: {'Content-Type': 'application/json'}, + ); + }); + + return router; + } +} diff --git a/compass_app/server/pubspec.yaml b/compass_app/server/pubspec.yaml new file mode 100644 index 0000000..f26d031 --- /dev/null +++ b/compass_app/server/pubspec.yaml @@ -0,0 +1,24 @@ +name: compass_server +description: A server app using the shelf package and Docker. +publish_to: 'none' +version: 1.0.0 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + args: ^2.4.0 + shelf: ^1.4.0 + shelf_router: ^1.1.0 + freezed_annotation: ^3.0.0 + json_annotation: ^4.9.0 + +dev_dependencies: + http: ^1.1.0 + analysis_defaults: + path: ../../analysis_defaults + test: ^1.24.0 + build_runner: ^2.4.11 + freezed: ^3.0.0 + json_serializable: ^6.8.0 diff --git a/compass_app/server/test/server_test.dart b/compass_app/server/test/server_test.dart new file mode 100644 index 0000000..3416d24 --- /dev/null +++ b/compass_app/server/test/server_test.dart @@ -0,0 +1,203 @@ +// 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. + +import 'dart:convert'; +import 'dart:io'; + +import 'package:compass_server/config/constants.dart'; +import 'package:compass_server/model/activity/activity.dart'; +import 'package:compass_server/model/booking/booking.dart'; +import 'package:compass_server/model/continent/continent.dart'; +import 'package:compass_server/model/destination/destination.dart'; +import 'package:compass_server/model/login_request/login_request.dart'; +import 'package:compass_server/model/login_response/login_response.dart'; +import 'package:compass_server/model/user/user.dart'; +import 'package:http/http.dart'; +import 'package:test/test.dart'; + +void main() { + final port = '8080'; + final host = 'http://127.0.0.1:$port'; + late Process p; + + final headers = {'Authorization': 'Bearer ${Constants.token}'}; + + setUp(() async { + p = await Process.start( + 'dart', + ['run', 'bin/compass_server.dart'], + environment: {'PORT': port}, + ); + // Wait for server to start and print to stdout. + await p.stdout.first; + }); + + tearDown(() => p.kill()); + + test('Get Continent end-point', () async { + // Query /continent end-point + final response = await get(Uri.parse('$host/continent'), headers: headers); + + expect(response.statusCode, 200); + // Parse json response list + final list = jsonDecode(response.body) as List; + // Parse items + final continents = list.map((element) => Continent.fromJson(element)); + expect(continents.length, 7); + expect(continents.first.name, 'Europe'); + }); + + test('Get Destination end-point', () async { + // Query /destination end-point + final response = await get( + Uri.parse('$host/destination'), + headers: headers, + ); + expect(response.statusCode, 200); + // Parse json response list + final list = jsonDecode(response.body) as List; + // Parse items + final destination = list.map((element) => Destination.fromJson(element)); + expect(destination.length, 137); + expect(destination.first.name, 'Alaska'); + }); + + test('Get Activities end-point', () async { + // Query /destination/alaska/activity end-point + final response = await get( + Uri.parse('$host/destination/alaska/activity'), + headers: headers, + ); + expect(response.statusCode, 200); + // Parse json response list + final list = jsonDecode(response.body) as List; + // Parse items + final activity = list.map((element) => Activity.fromJson(element)); + expect(activity.length, 20); + expect(activity.first.name, 'Glacier Trekking and Ice Climbing'); + }); + + test('Get bookings end-point', () async { + final response = await get(Uri.parse('$host/booking'), headers: headers); + expect(response.statusCode, 200); + // Parse json response list + final list = jsonDecode(response.body) as List; + // Parse items + final bookings = list.map((element) => Booking.fromJson(element)); + expect(bookings.length, 1); + expect(bookings.first.id, 0); + }); + + test('Get booking with id 0', () async { + final response = await get(Uri.parse('$host/booking/0'), headers: headers); + + expect(response.statusCode, 200); + final booking = Booking.fromJson(jsonDecode(response.body)); + + // Got booking with id 0 + expect(booking.id, 0); + }); + + test('Store a booking', () async { + final response = await post( + Uri.parse('$host/booking'), + headers: headers, + body: jsonEncode( + Booking( + name: 'DESTINATION, CONTINENT', + startDate: DateTime(2024, 1, 1), + endDate: DateTime(2024, 2, 2), + destinationRef: 'REF', + activitiesRef: ['ACT1', 'ACT2'], + ), + ), + ); + + expect(response.statusCode, 201); + final booking = Booking.fromJson(jsonDecode(response.body)); + + // New ID should be 1 + expect(booking.id, 1); + }); + + test('Delete a booking', () async { + // First create a booking + final response = await post( + Uri.parse('$host/booking'), + headers: headers, + body: jsonEncode( + Booking( + name: 'DESTINATION, CONTINENT', + startDate: DateTime(2024, 1, 1), + endDate: DateTime(2024, 2, 2), + destinationRef: 'REF', + activitiesRef: ['ACT1', 'ACT2'], + ), + ), + ); + expect(response.statusCode, 201); + final booking = Booking.fromJson(jsonDecode(response.body)); + + // Then delete it + final deleteResponse = await delete( + Uri.parse('$host/booking/${booking.id}'), + headers: headers, + ); + + expect(deleteResponse.statusCode, 204); + }); + + test('Delete a booking is not found', () async { + final response = await delete( + Uri.parse('$host/booking/42'), + headers: headers, + ); + + expect(response.statusCode, 404); + }); + + test('Get user', () async { + final response = await get(Uri.parse('$host/user'), headers: headers); + + expect(response.statusCode, 200); + final user = User.fromJson(jsonDecode(response.body)); + + // Should get the hardcoded user + expect(user, Constants.user); + }); + + test('404', () async { + final response = await get(Uri.parse('$host/foobar'), headers: headers); + expect(response.statusCode, 404); + }); + + test('Login with valid credentials', () async { + final response = await post( + Uri.parse('$host/login'), + body: jsonEncode( + LoginRequest(email: Constants.email, password: Constants.password), + ), + ); + expect(response.statusCode, 200); + final loginResponse = LoginResponse.fromJson(jsonDecode(response.body)); + expect(loginResponse.token, Constants.token); + expect(loginResponse.userId, Constants.userId); + }); + + test('Login with wrong credentials', () async { + final response = await post( + Uri.parse('$host/login'), + body: jsonEncode(LoginRequest(email: 'INVALID', password: 'INVALID')), + ); + expect(response.statusCode, 401); + }); + + test('Unauthorized request', () async { + // Query /continent end-point + // No auth headers + final response = await get(Uri.parse('$host/continent')); + + expect(response.statusCode, 401); + }); +} diff --git a/cupertino_gallery/.gitignore b/cupertino_gallery/.gitignore new file mode 100644 index 0000000..3820a95 --- /dev/null +++ b/cupertino_gallery/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/cupertino_gallery/.metadata b/cupertino_gallery/.metadata new file mode 100644 index 0000000..9064db0 --- /dev/null +++ b/cupertino_gallery/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "c0f2a1dd60414de7bef59318dc2554a6bb75d4ad" + channel: "beta" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: c0f2a1dd60414de7bef59318dc2554a6bb75d4ad + base_revision: c0f2a1dd60414de7bef59318dc2554a6bb75d4ad + - platform: android + create_revision: c0f2a1dd60414de7bef59318dc2554a6bb75d4ad + base_revision: c0f2a1dd60414de7bef59318dc2554a6bb75d4ad + - platform: ios + create_revision: c0f2a1dd60414de7bef59318dc2554a6bb75d4ad + base_revision: c0f2a1dd60414de7bef59318dc2554a6bb75d4ad + - platform: linux + create_revision: c0f2a1dd60414de7bef59318dc2554a6bb75d4ad + base_revision: c0f2a1dd60414de7bef59318dc2554a6bb75d4ad + - platform: macos + create_revision: c0f2a1dd60414de7bef59318dc2554a6bb75d4ad + base_revision: c0f2a1dd60414de7bef59318dc2554a6bb75d4ad + - platform: web + create_revision: c0f2a1dd60414de7bef59318dc2554a6bb75d4ad + base_revision: c0f2a1dd60414de7bef59318dc2554a6bb75d4ad + - platform: windows + create_revision: c0f2a1dd60414de7bef59318dc2554a6bb75d4ad + base_revision: c0f2a1dd60414de7bef59318dc2554a6bb75d4ad + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/cupertino_gallery/README.md b/cupertino_gallery/README.md new file mode 100644 index 0000000..064ec5b --- /dev/null +++ b/cupertino_gallery/README.md @@ -0,0 +1,16 @@ +# cupertino_gallery + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/cupertino_gallery/analysis_options.yaml b/cupertino_gallery/analysis_options.yaml new file mode 100644 index 0000000..5e2133e --- /dev/null +++ b/cupertino_gallery/analysis_options.yaml @@ -0,0 +1 @@ +include: ../analysis_options.yaml diff --git a/cupertino_gallery/android/.gitignore b/cupertino_gallery/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/cupertino_gallery/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/cupertino_gallery/android/app/build.gradle.kts b/cupertino_gallery/android/app/build.gradle.kts new file mode 100644 index 0000000..169bea0 --- /dev/null +++ b/cupertino_gallery/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.cupertino_gallery" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.cupertino_gallery" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/cupertino_gallery/android/app/src/debug/AndroidManifest.xml b/cupertino_gallery/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/cupertino_gallery/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/cupertino_gallery/android/app/src/main/AndroidManifest.xml b/cupertino_gallery/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..287f17c --- /dev/null +++ b/cupertino_gallery/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/cupertino_gallery/android/app/src/main/kotlin/com/example/cupertino_gallery/MainActivity.kt b/cupertino_gallery/android/app/src/main/kotlin/com/example/cupertino_gallery/MainActivity.kt new file mode 100644 index 0000000..9ef50d2 --- /dev/null +++ b/cupertino_gallery/android/app/src/main/kotlin/com/example/cupertino_gallery/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.cupertino_gallery + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/cupertino_gallery/android/app/src/main/res/drawable-v21/launch_background.xml b/cupertino_gallery/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/cupertino_gallery/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/cupertino_gallery/android/app/src/main/res/drawable/launch_background.xml b/cupertino_gallery/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/cupertino_gallery/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/cupertino_gallery/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/cupertino_gallery/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/cupertino_gallery/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/cupertino_gallery/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/cupertino_gallery/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/cupertino_gallery/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/cupertino_gallery/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/cupertino_gallery/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/cupertino_gallery/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/cupertino_gallery/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/cupertino_gallery/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/cupertino_gallery/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/cupertino_gallery/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/cupertino_gallery/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/cupertino_gallery/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/cupertino_gallery/android/app/src/main/res/values-night/styles.xml b/cupertino_gallery/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/cupertino_gallery/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/cupertino_gallery/android/app/src/main/res/values/styles.xml b/cupertino_gallery/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/cupertino_gallery/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/cupertino_gallery/android/app/src/profile/AndroidManifest.xml b/cupertino_gallery/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/cupertino_gallery/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/cupertino_gallery/android/build.gradle.kts b/cupertino_gallery/android/build.gradle.kts new file mode 100644 index 0000000..dbee657 --- /dev/null +++ b/cupertino_gallery/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/cupertino_gallery/android/gradle.properties b/cupertino_gallery/android/gradle.properties new file mode 100644 index 0000000..f018a61 --- /dev/null +++ b/cupertino_gallery/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/cupertino_gallery/android/gradle/wrapper/gradle-wrapper.properties b/cupertino_gallery/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ac3b479 --- /dev/null +++ b/cupertino_gallery/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/cupertino_gallery/android/settings.gradle.kts b/cupertino_gallery/android/settings.gradle.kts new file mode 100644 index 0000000..fb605bc --- /dev/null +++ b/cupertino_gallery/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.9.1" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false +} + +include(":app") diff --git a/cupertino_gallery/ios/.gitignore b/cupertino_gallery/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/cupertino_gallery/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/cupertino_gallery/ios/Flutter/AppFrameworkInfo.plist b/cupertino_gallery/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..1dc6cf7 --- /dev/null +++ b/cupertino_gallery/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 13.0 + + diff --git a/cupertino_gallery/ios/Flutter/Debug.xcconfig b/cupertino_gallery/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/cupertino_gallery/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/cupertino_gallery/ios/Flutter/Release.xcconfig b/cupertino_gallery/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/cupertino_gallery/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/cupertino_gallery/ios/Runner.xcodeproj/project.pbxproj b/cupertino_gallery/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..45736c7 --- /dev/null +++ b/cupertino_gallery/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,619 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 2UUT9AMTS2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.cupertinoGallery; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.cupertinoGallery.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.cupertinoGallery.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.cupertinoGallery.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 2UUT9AMTS2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.cupertinoGallery; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 2UUT9AMTS2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.cupertinoGallery; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/cupertino_gallery/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/cupertino_gallery/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/cupertino_gallery/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/cupertino_gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/cupertino_gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/cupertino_gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/cupertino_gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/cupertino_gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/cupertino_gallery/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/cupertino_gallery/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/cupertino_gallery/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..e3773d4 --- /dev/null +++ b/cupertino_gallery/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cupertino_gallery/ios/Runner.xcworkspace/contents.xcworkspacedata b/cupertino_gallery/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/cupertino_gallery/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/cupertino_gallery/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/cupertino_gallery/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/cupertino_gallery/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/cupertino_gallery/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/cupertino_gallery/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/cupertino_gallery/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/cupertino_gallery/ios/Runner/AppDelegate.swift b/cupertino_gallery/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..6266644 --- /dev/null +++ b/cupertino_gallery/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/cupertino_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/cupertino_gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/cupertino_gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/cupertino_gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/cupertino_gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/cupertino_gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/cupertino_gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/cupertino_gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/cupertino_gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/cupertino_gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/cupertino_gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/cupertino_gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/cupertino_gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/cupertino_gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/cupertino_gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/cupertino_gallery/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/cupertino_gallery/ios/Runner/Base.lproj/LaunchScreen.storyboard b/cupertino_gallery/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/cupertino_gallery/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cupertino_gallery/ios/Runner/Base.lproj/Main.storyboard b/cupertino_gallery/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/cupertino_gallery/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cupertino_gallery/ios/Runner/Info.plist b/cupertino_gallery/ios/Runner/Info.plist new file mode 100644 index 0000000..a8ead92 --- /dev/null +++ b/cupertino_gallery/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Cupertino Gallery + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + cupertino_gallery + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/cupertino_gallery/ios/Runner/Runner-Bridging-Header.h b/cupertino_gallery/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/cupertino_gallery/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/cupertino_gallery/ios/RunnerTests/RunnerTests.swift b/cupertino_gallery/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/cupertino_gallery/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/cupertino_gallery/lib/gallery_home.dart b/cupertino_gallery/lib/gallery_home.dart new file mode 100644 index 0000000..cd206ed --- /dev/null +++ b/cupertino_gallery/lib/gallery_home.dart @@ -0,0 +1,52 @@ +import 'package:flutter/cupertino.dart'; +import 'settings_page.dart'; +import 'widgets_page.dart'; + +class GalleryHome extends StatelessWidget { + const GalleryHome({ + super.key, + required this.onThemeChange, + required this.isDarkMode, + required this.onTextSizeChange, + required this.textSize, + }); + + final ValueChanged onThemeChange; + final bool isDarkMode; + final ValueChanged onTextSizeChange; + final double textSize; + + @override + Widget build(BuildContext context) { + return CupertinoTabScaffold( + tabBar: CupertinoTabBar( + items: const [ + BottomNavigationBarItem( + icon: Icon(CupertinoIcons.list_bullet), + label: 'Widgets', + ), + BottomNavigationBarItem( + icon: Icon(CupertinoIcons.settings), + label: 'Settings', + ), + ], + ), + tabBuilder: (context, index) { + return CupertinoTabView( + builder: (context) { + return switch (index) { + 0 => const WidgetsPage(), + 1 => SettingsPage( + onThemeChange: onThemeChange, + isDarkMode: isDarkMode, + onTextSizeChange: onTextSizeChange, + textSize: textSize, + ), + _ => const Center(child: Text('Widgets')), + }; + }, + ); + }, + ); + } +} diff --git a/cupertino_gallery/lib/main.dart b/cupertino_gallery/lib/main.dart new file mode 100644 index 0000000..eed53bd --- /dev/null +++ b/cupertino_gallery/lib/main.dart @@ -0,0 +1,79 @@ +// Copyright 2022 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. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import 'gallery_home.dart'; + +void main() { + runApp(const CupertinoGalleryApp()); +} + +class CupertinoGalleryApp extends StatefulWidget { + const CupertinoGalleryApp({super.key}); + + @override + State createState() => _CupertinoGalleryAppState(); +} + +class _CupertinoGalleryAppState extends State { + ThemeMode _themeMode = ThemeMode.system; + double _textSize = 1.0; + + void _handleThemeChange(bool isDarkMode) { + setState(() { + _themeMode = isDarkMode ? ThemeMode.dark : ThemeMode.light; + }); + } + + void _handleTextSizeChange(double newTextSize) { + setState(() { + _textSize = newTextSize; + }); + } + + @override + Widget build(BuildContext context) { + final baseTheme = CupertinoThemeData( + brightness: _themeMode == ThemeMode.dark + ? Brightness.dark + : Brightness.light, + ); + final textTheme = baseTheme.textTheme.copyWith( + textStyle: baseTheme.textTheme.textStyle.copyWith( + fontSize: 14 * _textSize, + ), + actionTextStyle: baseTheme.textTheme.actionTextStyle.copyWith( + fontSize: 14 * _textSize, + ), + tabLabelTextStyle: baseTheme.textTheme.tabLabelTextStyle.copyWith( + fontSize: 10 * _textSize, + ), + navTitleTextStyle: baseTheme.textTheme.navTitleTextStyle.copyWith( + fontSize: 17 * _textSize, + ), + navLargeTitleTextStyle: baseTheme.textTheme.navLargeTitleTextStyle + .copyWith(fontSize: 34 * _textSize), + navActionTextStyle: baseTheme.textTheme.navActionTextStyle.copyWith( + fontSize: 17 * _textSize, + ), + pickerTextStyle: baseTheme.textTheme.pickerTextStyle.copyWith( + fontSize: 21 * _textSize, + ), + dateTimePickerTextStyle: baseTheme.textTheme.dateTimePickerTextStyle + .copyWith(fontSize: 21 * _textSize), + ); + return CupertinoApp( + title: 'Cupertino Gallery', + theme: baseTheme.copyWith(textTheme: textTheme), + home: GalleryHome( + onThemeChange: _handleThemeChange, + isDarkMode: _themeMode == ThemeMode.dark, + onTextSizeChange: _handleTextSizeChange, + textSize: _textSize, + ), + ); + } +} diff --git a/cupertino_gallery/lib/settings_page.dart b/cupertino_gallery/lib/settings_page.dart new file mode 100644 index 0000000..6022afb --- /dev/null +++ b/cupertino_gallery/lib/settings_page.dart @@ -0,0 +1,142 @@ +import 'package:flutter/cupertino.dart'; + +class SettingsPage extends StatefulWidget { + const SettingsPage({ + super.key, + required this.onThemeChange, + required this.isDarkMode, + required this.onTextSizeChange, + required this.textSize, + }); + + final ValueChanged onThemeChange; + final bool isDarkMode; + final ValueChanged onTextSizeChange; + final double textSize; + + @override + State createState() => _SettingsPageState(); +} + +class _SettingsPageState extends State { + late bool isDarkMode; + late double _textSize; + + @override + void initState() { + super.initState(); + isDarkMode = widget.isDarkMode; + _textSize = widget.textSize; + } + + @override + void didUpdateWidget(SettingsPage oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.isDarkMode != oldWidget.isDarkMode) { + isDarkMode = widget.isDarkMode; + } + if (widget.textSize != oldWidget.textSize) { + _textSize = widget.textSize; + } + } + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar(middle: Text('Settings')), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CupertinoListSection( + header: const Text('Appearance'), + children: [ + CupertinoListTile( + title: const Text('Dark Mode'), + trailing: CupertinoSwitch( + value: isDarkMode, + onChanged: (isActive) { + setState(() { + isDarkMode = isActive; + widget.onThemeChange(isActive); + }); + }, + ), + ), + CupertinoListTile( + title: const Text('Text Size'), + trailing: Text('${(_textSize * 100).toStringAsFixed(0)}%'), + ), + CupertinoSlider( + value: _textSize, + min: 0.5, + max: 1.5, + onChanged: (value) { + setState(() { + _textSize = value; + }); + widget.onTextSizeChange(value); + }, + ), + ], + ), + CupertinoListSection( + header: const Text('General'), + children: [ + CupertinoListTile( + title: const Text('About'), + trailing: const Icon(CupertinoIcons.forward), + onTap: () {}, + ), + CupertinoListTile( + title: const Text('Privacy'), + trailing: const Icon(CupertinoIcons.forward), + onTap: () {}, + ), + CupertinoListTile( + title: const Text('Help'), + trailing: const Icon(CupertinoIcons.forward), + onTap: () {}, + ), + CupertinoListTile( + title: const Text('Reset Settings'), + trailing: const Icon(CupertinoIcons.forward), + onTap: () { + showCupertinoDialog( + context: context, + builder: (context) => CupertinoAlertDialog( + title: const Text('Reset Settings'), + content: const Text( + 'Are you sure you want to reset all settings?', + ), + actions: [ + CupertinoDialogAction( + child: const Text('Cancel'), + onPressed: () { + Navigator.pop(context); + }, + ), + CupertinoDialogAction( + isDestructiveAction: true, + child: const Text('Reset'), + onPressed: () { + setState(() { + isDarkMode = false; + _textSize = 1.0; + widget.onThemeChange(false); + widget.onTextSizeChange(1.0); + }); + Navigator.pop(context); + }, + ), + ], + ), + ); + }, + ), + ], + ), + ], + ), + ); + } +} diff --git a/cupertino_gallery/lib/widget_detail_page.dart b/cupertino_gallery/lib/widget_detail_page.dart new file mode 100644 index 0000000..bd289c4 --- /dev/null +++ b/cupertino_gallery/lib/widget_detail_page.dart @@ -0,0 +1,84 @@ +import 'package:flutter/cupertino.dart'; + +import 'widgets/action_sheet_page.dart'; +import 'widgets/activity_indicator_page.dart'; +import 'widgets/alert_dialog_page.dart'; +import 'widgets/button_page.dart'; +import 'widgets/checkbox_page.dart'; +import 'widgets/context_menu_page.dart'; +import 'widgets/date_picker_page.dart'; +import 'widgets/list_tile_page.dart'; +import 'widgets/picker_page.dart'; +import 'widgets/popup_surface_page.dart'; +import 'widgets/radio_page.dart'; +import 'widgets/scrollbar_page.dart'; +import 'widgets/search_text_field_page.dart'; +import 'widgets/segmented_control_page.dart'; +import 'widgets/sheet_page.dart'; +import 'widgets/slider_page.dart'; +import 'widgets/sliding_segmented_control_page.dart'; +import 'widgets/switch_page.dart'; +import 'widgets/text_field_page.dart'; +import 'widgets/text_theme_page.dart'; +import 'widgets/time_picker_page.dart'; + +class WidgetDetailPage extends StatelessWidget { + const WidgetDetailPage({super.key, required this.title}); + + final String title; + + @override + Widget build(BuildContext context) { + switch (title) { + case 'Action Sheet': + return const ActionSheetPage(); + case 'Activity Indicator': + return const ActivityIndicatorPage(); + case 'Alert Dialog': + return const AlertDialogPage(); + case 'Button': + return const ButtonPage(); + case 'Checkbox': + return const CheckboxPage(); + case 'Context Menu': + return const ContextMenuPage(); + case 'Date Picker': + return const DatePickerPage(); + case 'List Tile': + return const ListTilePage(); + case 'Picker': + return const PickerPage(); + case 'Popup Surface': + return const PopupSurfacePage(); + case 'Radio': + return const RadioPage(); + case 'Scrollbar': + return const ScrollbarPage(); + case 'Search Text Field': + return const SearchTextFieldPage(); + case 'Segmented Control': + return const SegmentedControlPage(); + case 'Sheet': + return const SheetPage(); + case 'Slider': + return const SliderPage(); + case 'Sliding Segmented Control': + return const SlidingSegmentedControlPage(); + case 'Switch': + return const SwitchPage(); + case 'Text Field': + return const TextFieldPage(); + case 'Text Theme': + return const TextThemePage(); + case 'Time Picker': + return const TimePickerPage(); + default: + return const CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text('Widget Not Found'), + ), + child: Center(child: Text('Widget Not Found')), + ); + } + } +} diff --git a/cupertino_gallery/lib/widgets/action_sheet_page.dart b/cupertino_gallery/lib/widgets/action_sheet_page.dart new file mode 100644 index 0000000..0a26a4f --- /dev/null +++ b/cupertino_gallery/lib/widgets/action_sheet_page.dart @@ -0,0 +1,47 @@ +import 'package:flutter/cupertino.dart'; + +class ActionSheetPage extends StatelessWidget { + const ActionSheetPage({super.key}); + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar(middle: Text('Action Sheet')), + child: Center( + child: CupertinoButton( + child: const Text('Show Action Sheet'), + onPressed: () { + showCupertinoModalPopup( + context: context, + builder: (context) => CupertinoActionSheet( + title: const Text('Title'), + message: const Text('Message'), + actions: [ + CupertinoActionSheetAction( + child: const Text('Action One'), + onPressed: () { + Navigator.pop(context); + }, + ), + CupertinoActionSheetAction( + child: const Text('Action Two'), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + cancelButton: CupertinoActionSheetAction( + isDefaultAction: true, + onPressed: () { + Navigator.pop(context); + }, + child: const Text('Cancel'), + ), + ), + ); + }, + ), + ), + ); + } +} diff --git a/cupertino_gallery/lib/widgets/activity_indicator_page.dart b/cupertino_gallery/lib/widgets/activity_indicator_page.dart new file mode 100644 index 0000000..f02452f --- /dev/null +++ b/cupertino_gallery/lib/widgets/activity_indicator_page.dart @@ -0,0 +1,13 @@ +import 'package:flutter/cupertino.dart'; + +class ActivityIndicatorPage extends StatelessWidget { + const ActivityIndicatorPage({super.key}); + + @override + Widget build(BuildContext context) { + return const CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar(middle: Text('Activity Indicator')), + child: Center(child: CupertinoActivityIndicator()), + ); + } +} diff --git a/cupertino_gallery/lib/widgets/alert_dialog_page.dart b/cupertino_gallery/lib/widgets/alert_dialog_page.dart new file mode 100644 index 0000000..bd8aa78 --- /dev/null +++ b/cupertino_gallery/lib/widgets/alert_dialog_page.dart @@ -0,0 +1,41 @@ +import 'package:flutter/cupertino.dart'; + +class AlertDialogPage extends StatelessWidget { + const AlertDialogPage({super.key}); + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar(middle: Text('Alert Dialog')), + child: Center( + child: CupertinoButton( + child: const Text('Show Alert Dialog'), + onPressed: () { + showCupertinoDialog( + context: context, + builder: (context) => CupertinoAlertDialog( + title: const Text('Alert'), + content: const Text('This is a sample alert dialog.'), + actions: [ + CupertinoDialogAction( + child: const Text('OK'), + onPressed: () { + Navigator.pop(context); + }, + ), + CupertinoDialogAction( + isDefaultAction: true, + child: const Text('NO'), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ), + ); + }, + ), + ), + ); + } +} diff --git a/cupertino_gallery/lib/widgets/button_page.dart b/cupertino_gallery/lib/widgets/button_page.dart new file mode 100644 index 0000000..d599168 --- /dev/null +++ b/cupertino_gallery/lib/widgets/button_page.dart @@ -0,0 +1,42 @@ +import 'package:flutter/cupertino.dart'; + +class ButtonPage extends StatelessWidget { + const ButtonPage({super.key}); + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar(middle: Text('Button')), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'CupertinoButton widget', + style: CupertinoTheme.of(context).textTheme.textStyle, + ), + const SizedBox(height: 16), + CupertinoButton(child: const Text('Enabled'), onPressed: () {}), + const SizedBox(height: 16), + const CupertinoButton(onPressed: null, child: Text('Disabled')), + const SizedBox(height: 32), + Text( + 'CupertinoButton.filled widget', + style: CupertinoTheme.of(context).textTheme.textStyle, + ), + const SizedBox(height: 16), + CupertinoButton.filled( + child: const Text('Enabled'), + onPressed: () {}, + ), + const SizedBox(height: 16), + const CupertinoButton.filled( + onPressed: null, + child: Text('Disabled'), + ), + ], + ), + ), + ); + } +} diff --git a/cupertino_gallery/lib/widgets/checkbox_page.dart b/cupertino_gallery/lib/widgets/checkbox_page.dart new file mode 100644 index 0000000..b482054 --- /dev/null +++ b/cupertino_gallery/lib/widgets/checkbox_page.dart @@ -0,0 +1,29 @@ +import 'package:flutter/cupertino.dart'; + +class CheckboxPage extends StatefulWidget { + const CheckboxPage({super.key}); + + @override + State createState() => _CheckboxPageState(); +} + +class _CheckboxPageState extends State { + bool _value = false; + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar(middle: Text('Checkbox')), + child: Center( + child: CupertinoCheckbox( + value: _value, + onChanged: (value) { + setState(() { + _value = value!; + }); + }, + ), + ), + ); + } +} diff --git a/cupertino_gallery/lib/widgets/context_menu_page.dart b/cupertino_gallery/lib/widgets/context_menu_page.dart new file mode 100644 index 0000000..6e44324 --- /dev/null +++ b/cupertino_gallery/lib/widgets/context_menu_page.dart @@ -0,0 +1,42 @@ +import 'package:flutter/cupertino.dart'; + +class ContextMenuPage extends StatelessWidget { + const ContextMenuPage({super.key}); + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar(middle: Text('Context Menu')), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Long press to activate context menu:'), + SizedBox(height: 16), + SizedBox( + width: 100, + height: 100, + child: CupertinoContextMenu( + actions: [ + CupertinoContextMenuAction( + child: const Text('Action one'), + onPressed: () { + Navigator.pop(context); + }, + ), + CupertinoContextMenuAction( + child: const Text('Action two'), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + child: Container(color: CupertinoColors.activeBlue), + ), + ), + ], + ), + ), + ); + } +} diff --git a/cupertino_gallery/lib/widgets/date_picker_page.dart b/cupertino_gallery/lib/widgets/date_picker_page.dart new file mode 100644 index 0000000..aee62b4 --- /dev/null +++ b/cupertino_gallery/lib/widgets/date_picker_page.dart @@ -0,0 +1,18 @@ +import 'package:flutter/cupertino.dart'; + +class DatePickerPage extends StatelessWidget { + const DatePickerPage({super.key}); + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar(middle: Text('Date Picker')), + child: Center( + child: SizedBox( + height: 200, + child: CupertinoDatePicker(onDateTimeChanged: (newDate) {}), + ), + ), + ); + } +} diff --git a/cupertino_gallery/lib/widgets/list_tile_page.dart b/cupertino_gallery/lib/widgets/list_tile_page.dart new file mode 100644 index 0000000..4821c14 --- /dev/null +++ b/cupertino_gallery/lib/widgets/list_tile_page.dart @@ -0,0 +1,62 @@ +import 'package:flutter/cupertino.dart'; + +class ListTilePage extends StatelessWidget { + const ListTilePage({super.key}); + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar(middle: Text('List Tile')), + child: Center( + child: ListView( + children: [ + CupertinoListSection.insetGrouped( + children: [ + CupertinoListTile( + title: Text('Title'), + subtitle: Text('Subtitle'), + leading: Icon(CupertinoIcons.info), + trailing: Icon(CupertinoIcons.forward), + ), + CupertinoListTile( + title: Text('Title'), + subtitle: Text('Subtitle'), + leading: Icon(CupertinoIcons.person), + trailing: Icon(CupertinoIcons.forward), + ), + CupertinoListTile( + title: Text('Title'), + subtitle: Text('Subtitle'), + leading: Icon(CupertinoIcons.wifi), + trailing: Icon(CupertinoIcons.forward), + ), + ], + ), + CupertinoListSection( + children: [ + CupertinoListTile( + title: Text('Title'), + subtitle: Text('Subtitle'), + leading: Icon(CupertinoIcons.search), + trailing: Icon(CupertinoIcons.forward), + ), + CupertinoListTile( + title: Text('Title'), + subtitle: Text('Subtitle'), + leading: Icon(CupertinoIcons.heart_fill), + trailing: Icon(CupertinoIcons.forward), + ), + CupertinoListTile( + title: Text('Title'), + subtitle: Text('Subtitle'), + leading: Icon(CupertinoIcons.ant), + trailing: Icon(CupertinoIcons.forward), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/cupertino_gallery/lib/widgets/picker_page.dart b/cupertino_gallery/lib/widgets/picker_page.dart new file mode 100644 index 0000000..60ef8e6 --- /dev/null +++ b/cupertino_gallery/lib/widgets/picker_page.dart @@ -0,0 +1,22 @@ +import 'package:flutter/cupertino.dart'; + +class PickerPage extends StatelessWidget { + const PickerPage({super.key}); + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar(middle: Text('Picker')), + child: Center( + child: SizedBox( + height: 200, + child: CupertinoPicker( + itemExtent: 32, + onSelectedItemChanged: (index) {}, + children: const [Text('One'), Text('Two'), Text('Three')], + ), + ), + ), + ); + } +} diff --git a/cupertino_gallery/lib/widgets/popup_surface_page.dart b/cupertino_gallery/lib/widgets/popup_surface_page.dart new file mode 100644 index 0000000..d871e6c --- /dev/null +++ b/cupertino_gallery/lib/widgets/popup_surface_page.dart @@ -0,0 +1,36 @@ +import 'package:flutter/cupertino.dart'; + +class PopupSurfacePage extends StatelessWidget { + const PopupSurfacePage({super.key}); + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar( + middle: Text('Popup Surface'), + ), + child: Center( + child: CupertinoButton( + child: const Text('Show Popup Surface'), + onPressed: () { + showCupertinoModalPopup( + context: context, + builder: (context) { + return CupertinoPopupSurface( + child: Container( + color: CupertinoColors.white, + width: 200, + height: 200, + child: const Center( + child: Text('This is a popup surface.'), + ), + ), + ); + }, + ); + }, + ), + ), + ); + } +} diff --git a/cupertino_gallery/lib/widgets/radio_page.dart b/cupertino_gallery/lib/widgets/radio_page.dart new file mode 100644 index 0000000..c6003c1 --- /dev/null +++ b/cupertino_gallery/lib/widgets/radio_page.dart @@ -0,0 +1,46 @@ +import 'package:flutter/cupertino.dart'; + +class RadioPage extends StatefulWidget { + const RadioPage({super.key}); + + @override + State createState() => _RadioPageState(); +} + +class _RadioPageState extends State { + int _selectedValue = 0; + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar(middle: Text('Radio')), + child: Center( + child: RadioGroup( + groupValue: _selectedValue, + onChanged: (value) { + setState(() { + _selectedValue = value!; + }); + }, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CupertinoListTile( + title: const Text('Option 1'), + leading: CupertinoRadio(value: 0), + ), + CupertinoListTile( + title: const Text('Option 2'), + leading: CupertinoRadio(value: 1), + ), + CupertinoListTile( + title: const Text('Option 3'), + leading: CupertinoRadio(value: 2), + ), + ], + ), + ), + ), + ); + } +} diff --git a/cupertino_gallery/lib/widgets/scrollbar_page.dart b/cupertino_gallery/lib/widgets/scrollbar_page.dart new file mode 100644 index 0000000..cf9e675 --- /dev/null +++ b/cupertino_gallery/lib/widgets/scrollbar_page.dart @@ -0,0 +1,26 @@ +import 'package:flutter/cupertino.dart'; + +class ScrollbarPage extends StatelessWidget { + const ScrollbarPage({super.key}); + + @override + Widget build(BuildContext context) { + final ScrollController controller = ScrollController(); + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar(middle: Text('Scrollbar')), + child: CupertinoScrollbar( + controller: controller, + child: ListView.separated( + controller: controller, + itemCount: 100, + itemBuilder: (context, index) { + return CupertinoListTile(title: Text('Item $index')); + }, + separatorBuilder: (context, index) { + return Container(height: 1, color: CupertinoColors.opaqueSeparator); + }, + ), + ), + ); + } +} diff --git a/cupertino_gallery/lib/widgets/search_text_field_page.dart b/cupertino_gallery/lib/widgets/search_text_field_page.dart new file mode 100644 index 0000000..7a591ef --- /dev/null +++ b/cupertino_gallery/lib/widgets/search_text_field_page.dart @@ -0,0 +1,18 @@ +import 'package:flutter/cupertino.dart'; + +class SearchTextFieldPage extends StatelessWidget { + const SearchTextFieldPage({super.key}); + + @override + Widget build(BuildContext context) { + return const CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar(middle: Text('Search Text Field')), + child: Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: CupertinoSearchTextField(), + ), + ), + ); + } +} diff --git a/cupertino_gallery/lib/widgets/segmented_control_page.dart b/cupertino_gallery/lib/widgets/segmented_control_page.dart new file mode 100644 index 0000000..4e344d6 --- /dev/null +++ b/cupertino_gallery/lib/widgets/segmented_control_page.dart @@ -0,0 +1,36 @@ +import 'package:flutter/cupertino.dart'; + +class SegmentedControlPage extends StatefulWidget { + const SegmentedControlPage({super.key}); + + @override + State createState() => _SegmentedControlPageState(); +} + +class _SegmentedControlPageState extends State { + int _selectedIndex = 0; + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar( + middle: Text('Segmented Control'), + ), + child: Center( + child: CupertinoSegmentedControl( + children: { + 0: Text('One'), + 1: Text('Two'), + 2: Text('Three'), + }, + onValueChanged: (val) { + setState(() { + _selectedIndex = val; + }); + }, + groupValue: _selectedIndex, + ), + ), + ); + } +} diff --git a/cupertino_gallery/lib/widgets/sheet_page.dart b/cupertino_gallery/lib/widgets/sheet_page.dart new file mode 100644 index 0000000..1e977a8 --- /dev/null +++ b/cupertino_gallery/lib/widgets/sheet_page.dart @@ -0,0 +1,38 @@ +import 'package:flutter/cupertino.dart'; + +class SheetPage extends StatelessWidget { + const SheetPage({super.key}); + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar(middle: Text('Sheet')), + child: Center( + child: CupertinoButton.filled( + child: const Text('Show Sheet'), + onPressed: () { + Navigator.of(context).push( + CupertinoSheetRoute( + scrollableBuilder: + (context, controller) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: const Text('Sheet'), + trailing: GestureDetector( + child: const Icon(CupertinoIcons.xmark), + onTap: () { + Navigator.of(context).pop(); + }, + ), + ), + child: const Center(child: Text('This is a sheet')), + ); + }, + ), + ); + }, + ), + ), + ); + } +} diff --git a/cupertino_gallery/lib/widgets/slider_page.dart b/cupertino_gallery/lib/widgets/slider_page.dart new file mode 100644 index 0000000..0ed332e --- /dev/null +++ b/cupertino_gallery/lib/widgets/slider_page.dart @@ -0,0 +1,29 @@ +import 'package:flutter/cupertino.dart'; + +class SliderPage extends StatefulWidget { + const SliderPage({super.key}); + + @override + State createState() => _SliderPageState(); +} + +class _SliderPageState extends State { + double _value = 0.5; + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar(middle: Text('Slider')), + child: Center( + child: CupertinoSlider( + value: _value, + onChanged: (value) { + setState(() { + _value = value; + }); + }, + ), + ), + ); + } +} diff --git a/cupertino_gallery/lib/widgets/sliding_segmented_control_page.dart b/cupertino_gallery/lib/widgets/sliding_segmented_control_page.dart new file mode 100644 index 0000000..11f97fd --- /dev/null +++ b/cupertino_gallery/lib/widgets/sliding_segmented_control_page.dart @@ -0,0 +1,38 @@ +import 'package:flutter/cupertino.dart'; + +class SlidingSegmentedControlPage extends StatefulWidget { + const SlidingSegmentedControlPage({super.key}); + + @override + State createState() => + _SlidingSegmentedControlPageState(); +} + +class _SlidingSegmentedControlPageState + extends State { + int? _groupValue = 0; + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar( + middle: Text('Sliding Segmented Control'), + ), + child: Center( + child: CupertinoSlidingSegmentedControl( + children: const { + 0: Text('One'), + 1: Text('Two'), + 2: Text('Three'), + }, + onValueChanged: (value) { + setState(() { + _groupValue = value; + }); + }, + groupValue: _groupValue, + ), + ), + ); + } +} diff --git a/cupertino_gallery/lib/widgets/switch_page.dart b/cupertino_gallery/lib/widgets/switch_page.dart new file mode 100644 index 0000000..6ebb66a --- /dev/null +++ b/cupertino_gallery/lib/widgets/switch_page.dart @@ -0,0 +1,29 @@ +import 'package:flutter/cupertino.dart'; + +class SwitchPage extends StatefulWidget { + const SwitchPage({super.key}); + + @override + State createState() => _SwitchPageState(); +} + +class _SwitchPageState extends State { + bool _value = true; + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar(middle: Text('Switch')), + child: Center( + child: CupertinoSwitch( + value: _value, + onChanged: (value) { + setState(() { + _value = value; + }); + }, + ), + ), + ); + } +} diff --git a/cupertino_gallery/lib/widgets/text_field_page.dart b/cupertino_gallery/lib/widgets/text_field_page.dart new file mode 100644 index 0000000..5d40a8a --- /dev/null +++ b/cupertino_gallery/lib/widgets/text_field_page.dart @@ -0,0 +1,18 @@ +import 'package:flutter/cupertino.dart'; + +class TextFieldPage extends StatelessWidget { + const TextFieldPage({super.key}); + + @override + Widget build(BuildContext context) { + return const CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar(middle: Text('Text Field')), + child: Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: CupertinoTextField(placeholder: 'Enter text'), + ), + ), + ); + } +} diff --git a/cupertino_gallery/lib/widgets/text_theme_page.dart b/cupertino_gallery/lib/widgets/text_theme_page.dart new file mode 100644 index 0000000..f26912e --- /dev/null +++ b/cupertino_gallery/lib/widgets/text_theme_page.dart @@ -0,0 +1,57 @@ +import 'package:flutter/cupertino.dart'; + +class TextThemePage extends StatelessWidget { + const TextThemePage({super.key}); + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar(middle: Text('Text Theme')), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'This is the default text style', + style: CupertinoTheme.of(context).textTheme.textStyle, + ), + const SizedBox(height: 16), + Text( + 'This is the action text style', + style: CupertinoTheme.of(context).textTheme.actionTextStyle, + ), + const SizedBox(height: 16), + Text( + 'This is the tab label text style', + style: CupertinoTheme.of(context).textTheme.tabLabelTextStyle, + ), + const SizedBox(height: 16), + Text( + 'This is the nav title text style', + style: CupertinoTheme.of(context).textTheme.navTitleTextStyle, + ), + const SizedBox(height: 16), + Text( + 'This is the nav large title text style', + style: CupertinoTheme.of( + context, + ).textTheme.navLargeTitleTextStyle, + ), + const SizedBox(height: 16), + Text( + 'This is the picker text style', + style: CupertinoTheme.of(context).textTheme.pickerTextStyle, + ), + const SizedBox(height: 16), + Text( + 'This is the date time picker text style', + style: CupertinoTheme.of( + context, + ).textTheme.dateTimePickerTextStyle, + ), + ], + ), + ), + ); + } +} diff --git a/cupertino_gallery/lib/widgets/time_picker_page.dart b/cupertino_gallery/lib/widgets/time_picker_page.dart new file mode 100644 index 0000000..f50ca14 --- /dev/null +++ b/cupertino_gallery/lib/widgets/time_picker_page.dart @@ -0,0 +1,20 @@ +import 'package:flutter/cupertino.dart'; + +class TimePickerPage extends StatelessWidget { + const TimePickerPage({super.key}); + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar(middle: Text('Time Picker')), + child: Center( + child: SizedBox( + height: 200, + child: CupertinoTimerPicker( + onTimerDurationChanged: (newDuration) {}, + ), + ), + ), + ); + } +} diff --git a/cupertino_gallery/lib/widgets_page.dart b/cupertino_gallery/lib/widgets_page.dart new file mode 100644 index 0000000..af5d5ad --- /dev/null +++ b/cupertino_gallery/lib/widgets_page.dart @@ -0,0 +1,75 @@ +import 'package:flutter/cupertino.dart'; +import 'widget_detail_page.dart'; + +class WidgetsPage extends StatelessWidget { + const WidgetsPage({super.key}); + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + child: CustomScrollView( + slivers: [ + CupertinoSliverNavigationBar(largeTitle: Text('Cupertino Gallery')), + SliverFillRemaining( + child: ListView( + children: [ + CustomCupertinoListTile(title: 'Action Sheet'), + CustomCupertinoListTile(title: 'Activity Indicator'), + CustomCupertinoListTile(title: 'Alert Dialog'), + CustomCupertinoListTile(title: 'Button'), + CustomCupertinoListTile(title: 'Checkbox'), + CustomCupertinoListTile(title: 'Context Menu'), + CustomCupertinoListTile(title: 'Date Picker'), + CustomCupertinoListTile(title: 'List Tile'), + CustomCupertinoListTile(title: 'Picker'), + CustomCupertinoListTile(title: 'Popup Surface'), + CustomCupertinoListTile(title: 'Radio'), + CustomCupertinoListTile(title: 'Scrollbar'), + CustomCupertinoListTile(title: 'Search Text Field'), + CustomCupertinoListTile(title: 'Segmented Control'), + CustomCupertinoListTile(title: 'Sheet'), + CustomCupertinoListTile(title: 'Slider'), + CustomCupertinoListTile(title: 'Sliding Segmented Control'), + CustomCupertinoListTile(title: 'Switch'), + CustomCupertinoListTile(title: 'Text Field'), + CustomCupertinoListTile(title: 'Text Theme'), + CustomCupertinoListTile(title: 'Time Picker'), + ], + ), + ), + ], + ), + ); + } +} + +class CustomCupertinoListTile extends StatelessWidget { + const CustomCupertinoListTile({super.key, required this.title}); + + final String title; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + Navigator.of(context).push( + CupertinoPageRoute( + builder: (context) { + return WidgetDetailPage(title: title); + }, + ), + ); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), + decoration: const BoxDecoration( + border: Border(bottom: BorderSide(color: CupertinoColors.separator)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [Text(title), const Icon(CupertinoIcons.forward)], + ), + ), + ); + } +} diff --git a/cupertino_gallery/linux/.gitignore b/cupertino_gallery/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/cupertino_gallery/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/cupertino_gallery/linux/CMakeLists.txt b/cupertino_gallery/linux/CMakeLists.txt new file mode 100644 index 0000000..cee57a9 --- /dev/null +++ b/cupertino_gallery/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "cupertino_gallery") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.cupertino_gallery") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/cupertino_gallery/linux/flutter/CMakeLists.txt b/cupertino_gallery/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/cupertino_gallery/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/cupertino_gallery/linux/flutter/generated_plugin_registrant.cc b/cupertino_gallery/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..e71a16d --- /dev/null +++ b/cupertino_gallery/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/cupertino_gallery/linux/flutter/generated_plugin_registrant.h b/cupertino_gallery/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/cupertino_gallery/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/cupertino_gallery/linux/flutter/generated_plugins.cmake b/cupertino_gallery/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..2e1de87 --- /dev/null +++ b/cupertino_gallery/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/cupertino_gallery/linux/runner/CMakeLists.txt b/cupertino_gallery/linux/runner/CMakeLists.txt new file mode 100644 index 0000000..e97dabc --- /dev/null +++ b/cupertino_gallery/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/cupertino_gallery/linux/runner/main.cc b/cupertino_gallery/linux/runner/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/cupertino_gallery/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/cupertino_gallery/linux/runner/my_application.cc b/cupertino_gallery/linux/runner/my_application.cc new file mode 100644 index 0000000..781e741 --- /dev/null +++ b/cupertino_gallery/linux/runner/my_application.cc @@ -0,0 +1,144 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Called when first Flutter frame received. +static void first_frame_cb(MyApplication* self, FlView *view) +{ + gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); +} + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "cupertino_gallery"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "cupertino_gallery"); + } + + gtk_window_set_default_size(window, 1280, 720); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + GdkRGBA background_color; + // Background defaults to black, override it here if necessary, e.g. #00000000 for transparent. + gdk_rgba_parse(&background_color, "#000000"); + fl_view_set_background_color(view, &background_color); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + // Show the window when Flutter renders. + // Requires the view to be realized so we can start rendering. + g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), self); + gtk_widget_realize(GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/cupertino_gallery/linux/runner/my_application.h b/cupertino_gallery/linux/runner/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/cupertino_gallery/linux/runner/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/cupertino_gallery/macos/.gitignore b/cupertino_gallery/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/cupertino_gallery/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/cupertino_gallery/macos/Flutter/Flutter-Debug.xcconfig b/cupertino_gallery/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/cupertino_gallery/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/cupertino_gallery/macos/Flutter/Flutter-Release.xcconfig b/cupertino_gallery/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/cupertino_gallery/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/cupertino_gallery/macos/Flutter/GeneratedPluginRegistrant.swift b/cupertino_gallery/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..cccf817 --- /dev/null +++ b/cupertino_gallery/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,10 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { +} diff --git a/cupertino_gallery/macos/Runner.xcodeproj/project.pbxproj b/cupertino_gallery/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..ad1b793 --- /dev/null +++ b/cupertino_gallery/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,705 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* cupertino_gallery.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "cupertino_gallery.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* cupertino_gallery.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* cupertino_gallery.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.cupertinoGallery.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/cupertino_gallery.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/cupertino_gallery"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.cupertinoGallery.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/cupertino_gallery.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/cupertino_gallery"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.cupertinoGallery.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/cupertino_gallery.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/cupertino_gallery"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/cupertino_gallery/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/cupertino_gallery/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/cupertino_gallery/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/cupertino_gallery/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/cupertino_gallery/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..6cebf58 --- /dev/null +++ b/cupertino_gallery/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cupertino_gallery/macos/Runner.xcworkspace/contents.xcworkspacedata b/cupertino_gallery/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/cupertino_gallery/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/cupertino_gallery/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/cupertino_gallery/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/cupertino_gallery/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/cupertino_gallery/macos/Runner/AppDelegate.swift b/cupertino_gallery/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..b3c1761 --- /dev/null +++ b/cupertino_gallery/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/cupertino_gallery/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/cupertino_gallery/macos/Runner/Base.lproj/MainMenu.xib b/cupertino_gallery/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/cupertino_gallery/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cupertino_gallery/macos/Runner/Configs/AppInfo.xcconfig b/cupertino_gallery/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..4aaf31c --- /dev/null +++ b/cupertino_gallery/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = cupertino_gallery + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.cupertinoGallery + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright Β© 2025 com.example. All rights reserved. diff --git a/cupertino_gallery/macos/Runner/Configs/Debug.xcconfig b/cupertino_gallery/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/cupertino_gallery/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/cupertino_gallery/macos/Runner/Configs/Release.xcconfig b/cupertino_gallery/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/cupertino_gallery/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/cupertino_gallery/macos/Runner/Configs/Warnings.xcconfig b/cupertino_gallery/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/cupertino_gallery/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/cupertino_gallery/macos/Runner/DebugProfile.entitlements b/cupertino_gallery/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/cupertino_gallery/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/cupertino_gallery/macos/Runner/Info.plist b/cupertino_gallery/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/cupertino_gallery/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/cupertino_gallery/macos/Runner/MainFlutterWindow.swift b/cupertino_gallery/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/cupertino_gallery/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/cupertino_gallery/macos/Runner/Release.entitlements b/cupertino_gallery/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/cupertino_gallery/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/cupertino_gallery/macos/RunnerTests/RunnerTests.swift b/cupertino_gallery/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/cupertino_gallery/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/cupertino_gallery/pubspec.yaml b/cupertino_gallery/pubspec.yaml new file mode 100644 index 0000000..69e483a --- /dev/null +++ b/cupertino_gallery/pubspec.yaml @@ -0,0 +1,22 @@ +name: cupertino_gallery +description: A Flutter project showcasing supported Cupertino components. +publish_to: "none" +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^1.0.8 + +dev_dependencies: + analysis_defaults: + path: ../analysis_defaults + flutter_test: + sdk: flutter + +flutter: + uses-material-design: true \ No newline at end of file diff --git a/cupertino_gallery/test/widget_test.dart b/cupertino_gallery/test/widget_test.dart new file mode 100644 index 0000000..2a2b819 --- /dev/null +++ b/cupertino_gallery/test/widget_test.dart @@ -0,0 +1,8 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +void main() {} diff --git a/cupertino_gallery/web/favicon.png b/cupertino_gallery/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/cupertino_gallery/web/favicon.png differ diff --git a/cupertino_gallery/web/icons/Icon-192.png b/cupertino_gallery/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/cupertino_gallery/web/icons/Icon-192.png differ diff --git a/cupertino_gallery/web/icons/Icon-512.png b/cupertino_gallery/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/cupertino_gallery/web/icons/Icon-512.png differ diff --git a/cupertino_gallery/web/icons/Icon-maskable-192.png b/cupertino_gallery/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/cupertino_gallery/web/icons/Icon-maskable-192.png differ diff --git a/cupertino_gallery/web/icons/Icon-maskable-512.png b/cupertino_gallery/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/cupertino_gallery/web/icons/Icon-maskable-512.png differ diff --git a/cupertino_gallery/web/index.html b/cupertino_gallery/web/index.html new file mode 100644 index 0000000..927e427 --- /dev/null +++ b/cupertino_gallery/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + cupertino_gallery + + + + + + diff --git a/cupertino_gallery/web/manifest.json b/cupertino_gallery/web/manifest.json new file mode 100644 index 0000000..363529b --- /dev/null +++ b/cupertino_gallery/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "cupertino_gallery", + "short_name": "cupertino_gallery", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/cupertino_gallery/windows/.gitignore b/cupertino_gallery/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/cupertino_gallery/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/cupertino_gallery/windows/CMakeLists.txt b/cupertino_gallery/windows/CMakeLists.txt new file mode 100644 index 0000000..dbab392 --- /dev/null +++ b/cupertino_gallery/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(cupertino_gallery LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "cupertino_gallery") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/cupertino_gallery/windows/flutter/CMakeLists.txt b/cupertino_gallery/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/cupertino_gallery/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/cupertino_gallery/windows/flutter/generated_plugin_registrant.cc b/cupertino_gallery/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..8b6d468 --- /dev/null +++ b/cupertino_gallery/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/cupertino_gallery/windows/flutter/generated_plugin_registrant.h b/cupertino_gallery/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/cupertino_gallery/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/cupertino_gallery/windows/flutter/generated_plugins.cmake b/cupertino_gallery/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..b93c4c3 --- /dev/null +++ b/cupertino_gallery/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/cupertino_gallery/windows/runner/CMakeLists.txt b/cupertino_gallery/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/cupertino_gallery/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/cupertino_gallery/windows/runner/Runner.rc b/cupertino_gallery/windows/runner/Runner.rc new file mode 100644 index 0000000..755bfcf --- /dev/null +++ b/cupertino_gallery/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "cupertino_gallery" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "cupertino_gallery" "\0" + VALUE "LegalCopyright", "Copyright (C) 2025 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "cupertino_gallery.exe" "\0" + VALUE "ProductName", "cupertino_gallery" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/cupertino_gallery/windows/runner/flutter_window.cpp b/cupertino_gallery/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/cupertino_gallery/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/cupertino_gallery/windows/runner/flutter_window.h b/cupertino_gallery/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/cupertino_gallery/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/cupertino_gallery/windows/runner/main.cpp b/cupertino_gallery/windows/runner/main.cpp new file mode 100644 index 0000000..d2938f5 --- /dev/null +++ b/cupertino_gallery/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"cupertino_gallery", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/cupertino_gallery/windows/runner/resource.h b/cupertino_gallery/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/cupertino_gallery/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/cupertino_gallery/windows/runner/resources/app_icon.ico b/cupertino_gallery/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/cupertino_gallery/windows/runner/resources/app_icon.ico differ diff --git a/cupertino_gallery/windows/runner/runner.exe.manifest b/cupertino_gallery/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/cupertino_gallery/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/cupertino_gallery/windows/runner/utils.cpp b/cupertino_gallery/windows/runner/utils.cpp new file mode 100644 index 0000000..3a0b465 --- /dev/null +++ b/cupertino_gallery/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/cupertino_gallery/windows/runner/utils.h b/cupertino_gallery/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/cupertino_gallery/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/cupertino_gallery/windows/runner/win32_window.cpp b/cupertino_gallery/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/cupertino_gallery/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/cupertino_gallery/windows/runner/win32_window.h b/cupertino_gallery/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/cupertino_gallery/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/date_planner/.gitignore b/date_planner/.gitignore new file mode 100644 index 0000000..29a3a50 --- /dev/null +++ b/date_planner/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/date_planner/.metadata b/date_planner/.metadata new file mode 100644 index 0000000..eeac26e --- /dev/null +++ b/date_planner/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + - platform: ios + create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/date_planner/README.md b/date_planner/README.md new file mode 100644 index 0000000..4a6d593 --- /dev/null +++ b/date_planner/README.md @@ -0,0 +1,5 @@ +An in-progress exploration of an iOS-style Date Planner app, +similar to the SwiftUI Tutorial app +[Date Planner](https://developer.apple.com/tutorials/sample-apps/dateplanner). + +**NB**: This app is ran against the main channel of Flutter only. diff --git a/date_planner/analysis_options.yaml b/date_planner/analysis_options.yaml new file mode 100644 index 0000000..77ebd6f --- /dev/null +++ b/date_planner/analysis_options.yaml @@ -0,0 +1,5 @@ +include: ../analysis_options.yaml + +linter: + rules: + sort_pub_dependencies: true diff --git a/date_planner/ios/.gitignore b/date_planner/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/date_planner/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/date_planner/ios/Flutter/AppFrameworkInfo.plist b/date_planner/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/date_planner/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/date_planner/ios/Flutter/Debug.xcconfig b/date_planner/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/date_planner/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/date_planner/ios/Flutter/Release.xcconfig b/date_planner/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/date_planner/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/date_planner/ios/Runner.xcodeproj/project.pbxproj b/date_planner/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..a07b33e --- /dev/null +++ b/date_planner/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,619 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = GAD6HE5MEM; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.datePlanner; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.datePlanner.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.datePlanner.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.datePlanner.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = GAD6HE5MEM; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.datePlanner; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = GAD6HE5MEM; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.datePlanner; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/date_planner/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/date_planner/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/date_planner/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/date_planner/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/date_planner/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/date_planner/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/date_planner/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/date_planner/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/date_planner/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/date_planner/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/date_planner/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..8e3ca5d --- /dev/null +++ b/date_planner/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/date_planner/ios/Runner.xcworkspace/contents.xcworkspacedata b/date_planner/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/date_planner/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/date_planner/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/date_planner/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/date_planner/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/date_planner/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/date_planner/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/date_planner/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/date_planner/ios/Runner/AppDelegate.swift b/date_planner/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..6266644 --- /dev/null +++ b/date_planner/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/date_planner/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/date_planner/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/date_planner/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/date_planner/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/date_planner/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/date_planner/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/date_planner/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/date_planner/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/date_planner/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/date_planner/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/date_planner/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/date_planner/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/date_planner/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/date_planner/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/date_planner/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/date_planner/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/date_planner/ios/Runner/Base.lproj/LaunchScreen.storyboard b/date_planner/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/date_planner/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/date_planner/ios/Runner/Base.lproj/Main.storyboard b/date_planner/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/date_planner/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/date_planner/ios/Runner/Info.plist b/date_planner/ios/Runner/Info.plist new file mode 100644 index 0000000..c836cd5 --- /dev/null +++ b/date_planner/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Date Planner + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + date_planner + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/date_planner/ios/Runner/Runner-Bridging-Header.h b/date_planner/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/date_planner/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/date_planner/ios/RunnerTests/RunnerTests.swift b/date_planner/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/date_planner/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/date_planner/lib/color_options.dart b/date_planner/lib/color_options.dart new file mode 100644 index 0000000..9d13e58 --- /dev/null +++ b/date_planner/lib/color_options.dart @@ -0,0 +1,28 @@ +// Copyright 2024 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:math'; + +import 'package:flutter/cupertino.dart'; + +enum ColorOptions { + primary(CupertinoColors.black), + gray(CupertinoColors.lightBackgroundGray), + red(CupertinoColors.systemRed), + orange(CupertinoColors.systemOrange), + yellow(CupertinoColors.systemYellow), + green(CupertinoColors.systemGreen), + mint(CupertinoColors.systemMint), + cyan(CupertinoColors.systemCyan), + indigo(CupertinoColors.systemIndigo), + purple(CupertinoColors.systemPurple); + + final Color color; + static final _rnd = Random(); + + const ColorOptions(this.color); + + factory ColorOptions.random() => + ColorOptions.values[_rnd.nextInt(ColorOptions.values.length)]; +} diff --git a/date_planner/lib/event.dart b/date_planner/lib/event.dart new file mode 100644 index 0000000..3dae0df --- /dev/null +++ b/date_planner/lib/event.dart @@ -0,0 +1,81 @@ +// Copyright 2024 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; +import 'package:intl/intl.dart'; +import 'package:uuid/uuid.dart'; + +import 'color_options.dart'; +import 'event_task.dart'; + +class Event implements Comparable { + static const _uuid = Uuid(); + + final id = _uuid.v4(); + String title; + ColorOptions color; + IconData icon; + List tasks; + DateTime date; + + Event({ + required this.title, + ColorOptions? color, + this.icon = CupertinoIcons.add, + List? tasks, + DateTime? date, + }) : color = color ?? ColorOptions.random(), + tasks = tasks ?? [EventTask(text: '')], + date = date ?? DateTime.now(); + + Event copy() { + return Event( + title: title, + color: color, + icon: icon, + tasks: tasks, + date: date, + ); + } + + void updateWith(Event e) { + title = e.title; + color = e.color; + icon = e.icon; + tasks = e.tasks; + date = e.date; + } + + int get remainingTaskCount => tasks.where((e) => !e.isCompleted).length; + + bool get isComplete => remainingTaskCount == 0; + + bool get isPast => DateTime.now().isAfter(date); + + bool get isWithinSevenDays => !isPast && date.isBefore(FromNow.sevenDays); + + bool get isWithinSevenToThirtyDays => + !isPast && !isWithinSevenDays && date.isBefore(FromNow.thirtyDays); + + bool get isDistant => date.isAfter(FromNow.thirtyDays); + + String get dateFormated => + '${DateFormat.yMMMd().format(date)} at ' + '${DateFormat.Hm().format(date)}'; + + @override + int compareTo(Event other) => date.compareTo(other.date); +} + +class FromNow { + static DateTime get sevenDays => DateTime.now().add(const Duration(days: 7)); + + static DateTime get thirtyDays => + DateTime.now().add(const Duration(days: 30)); + + static DateTime roundedHours(int hours) { + final date = DateTime.now().add(Duration(hours: hours)); + return DateTime(date.year, date.month, date.day, date.hour); + } +} diff --git a/date_planner/lib/event_data.dart b/date_planner/lib/event_data.dart new file mode 100644 index 0000000..93e066e --- /dev/null +++ b/date_planner/lib/event_data.dart @@ -0,0 +1,166 @@ +// Copyright 2024 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; + +import 'color_options.dart'; +import 'event.dart'; +import 'event_task.dart'; + +class EventData with ChangeNotifier { + static final _events = buildSampleData(); + + void add(Event event) { + _events.add(event); + notifyListeners(); + } + + void delete(Event event) { + _events.remove(event); + notifyListeners(); + } + + void update(Event original, Event updated) { + _events.firstWhere((e) => e.id == original.id).updateWith(updated); + notifyListeners(); + } + + void exists(Event event) => _events.contains(event); + + List sorted(Period period) => + _events + .where( + (e) => switch (period) { + Period.nextSevenDays => e.isWithinSevenDays, + Period.nextThirtyDays => e.isWithinSevenToThirtyDays, + Period.past => e.isPast, + Period.future => e.isDistant, + }, + ) + .toList() + ..sort((e1, e2) => e1.date.compareTo(e2.date)); +} + +enum Period { + nextSevenDays(name: 'Next 7 Days'), + nextThirtyDays(name: 'Next 30 Days'), + future(name: 'Future'), + past(name: 'Past'); + + const Period({required this.name}); + + final String name; +} + +List buildSampleData() { + return [ + Event( + title: 'Maya\'s Birthday', + color: ColorOptions.red, + icon: CupertinoIcons.gift, + tasks: EventTask.buildList([ + 'Guava kombucha', + 'Paper cups and plates', + 'Cheese plate', + 'Party poppers', + ]), + date: FromNow.roundedHours(24 * 30), + ), + Event( + title: 'Pagliacci', + color: ColorOptions.yellow, + // TODO(mit-mit): Use the icon "theatermasks.fill". + icon: CupertinoIcons.thermometer_snowflake, + tasks: EventTask.buildList([ + 'Buy new tux', + 'Get tickets', + 'Pick up Carmen at the airport and bring her to the show', + ]), + date: FromNow.roundedHours(22), + ), + Event( + title: 'Doctor\'s Appointment', + // TODO(mit-mit): Use the icon "facemask.fill". + icon: CupertinoIcons.lab_flask_solid, + color: ColorOptions.indigo, + tasks: EventTask.buildList([ + 'Bring medical ID', + 'Record heart rate data', + ]), + date: FromNow.roundedHours(24 * 4), + ), + Event( + title: 'Camping Trip', + // TODO(mit-mit): Use the icon "leaf.fill". + icon: CupertinoIcons.leaf_arrow_circlepath, + color: ColorOptions.green, + tasks: EventTask.buildList([ + 'Find a sleeping bag', + 'Bug spray', + 'Paper towels', + 'Food for 4 meals', + 'Straw hat', + ]), + date: FromNow.roundedHours(36), + ), + Event( + title: 'Game Night', + icon: CupertinoIcons.gamecontroller_fill, + color: ColorOptions.cyan, + tasks: EventTask.buildList([ + 'Find a board game to bring', + 'Bring a desert to share', + ]), + date: FromNow.roundedHours(24 * 2), + ), + Event( + title: 'First Day of School', + // TODO(mit-mit): Use the icon "graduationcap.fill". + icon: CupertinoIcons.hammer, + color: ColorOptions.primary, + tasks: EventTask.buildList([ + 'Notebooks', + 'Pencils', + 'Binder', + 'First day of school outfit', + ]), + date: FromNow.roundedHours(24 * 365), + ), + Event( + title: 'Book Launch', + icon: CupertinoIcons.book_fill, + color: ColorOptions.purple, + tasks: EventTask.buildList([ + 'Finish first draft', + 'Send draft to editor', + 'Final read-through', + ]), + date: FromNow.roundedHours(24 * 365 * 2), + ), + Event( + title: 'WWDC', + // TODO(mit-mit): Use the icon "globe.americas.fill" + icon: CupertinoIcons.globe, + color: ColorOptions.gray, + tasks: EventTask.buildList([ + 'Watch Keynote', + 'Watch What\'s new in SwiftUI', + 'Go to DT developer labs', + 'Learn about Create ML', + ]), + date: DateTime(7, 6, 2021), + ), + Event( + title: 'Sayulita Trip', + icon: CupertinoIcons.briefcase_fill, + color: ColorOptions.orange, + tasks: EventTask.buildList([ + 'Buy plane tickets', + 'Get a new bathing suit', + 'Find a hotel room', + ]), + date: FromNow.roundedHours(24 * 19), + ), + ]; +} diff --git a/date_planner/lib/event_detail.dart b/date_planner/lib/event_detail.dart new file mode 100644 index 0000000..b144530 --- /dev/null +++ b/date_planner/lib/event_detail.dart @@ -0,0 +1,143 @@ +// Copyright 2024 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; + +import 'color_options.dart'; +import 'event.dart'; +import 'event_task.dart'; +import 'symbol_editor.dart'; +import 'task_row.dart'; + +class EventDetail extends StatefulWidget { + final Event event; + final bool isEditing; + + const EventDetail({super.key, required this.event, required this.isEditing}); + + @override + State createState() => _EventDetailState(); +} + +class _EventDetailState extends State { + final _eventText = TextEditingController(); + + @override + void initState() { + _eventText.text = widget.event.title; + super.initState(); + } + + @override + Widget build(BuildContext context) { + const titleStyle = TextStyle(fontWeight: FontWeight.bold, fontSize: 22); + final event = widget.event; + + // TODO(mit-mit): Investigate manual overriding of colors and padding. + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Container( + padding: const EdgeInsets.fromLTRB(16, 8, 0, 0), + color: CupertinoColors.systemBackground, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + if (widget.isEditing) + CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + onPressed: () { + Navigator.of(context) + .push( + CupertinoPageRoute<(IconData, ColorOptions)?>( + builder: (_) => + SymbolEditor(event.icon, event.color), + ), + ) + .then(((IconData, ColorOptions)? data) { + if (data != null) { + setState(() { + var (icon, color) = data; + event.icon = icon; + event.color = color; + }); + } + }); + }, + child: Icon( + event.icon, + size: 28, + color: event.color.color, + ), + ), + if (!widget.isEditing) + Icon(event.icon, size: 28, color: event.color.color), + const SizedBox(width: 12), + if (widget.isEditing) + Expanded( + child: CupertinoTextField( + decoration: null, + padding: EdgeInsets.zero, + style: titleStyle, + controller: _eventText, + onChanged: (value) => event.title = value, + ), + ), + if (!widget.isEditing) + Text(event.title, style: titleStyle), + ], + ), + const SizedBox(height: 12), + // TODO(mit-mit): Use a widget for picking a date. + // Issue: Blocked on not having the right calendar widget: + // https://github.com/flutter/flutter/issues/63693 + Text(event.dateFormated), + CupertinoListSection( + // TODO(mit-mit): The list of tasks should be left-flush with the date above. + margin: EdgeInsets.zero, + backgroundColor: CupertinoColors.systemBackground, + decoration: null, + header: const Text( + 'Tasks', + style: TextStyle( + color: CupertinoColors.black, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + children: [ + for (EventTask t in event.tasks) + TaskRow(task: t, isEditing: widget.isEditing), + if (widget.isEditing) + // TODO(mit-mit): CupertinoButton with icon support? + // Consider if CupertinoButton could support setting + // both a label and an icon directly: + // https://www.kodeco.com/books/swiftui-cookbook/v1.0/chapters/8-add-an-icon-to-a-button-in-swiftui + CupertinoButton( + child: const Row( + children: [ + Icon(CupertinoIcons.plus), + Text('Add task'), + ], + ), + onPressed: () { + setState(() { + event.tasks.add(EventTask(text: '')); + }); + }, + ), + ], + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/date_planner/lib/event_editor.dart b/date_planner/lib/event_editor.dart new file mode 100644 index 0000000..03d0093 --- /dev/null +++ b/date_planner/lib/event_editor.dart @@ -0,0 +1,107 @@ +// Copyright 2024 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; + +import 'event.dart'; +import 'event_detail.dart'; + +class EventEditor extends StatefulWidget { + final Event event; + final bool isNew; + const EventEditor({super.key, required this.event, required this.isNew}); + + @override + State createState() => _EventEditorState(); +} + +class _EventEditorState extends State { + late Event event; + late bool isNew; + late bool isEditing; + + @override + void initState() { + isNew = widget.isNew; + isEditing = isNew; + event = widget.event; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + backgroundColor: CupertinoColors.secondarySystemBackground, + navigationBar: CupertinoNavigationBar( + // TODO(mit-mit): Resolve manual padding issues. + // + // Note that even with the padding overriding below, the chevron/ + // back arrow doesn't seem to be far enough to the left. + // + // Is this maybe the issue here? + // https://github.com/flutter/flutter/issues/91715 + leading: isNew + ? CupertinoButton( + padding: EdgeInsets.zero, + child: const Text('Cancel'), + onPressed: () => Navigator.pop(context, null), + ) + : CupertinoButton( + padding: EdgeInsets.zero, + onPressed: () { + Navigator.pop(context, event); + }, + child: const Row( + children: [Icon(CupertinoIcons.back), Text('Date Planner')], + ), + ), + trailing: CupertinoButton( + padding: EdgeInsets.zero, + child: Text(isNew ? 'Add' : (isEditing ? 'Done' : 'Edit')), + onPressed: () { + if (isNew) { + Navigator.pop(context, event); + } else { + setState(() { + if (isEditing) { + isEditing = false; + } else { + isEditing = true; + } + }); + } + }, + ), + ), + // TODO(mit-mit): Why isn't SafeArea included by default? + child: SafeArea( + bottom: false, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + EventDetail(event: event, isEditing: isEditing), + const Spacer(), + if (isEditing && !isNew) + ColoredBox( + color: CupertinoColors.white, + child: Column( + children: [ + CupertinoButton( + child: const Text('Delete Event'), + onPressed: () { + setState(() { + Navigator.pop(context, null); + }); + }, + ), + const SizedBox(height: 24), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/date_planner/lib/event_list.dart b/date_planner/lib/event_list.dart new file mode 100644 index 0000000..0263505 --- /dev/null +++ b/date_planner/lib/event_list.dart @@ -0,0 +1,96 @@ +// Copyright 2024 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; + +import 'event.dart'; +import 'event_data.dart'; +import 'event_editor.dart'; +import 'event_row.dart'; + +class EventList extends StatelessWidget { + const EventList({super.key}); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, events, child) { + return CupertinoPageScaffold( + // TODO(mit-mit): Avoid having to pass nav bar manually. + // + // Would like to pass nav bar and body to CupertinoPageScaffold + // directly, similar to the Material Scaffold's `appBar` and `body` + // args. + // https://github.com/flutter/flutter/issues/149625 + child: CustomScrollView( + slivers: [ + CupertinoSliverNavigationBar( + largeTitle: const Text('Date Planner'), + trailing: CupertinoButton( + padding: EdgeInsets.zero, + child: const Icon(CupertinoIcons.plus), + onPressed: () async { + // Issue: Should go to a sheet, not a a full-screen page. + // Blocked on https://github.com/flutter/flutter/issues/42560. + Event? newEvent = await Navigator.of(context).push( + CupertinoPageRoute( + builder: (_) => EventEditor( + event: Event(title: 'New event'), + isNew: true, + ), + ), + ); + + if (newEvent != null) { + events.add(newEvent); + } + }, + ), + ), + SliverList.list( + children: [ + for (Period p in Period.values) + CupertinoListSection( + header: Text( + p.name.toUpperCase(), + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + children: [ + for (Event e in events.sorted(p)) + EventRow( + event: e, + onTap: () async { + Event? updatedEvent = await Navigator.of(context) + .push( + CupertinoPageRoute( + builder: (_) => EventEditor( + event: e.copy(), + isNew: false, + ), + ), + ); + if (updatedEvent == null) { + // The editor passes back null when it deleted + // the element. + events.delete(e); + } else { + events.update(e, updatedEvent); + } + }, + ), + ], + ), + ], + ), + ], + ), + ); + }, + ); + } +} diff --git a/date_planner/lib/event_row.dart b/date_planner/lib/event_row.dart new file mode 100644 index 0000000..1425693 --- /dev/null +++ b/date_planner/lib/event_row.dart @@ -0,0 +1,44 @@ +// Copyright 2024 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; + +import 'event.dart'; + +class EventRow extends StatelessWidget { + const EventRow({super.key, required this.event, this.onTap}); + + final Event event; + final FutureOr Function()? onTap; + + @override + Widget build(BuildContext context) { + // TODO(mit-mit): The corners of the tiles should be rounded. + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: CupertinoListTile( + title: Text( + event.title, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: Text(event.dateFormated), + leading: Icon(event.icon, size: 28, color: event.color.color), + trailing: Row( + children: [ + event.isComplete + ? const Icon(CupertinoIcons.check_mark) + : Text( + '${event.remainingTaskCount}', + style: const TextStyle(color: CupertinoColors.systemGrey), + ), + const CupertinoListTileChevron(), + ], + ), + onTap: onTap, + ), + ); + } +} diff --git a/date_planner/lib/event_symbol.dart b/date_planner/lib/event_symbol.dart new file mode 100644 index 0000000..81a6372 --- /dev/null +++ b/date_planner/lib/event_symbol.dart @@ -0,0 +1,58 @@ +// Copyright 2024 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; + +// TODO(mit-mit): Update when missing icons are added. +// https://github.com/flutter/flutter/issues/82634 +final eventSymbols = [ + CupertinoIcons.house_fill, + CupertinoIcons.ticket_fill, + CupertinoIcons.gamecontroller_fill, + //CupertinoIcons.theatermasks_fill, + //CupertinoIcons.ladybug_fill, + //CupertinoIcons.books.vertical_fill, + //CupertinoIcons.moon.zzz_fill, + CupertinoIcons.umbrella_fill, + //CupertinoIcons.paintbrush.pointed_fill, + //CupertinoIcons.leaf_fill, + //CupertinoIcons.globe.americas_fill, + CupertinoIcons.clock_fill, + //CupertinoIcons.building.2_fill, + CupertinoIcons.gift_fill, + //CupertinoIcons.graduationcap_fill, + //CupertinoIcons.heart.rectangle_fill, + //CupertinoIcons.phone.bubble.left_fill, + //CupertinoIcons.cloud.rain_fill, + //CupertinoIcons.building.columns_fill, + //CupertinoIcons.mic.circle_fill, + //CupertinoIcons.comb_fill, + //CupertinoIcons.person.3_fill, + CupertinoIcons.bell_fill, + CupertinoIcons.hammer_fill, + CupertinoIcons.star_fill, + //CupertinoIcons.crown_fill, + CupertinoIcons.briefcase_fill, + //CupertinoIcons.speaker.wave.3_fill, + //CupertinoIcons.tshirt_fill, + CupertinoIcons.tag_fill, + CupertinoIcons.airplane, + //CupertinoIcons.pawprint_fill, + //CupertinoIcons.case_fill, + CupertinoIcons.creditcard_fill, + //CupertinoIcons.infinity.circle_fill, + //CupertinoIcons.dice_fill, + CupertinoIcons.heart_fill, + CupertinoIcons.camera_fill, + //CupertinoIcons.bicycle, + //CupertinoIcons.radio_fill, + CupertinoIcons.car_fill, + CupertinoIcons.flag_fill, + CupertinoIcons.map_fill, + //CupertinoIcons.figure.wave, + //CupertinoIcons.mappin.and.ellipse, + //CupertinoIcons.facemask_fill, + CupertinoIcons.eyeglasses, + CupertinoIcons.tram_fill, +]; diff --git a/date_planner/lib/event_task.dart b/date_planner/lib/event_task.dart new file mode 100644 index 0000000..d15b724 --- /dev/null +++ b/date_planner/lib/event_task.dart @@ -0,0 +1,14 @@ +// Copyright 2024 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +class EventTask { + String text; + bool isCompleted; + + EventTask({required this.text, this.isCompleted = false}); + + static List buildList(List taskDescriptions) => [ + for (var task in taskDescriptions) EventTask(text: task), + ]; +} diff --git a/date_planner/lib/main.dart b/date_planner/lib/main.dart new file mode 100644 index 0000000..0f406d0 --- /dev/null +++ b/date_planner/lib/main.dart @@ -0,0 +1,32 @@ +// Copyright 2024 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; +import 'package:provider/provider.dart'; + +import 'event_data.dart'; +import 'event_list.dart'; + +void main() { + runApp( + ChangeNotifierProvider( + create: (context) => EventData(), + child: const DatePlannerApp(), + ), + ); +} + +CupertinoThemeData cupertinoLight = const CupertinoThemeData( + brightness: Brightness.light, + primaryColor: CupertinoColors.activeBlue, +); + +class DatePlannerApp extends StatelessWidget { + const DatePlannerApp({super.key}); + + @override + Widget build(BuildContext context) { + return CupertinoApp(home: const EventList(), theme: cupertinoLight); + } +} diff --git a/date_planner/lib/symbol_editor.dart b/date_planner/lib/symbol_editor.dart new file mode 100644 index 0000000..a2299ae --- /dev/null +++ b/date_planner/lib/symbol_editor.dart @@ -0,0 +1,101 @@ +// Copyright 2024 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; + +import 'color_options.dart'; +import 'event_symbol.dart'; + +class SymbolEditor extends StatefulWidget { + final IconData icon; + final ColorOptions color; + + const SymbolEditor(this.icon, this.color, {super.key}); + + @override + State createState() => _SymbolEditorState(); +} + +class _SymbolEditorState extends State { + late IconData _currentIcon = widget.icon; + late ColorOptions _currentColor = widget.color; + _SymbolEditorState(); + + @override + Widget build(BuildContext context) { + // TODO(mit-mit): Should use a Sheet + // https://github.com/flutter/flutter/issues/42560 + return CupertinoPageScaffold( + backgroundColor: CupertinoColors.white, + child: SafeArea( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Row( + children: [ + const Spacer(), + CupertinoButton( + padding: EdgeInsets.zero, + child: const Text('Done'), + onPressed: () => + Navigator.pop(context, (_currentIcon, _currentColor)), + ), + ], + ), + const SizedBox(height: 16), + Icon(_currentIcon, size: 48, color: _currentColor.color), + const SizedBox(height: 32), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + for (ColorOptions color in ColorOptions.values) + // TODO(mit-mit): Circles should be bigger and have less padding between them. + CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + child: Icon( + CupertinoIcons.circle_fill, + color: color.color, + ), + onPressed: () { + setState(() { + _currentColor = color; + }); + }, + ), + ], + ), + const SizedBox(height: 16), + // TODO(mit-mit): File issue for missing Cupertino Divider widget. + // Should have something similar to the Material devider. + // https://api.flutter.dev/flutter/material/Divider-class.html + const Text('. . . . . . . . . . . . . . . '), + const SizedBox(height: 16), + Expanded( + child: GridView.count( + primary: false, + crossAxisCount: 6, + mainAxisSpacing: 10, + children: [ + for (var icon in eventSymbols) + CupertinoButton( + padding: EdgeInsets.zero, + child: Icon(icon, size: 32), + onPressed: () { + setState(() { + _currentIcon = icon; + }); + }, + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/date_planner/lib/task_row.dart b/date_planner/lib/task_row.dart new file mode 100644 index 0000000..4c817ba --- /dev/null +++ b/date_planner/lib/task_row.dart @@ -0,0 +1,59 @@ +// Copyright 2024 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/cupertino.dart'; + +import 'event_task.dart'; + +class TaskRow extends StatefulWidget { + final EventTask task; + final bool isEditing; + const TaskRow({super.key, required this.task, required this.isEditing}); + + @override + State createState() => _TaskRowState(); +} + +class _TaskRowState extends State { + final _taskText = TextEditingController(); + + @override + void initState() { + _taskText.text = widget.task.text; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Row( + children: [ + CupertinoButton( + onPressed: widget.isEditing + ? () { + setState(() { + widget.task.isCompleted = !widget.task.isCompleted; + }); + } + : null, + child: Icon( + widget.task.isCompleted + ? CupertinoIcons.checkmark_circle_fill + : CupertinoIcons.circle, + color: CupertinoColors.black, + ), + ), + Expanded( + child: widget.isEditing + ? CupertinoTextField( + decoration: null, + padding: EdgeInsets.zero, + controller: _taskText, + onChanged: (value) => widget.task.text = value, + ) + : Text(widget.task.text), + ), + ], + ); + } +} diff --git a/date_planner/pubspec.yaml b/date_planner/pubspec.yaml new file mode 100644 index 0000000..b06a204 --- /dev/null +++ b/date_planner/pubspec.yaml @@ -0,0 +1,21 @@ +name: date_planner +description: "An iOS-style date planner." +publish_to: 'none' # Remove this line if you wish to publish to pub.dev +version: 0.1.0 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + cupertino_icons: ^1.0.8 + flutter: + sdk: flutter + intl: + provider: + uuid: + +dev_dependencies: + analysis_defaults: + path: ../analysis_defaults + flutter_lints: ^6.0.0 diff --git a/desktop_photo_search/README.md b/desktop_photo_search/README.md new file mode 100644 index 0000000..9825d37 --- /dev/null +++ b/desktop_photo_search/README.md @@ -0,0 +1,15 @@ +# Photo Search app + +This desktop application enables you to search +[Unsplash](https://unsplash.com/) for photographs that interest you. +To use it, you need to add an **Access Key** from +[Unsplash API](https://unsplash.com/developers) to +`lib/unsplash_access_key.dart`. + +This is the Photo Search app, built out with two different widget sets: + + - [Material](material) shows the Photo Search app built with [Material widgets][] + - [Fluent UI](fluent_ui) shows the Photo Search app built with [Fluent UI][] widgets + +[Fluent UI]: https://pub.dev/packages/fluent_ui +[Material widgets]: https://docs.flutter.dev/development/ui/widgets/material diff --git a/desktop_photo_search/codelab_rebuild.yaml b/desktop_photo_search/codelab_rebuild.yaml new file mode 100644 index 0000000..1c19500 --- /dev/null +++ b/desktop_photo_search/codelab_rebuild.yaml @@ -0,0 +1,115 @@ +# Run with tooling from https://github.com/flutter/codelabs/tree/main/tooling/codelab_rebuild +name: Desktop Photo Search rebuild script +steps: + - name: Remove fluent_ui runners + path: fluent_ui + rmdirs: + - macos + - linux + - windows + - name: Flutter recreate + path: fluent_ui + flutter: create --platforms windows,linux,macos --project-name desktop_photo_search . + - name: Replace fluent_ui/macos/Runner/DebugProfile.entitlements + path: fluent_ui/macos/Runner/DebugProfile.entitlements + patch-u: | + --- b/desktop_photo_search/fluent_ui/macos/Runner/DebugProfile.entitlements + +++ a/desktop_photo_search/fluent_ui/macos/Runner/DebugProfile.entitlements + @@ -6,6 +6,10 @@ + + com.apple.security.cs.allow-jit + + + com.apple.security.files.user-selected.read-write + + + + com.apple.security.network.client + + + com.apple.security.network.server + + + - name: Patch fluent_ui/macos/Runner/Release.entitlements + path: fluent_ui/macos/Runner/Release.entitlements + patch-u: | + --- b/desktop_photo_search/fluent_ui/macos/Runner/Release.entitlements + +++ a/desktop_photo_search/fluent_ui/macos/Runner/Release.entitlements + @@ -4,5 +4,9 @@ + + com.apple.security.app-sandbox + + + com.apple.security.files.user-selected.read-write + + + + com.apple.security.network.client + + + + + - name: Flutter upgrade + path: fluent_ui + flutter: pub upgrade --major-versions + - name: Flutter build macOS + path: fluent_ui + flutter: build macos + - name: Remove material runners + path: material + rmdirs: + - macos + - linux + - windows + - name: Flutter recreate + path: material + flutter: create --platforms windows,linux,macos --project-name desktop_photo_search . + - name: Replace material/macos/Runner/DebugProfile.entitlements + path: material/macos/Runner/DebugProfile.entitlements + patch-u: | + --- b/desktop_photo_search/material/macos/Runner/DebugProfile.entitlements + +++ a/desktop_photo_search/material/macos/Runner/DebugProfile.entitlements + @@ -6,6 +6,10 @@ + + com.apple.security.cs.allow-jit + + + com.apple.security.files.user-selected.read-write + + + + com.apple.security.network.client + + + com.apple.security.network.server + + + - name: Replace material/macos/Runner/Release.entitlements + path: material/macos/Runner/Release.entitlements + patch-u: | + --- b/desktop_photo_search/material/macos/Runner/Release.entitlements + +++ a/desktop_photo_search/material/macos/Runner/Release.entitlements + @@ -4,5 +4,9 @@ + + com.apple.security.app-sandbox + + + com.apple.security.files.user-selected.read-write + + + + com.apple.security.network.client + + + + + - name: Patch material/macos/Runner/Base.lproj/MainMenu.xib + path: material/macos/Runner/Base.lproj/MainMenu.xib + patch-u: | + --- b/desktop_photo_search/material/macos/Runner/Base.lproj/MainMenu.xib + +++ a/desktop_photo_search/material/macos/Runner/Base.lproj/MainMenu.xib + @@ -330,10 +330,11 @@ + + + + - + - + + + + + + + + - + + + + + + - name: Flutter upgrade + path: material + flutter: pub upgrade --major-versions + - name: Flutter build macOS + path: material + flutter: build macos diff --git a/desktop_photo_search/fluent_ui/.gitignore b/desktop_photo_search/fluent_ui/.gitignore new file mode 100644 index 0000000..0fa6b67 --- /dev/null +++ b/desktop_photo_search/fluent_ui/.gitignore @@ -0,0 +1,46 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/desktop_photo_search/fluent_ui/.metadata b/desktop_photo_search/fluent_ui/.metadata new file mode 100644 index 0000000..16c0689 --- /dev/null +++ b/desktop_photo_search/fluent_ui/.metadata @@ -0,0 +1,36 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "db7ef5bf9f59442b0e200a90587e8fa5e0c6336a" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: linux + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: macos + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: windows + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/desktop_photo_search/fluent_ui/README.md b/desktop_photo_search/fluent_ui/README.md new file mode 100644 index 0000000..5259335 --- /dev/null +++ b/desktop_photo_search/fluent_ui/README.md @@ -0,0 +1,66 @@ +# Photo Search app + +This desktop application enables you to search +[Unsplash](https://unsplash.com/) for photographs that interest you. +To use it, you need to add an **Access Key** from +[Unsplash API](https://unsplash.com/developers) to +`lib/unsplash_access_key.dart`. + +This sample works on Windows, macOS and Linux. + +## A quick tour of the code + +This Flutter project builds a desktop application. It utilises the following +desktop specific plugins: + + - [fluent_ui] for the Fluent UI widget set + - [file_chooser] to enable the application user to select where to save a photo + from the Unsplash API. + - [menubar] for exposing Image Search functionality through the menu bar. + - [url_launcher] to open external links. + +The Unsplash API client entry point is in the [Unsplash] class, and is built +atop [http], [built_value] and [built_collection] for JSON Rest API access. + +## Deploying to the Microsoft Store + +This sample uses Yehuda Kremer's [MSIX pub package] to bundle up the Windows +release build for distribution to the [Microsoft Store]. Microsoft maintains +a [plethora of documentation][ms_store_publishing_doc] on deploying to the +Microsoft Store. + +See the `msix_config` stanza in this sample's `pubspec.yaml` for an +example configuration. Make sure the `display_name`, `publisher_display_name`, +`publisher` and `identity_name` attributes match the settings in your +Microsoft Partner Center application submission. + +## macOS Network and File entitlements + +To access the network, macOS requires applications enable the +[com.apple.security.network.client entitlement][macOS-client]. For this +sample, this entitlement is required to access the Unsplash API. + +Likewise, to save a Photo to the local file system using the `file_chooser` plugin requires the +[com.apple.security.files.user-selected.read-write entitlement][macOS-read-write]. + +Please see [macOS Signing and Security][macOS-security] for more detail. + + +[Unsplash]: lib/src/unsplash/unsplash.dart + +[built_collection]: https://pub.dev/packages/built_collection +[built_value]: https://pub.dev/packages/built_value +[file_chooser]: https://github.com/google/flutter-desktop-embedding/tree/master/plugins/file_chooser +[fluent_ui]: https://pub.dev/packages/fluent_ui +[flutter_channels]: https://github.com/flutter/flutter/blob/master/docs/releases/Flutter-build-release-channels.md +[http]: https://pub.dev/packages/http +[macOS-client]: https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_network_client +[macOS-read-write]: https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_files_user-selected_read-write +[macOS-security]: https://github.com/google/flutter-desktop-embedding/blob/master/macOS-Security.md +[menubar]: https://github.com/google/flutter-desktop-embedding/tree/master/plugins/menubar +[setup documentation]: https://flutter.dev/desktop#set-up +[url_launcher]: https://pub.dev/packages/url_launcher + +[MSIX pub package]: https://pub.dev/packages/msix +[Microsoft Store]: https://www.microsoft.com/en-au/p/flutter-desktop-photo-search/9nh719dxcpj4 +[ms_store_publishing_doc]: https://docs.microsoft.com/en-us/windows/uwp/publish/ diff --git a/desktop_photo_search/fluent_ui/analysis_options.yaml b/desktop_photo_search/fluent_ui/analysis_options.yaml new file mode 100644 index 0000000..511bdba --- /dev/null +++ b/desktop_photo_search/fluent_ui/analysis_options.yaml @@ -0,0 +1,5 @@ +include: ../../analysis_options.yaml + +linter: + rules: + sort_pub_dependencies: true diff --git a/desktop_photo_search/fluent_ui/lib/main.dart b/desktop_photo_search/fluent_ui/lib/main.dart new file mode 100644 index 0000000..9fcb26d --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/main.dart @@ -0,0 +1,119 @@ +// 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. + +import 'dart:io'; + +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter/services.dart'; +import 'package:logging/logging.dart'; +import 'package:menubar/menubar.dart' as menubar; +import 'package:provider/provider.dart'; + +import 'src/model/photo_search_model.dart'; +import 'src/unsplash/unsplash.dart'; +import 'src/widgets/photo_search_dialog.dart'; +import 'src/widgets/policy_dialog.dart'; +import 'src/widgets/unsplash_notice.dart'; +import 'src/widgets/unsplash_search_content.dart'; +import 'unsplash_access_key.dart'; + +void main() { + Logger.root.level = Level.ALL; + Logger.root.onRecord.listen((rec) { + // ignore: avoid_print + print( + '${rec.loggerName} ${rec.level.name}: ${rec.time}: ${rec.message}', + ); + }); + + if (unsplashAccessKey.isEmpty) { + Logger('main').severe( + 'Unsplash Access Key is required. ' + 'Please add to `lib/unsplash_access_key.dart`.', + ); + exit(1); + } + + // Removed setupWindow() call + + runApp( + ChangeNotifierProvider( + create: (context) => + PhotoSearchModel(Unsplash(accessKey: unsplashAccessKey)), + child: const UnsplashSearchApp(), + ), + ); +} + +class UnsplashSearchApp extends StatelessWidget { + const UnsplashSearchApp({super.key}); + + @override + Widget build(BuildContext context) { + return const FluentApp( + title: 'Photo Search', + home: UnsplashHomePage(title: 'Photo Search'), + ); + } +} + +class UnsplashHomePage extends StatelessWidget { + const UnsplashHomePage({required this.title, super.key}); + final String title; + + @override + Widget build(BuildContext context) { + final photoSearchModel = Provider.of(context); + menubar.setApplicationMenu([ + menubar.NativeSubmenu( + label: 'Search', + children: [ + menubar.NativeMenuItem( + label: 'Search…', + onSelected: () { + showDialog( + context: context, + builder: (context) => PhotoSearchDialog( + callback: photoSearchModel.addSearch, + ), + ); + }, + ), + if (!Platform.isMacOS) + menubar.NativeMenuItem( + label: 'Quit', + onSelected: () { + SystemNavigator.pop(); + }, + ), + ], + ), + menubar.NativeSubmenu( + label: 'About', + children: [ + menubar.NativeMenuItem( + label: 'About', + onSelected: () { + showDialog( + context: context, + builder: (context) => const PolicyDialog(), + ); + }, + ), + ], + ), + ]); + + return UnsplashNotice( + child: Container( + color: Colors.white, + child: photoSearchModel.entries.isNotEmpty + ? const UnsplashSearchContent() + : const Center( + child: Text('Search for Photos using the Search menu'), + ), + ), + ); + } +} diff --git a/desktop_photo_search/fluent_ui/lib/src/model/photo_search_model.dart b/desktop_photo_search/fluent_ui/lib/src/model/photo_search_model.dart new file mode 100644 index 0000000..c3b1878 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/model/photo_search_model.dart @@ -0,0 +1,50 @@ +// 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. + +import 'package:flutter/foundation.dart'; + +import '../unsplash/photo.dart'; +import '../unsplash/unsplash.dart'; +import 'search.dart'; + +class SearchEntry { + const SearchEntry(this.query, this.photos, this.model); + final String query; + final List photos; + final PhotoSearchModel model; +} + +class PhotoSearchModel extends ChangeNotifier { + PhotoSearchModel(this._client); + final Unsplash _client; + + List get entries => List.unmodifiable(_entries); + final List _entries = []; + + Photo? get selectedPhoto => _selectedPhoto; + set selectedPhoto(Photo? photo) { + _selectedPhoto = photo; + notifyListeners(); + } + + Photo? _selectedPhoto; + + Future addSearch(String query) async { + final result = await _client.searchPhotos( + query: query, + orientation: SearchPhotosOrientation.portrait, + ); + final search = Search((s) { + s + ..query = query + ..results.addAll(result!.results); + }); + + _entries.add(SearchEntry(query, search.results.toList(), this)); + notifyListeners(); + } + + Future download({required Photo photo}) => + _client.download(photo); +} diff --git a/desktop_photo_search/fluent_ui/lib/src/model/search.dart b/desktop_photo_search/fluent_ui/lib/src/model/search.dart new file mode 100644 index 0000000..4b85be8 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/model/search.dart @@ -0,0 +1,38 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_collection/built_collection.dart'; +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; +import '../unsplash/photo.dart'; + +part 'search.g.dart'; + +abstract class Search implements Built { + factory Search([void Function(SearchBuilder)? updates]) = _$Search; + Search._(); + + @BuiltValueField(wireName: 'query') + String get query; + + @BuiltValueField(wireName: 'results') + BuiltList get results; + + String toJson() { + return json.encode(serializers.serializeWith(Search.serializer, this)); + } + + static Search? fromJson(String jsonString) { + return serializers.deserializeWith( + Search.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => _$searchSerializer; +} diff --git a/desktop_photo_search/fluent_ui/lib/src/model/search.g.dart b/desktop_photo_search/fluent_ui/lib/src/model/search.g.dart new file mode 100644 index 0000000..578c88f --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/model/search.g.dart @@ -0,0 +1,200 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'search.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$searchSerializer = new _$SearchSerializer(); + +class _$SearchSerializer implements StructuredSerializer { + @override + final Iterable types = const [Search, _$Search]; + @override + final String wireName = 'Search'; + + @override + Iterable serialize( + Serializers serializers, + Search object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = [ + 'query', + serializers.serialize( + object.query, + specifiedType: const FullType(String), + ), + 'results', + serializers.serialize( + object.results, + specifiedType: const FullType(BuiltList, const [ + const FullType(Photo), + ]), + ), + ]; + + return result; + } + + @override + Search deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new SearchBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'query': + result.query = + serializers.deserialize( + value, + specifiedType: const FullType(String), + )! + as String; + break; + case 'results': + result.results.replace( + serializers.deserialize( + value, + specifiedType: const FullType(BuiltList, const [ + const FullType(Photo), + ]), + )! + as BuiltList, + ); + break; + } + } + + return result.build(); + } +} + +class _$Search extends Search { + @override + final String query; + @override + final BuiltList results; + + factory _$Search([void Function(SearchBuilder)? updates]) => + (new SearchBuilder()..update(updates))._build(); + + _$Search._({required this.query, required this.results}) : super._() { + BuiltValueNullFieldError.checkNotNull(query, r'Search', 'query'); + BuiltValueNullFieldError.checkNotNull(results, r'Search', 'results'); + } + + @override + Search rebuild(void Function(SearchBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + SearchBuilder toBuilder() => new SearchBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Search && query == other.query && results == other.results; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, query.hashCode); + _$hash = $jc(_$hash, results.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'Search') + ..add('query', query) + ..add('results', results)) + .toString(); + } +} + +class SearchBuilder implements Builder { + _$Search? _$v; + + String? _query; + String? get query => _$this._query; + set query(String? query) => _$this._query = query; + + ListBuilder? _results; + ListBuilder get results => + _$this._results ??= new ListBuilder(); + set results(ListBuilder? results) => _$this._results = results; + + SearchBuilder(); + + SearchBuilder get _$this { + final $v = _$v; + if ($v != null) { + _query = $v.query; + _results = $v.results.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(Search other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$Search; + } + + @override + void update(void Function(SearchBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + Search build() => _build(); + + _$Search _build() { + _$Search _$result; + try { + _$result = + _$v ?? + new _$Search._( + query: BuiltValueNullFieldError.checkNotNull( + query, + r'Search', + 'query', + ), + results: results.build(), + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'results'; + results.build(); + } catch (e) { + throw new BuiltValueNestedFieldError( + r'Search', + _$failedField, + e.toString(), + ); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/fluent_ui/lib/src/serializers.dart b/desktop_photo_search/fluent_ui/lib/src/serializers.dart new file mode 100644 index 0000000..6153a3d --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/serializers.dart @@ -0,0 +1,27 @@ +// 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. + +import 'package:built_collection/built_collection.dart'; +import 'package:built_value/serializer.dart'; +import 'package:built_value/standard_json_plugin.dart'; + +import 'model/search.dart'; +import 'unsplash/api_error.dart'; +import 'unsplash/current_user_collections.dart'; +import 'unsplash/exif.dart'; +import 'unsplash/links.dart'; +import 'unsplash/location.dart'; +import 'unsplash/photo.dart'; +import 'unsplash/position.dart'; +import 'unsplash/search_photos_response.dart'; +import 'unsplash/tags.dart'; +import 'unsplash/urls.dart'; +import 'unsplash/user.dart'; + +part 'serializers.g.dart'; + +//add all of the built value types that require serialization +@SerializersFor([Search, ApiError, Photo, SearchPhotosResponse]) +final Serializers serializers = + (_$serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build(); diff --git a/desktop_photo_search/fluent_ui/lib/src/serializers.g.dart b/desktop_photo_search/fluent_ui/lib/src/serializers.g.dart new file mode 100644 index 0000000..f2d7986 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/serializers.g.dart @@ -0,0 +1,51 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'serializers.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializers _$serializers = + (new Serializers().toBuilder() + ..add(ApiError.serializer) + ..add(CurrentUserCollections.serializer) + ..add(Exif.serializer) + ..add(Links.serializer) + ..add(Location.serializer) + ..add(Photo.serializer) + ..add(Position.serializer) + ..add(Search.serializer) + ..add(SearchPhotosResponse.serializer) + ..add(Tags.serializer) + ..add(Urls.serializer) + ..add(User.serializer) + ..addBuilderFactory( + const FullType(BuiltList, const [const FullType(Photo)]), + () => new ListBuilder(), + ) + ..addBuilderFactory( + const FullType(BuiltList, const [const FullType(Photo)]), + () => new ListBuilder(), + ) + ..addBuilderFactory( + const FullType(BuiltList, const [const FullType(String)]), + () => new ListBuilder(), + ) + ..addBuilderFactory( + const FullType(BuiltList, const [const FullType(Tags)]), + () => new ListBuilder(), + ) + ..addBuilderFactory( + const FullType(BuiltList, const [ + const FullType(CurrentUserCollections), + ]), + () => new ListBuilder(), + )) + .build(); + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/api_error.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/api_error.dart new file mode 100644 index 0000000..e7607c0 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/api_error.dart @@ -0,0 +1,37 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_collection/built_collection.dart'; +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; + +part 'api_error.g.dart'; + +abstract class ApiError implements Built { + factory ApiError([void Function(ApiErrorBuilder)? updates]) = _$ApiError; + + ApiError._(); + + @BuiltValueField(wireName: 'errors') + BuiltList? get errors; + + String toJson() { + return json.encode( + serializers.serializeWith(ApiError.serializer, this), + ); + } + + static ApiError? fromJson(String jsonString) { + return serializers.deserializeWith( + ApiError.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => _$apiErrorSerializer; +} diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/api_error.g.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/api_error.g.dart new file mode 100644 index 0000000..acc2aab --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/api_error.g.dart @@ -0,0 +1,171 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'api_error.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$apiErrorSerializer = new _$ApiErrorSerializer(); + +class _$ApiErrorSerializer implements StructuredSerializer { + @override + final Iterable types = const [ApiError, _$ApiError]; + @override + final String wireName = 'ApiError'; + + @override + Iterable serialize( + Serializers serializers, + ApiError object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = []; + Object? value; + value = object.errors; + if (value != null) { + result + ..add('errors') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(BuiltList, const [ + const FullType(String), + ]), + ), + ); + } + return result; + } + + @override + ApiError deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new ApiErrorBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'errors': + result.errors.replace( + serializers.deserialize( + value, + specifiedType: const FullType(BuiltList, const [ + const FullType(String), + ]), + )! + as BuiltList, + ); + break; + } + } + + return result.build(); + } +} + +class _$ApiError extends ApiError { + @override + final BuiltList? errors; + + factory _$ApiError([void Function(ApiErrorBuilder)? updates]) => + (new ApiErrorBuilder()..update(updates))._build(); + + _$ApiError._({this.errors}) : super._(); + + @override + ApiError rebuild(void Function(ApiErrorBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + ApiErrorBuilder toBuilder() => new ApiErrorBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is ApiError && errors == other.errors; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, errors.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper( + r'ApiError', + )..add('errors', errors)).toString(); + } +} + +class ApiErrorBuilder implements Builder { + _$ApiError? _$v; + + ListBuilder? _errors; + ListBuilder get errors => + _$this._errors ??= new ListBuilder(); + set errors(ListBuilder? errors) => _$this._errors = errors; + + ApiErrorBuilder(); + + ApiErrorBuilder get _$this { + final $v = _$v; + if ($v != null) { + _errors = $v.errors?.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(ApiError other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$ApiError; + } + + @override + void update(void Function(ApiErrorBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + ApiError build() => _build(); + + _$ApiError _build() { + _$ApiError _$result; + try { + _$result = _$v ?? new _$ApiError._(errors: _errors?.build()); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'errors'; + _errors?.build(); + } catch (e) { + throw new BuiltValueNestedFieldError( + r'ApiError', + _$failedField, + e.toString(), + ); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/current_user_collections.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/current_user_collections.dart new file mode 100644 index 0000000..610ae21 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/current_user_collections.dart @@ -0,0 +1,49 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; + +part 'current_user_collections.g.dart'; + +abstract class CurrentUserCollections + implements Built { + factory CurrentUserCollections([ + void Function(CurrentUserCollectionsBuilder)? updates, + ]) = _$CurrentUserCollections; + + CurrentUserCollections._(); + + @BuiltValueField(wireName: 'id') + int get id; + + @BuiltValueField(wireName: 'title') + String? get title; + + @BuiltValueField(wireName: 'published_at') + String? get publishedAt; + + @BuiltValueField(wireName: 'updated_at') + String? get updatedAt; + + String toJson() { + return json.encode( + serializers.serializeWith(CurrentUserCollections.serializer, this), + ); + } + + static CurrentUserCollections? fromJson(String jsonString) { + return serializers.deserializeWith( + CurrentUserCollections.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => + _$currentUserCollectionsSerializer; +} diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/current_user_collections.g.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/current_user_collections.g.dart new file mode 100644 index 0000000..f37c6c9 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/current_user_collections.g.dart @@ -0,0 +1,260 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'current_user_collections.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$currentUserCollectionsSerializer = + new _$CurrentUserCollectionsSerializer(); + +class _$CurrentUserCollectionsSerializer + implements StructuredSerializer { + @override + final Iterable types = const [ + CurrentUserCollections, + _$CurrentUserCollections, + ]; + @override + final String wireName = 'CurrentUserCollections'; + + @override + Iterable serialize( + Serializers serializers, + CurrentUserCollections object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = [ + 'id', + serializers.serialize(object.id, specifiedType: const FullType(int)), + ]; + Object? value; + value = object.title; + if (value != null) { + result + ..add('title') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.publishedAt; + if (value != null) { + result + ..add('published_at') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.updatedAt; + if (value != null) { + result + ..add('updated_at') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + return result; + } + + @override + CurrentUserCollections deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new CurrentUserCollectionsBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'id': + result.id = + serializers.deserialize( + value, + specifiedType: const FullType(int), + )! + as int; + break; + case 'title': + result.title = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'published_at': + result.publishedAt = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'updated_at': + result.updatedAt = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + } + } + + return result.build(); + } +} + +class _$CurrentUserCollections extends CurrentUserCollections { + @override + final int id; + @override + final String? title; + @override + final String? publishedAt; + @override + final String? updatedAt; + + factory _$CurrentUserCollections([ + void Function(CurrentUserCollectionsBuilder)? updates, + ]) => (new CurrentUserCollectionsBuilder()..update(updates))._build(); + + _$CurrentUserCollections._({ + required this.id, + this.title, + this.publishedAt, + this.updatedAt, + }) : super._() { + BuiltValueNullFieldError.checkNotNull( + id, + r'CurrentUserCollections', + 'id', + ); + } + + @override + CurrentUserCollections rebuild( + void Function(CurrentUserCollectionsBuilder) updates, + ) => (toBuilder()..update(updates)).build(); + + @override + CurrentUserCollectionsBuilder toBuilder() => + new CurrentUserCollectionsBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is CurrentUserCollections && + id == other.id && + title == other.title && + publishedAt == other.publishedAt && + updatedAt == other.updatedAt; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, id.hashCode); + _$hash = $jc(_$hash, title.hashCode); + _$hash = $jc(_$hash, publishedAt.hashCode); + _$hash = $jc(_$hash, updatedAt.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'CurrentUserCollections') + ..add('id', id) + ..add('title', title) + ..add('publishedAt', publishedAt) + ..add('updatedAt', updatedAt)) + .toString(); + } +} + +class CurrentUserCollectionsBuilder + implements Builder { + _$CurrentUserCollections? _$v; + + int? _id; + int? get id => _$this._id; + set id(int? id) => _$this._id = id; + + String? _title; + String? get title => _$this._title; + set title(String? title) => _$this._title = title; + + String? _publishedAt; + String? get publishedAt => _$this._publishedAt; + set publishedAt(String? publishedAt) => _$this._publishedAt = publishedAt; + + String? _updatedAt; + String? get updatedAt => _$this._updatedAt; + set updatedAt(String? updatedAt) => _$this._updatedAt = updatedAt; + + CurrentUserCollectionsBuilder(); + + CurrentUserCollectionsBuilder get _$this { + final $v = _$v; + if ($v != null) { + _id = $v.id; + _title = $v.title; + _publishedAt = $v.publishedAt; + _updatedAt = $v.updatedAt; + _$v = null; + } + return this; + } + + @override + void replace(CurrentUserCollections other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$CurrentUserCollections; + } + + @override + void update(void Function(CurrentUserCollectionsBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + CurrentUserCollections build() => _build(); + + _$CurrentUserCollections _build() { + final _$result = + _$v ?? + new _$CurrentUserCollections._( + id: BuiltValueNullFieldError.checkNotNull( + id, + r'CurrentUserCollections', + 'id', + ), + title: title, + publishedAt: publishedAt, + updatedAt: updatedAt, + ); + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/exif.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/exif.dart new file mode 100644 index 0000000..9eb505d --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/exif.dart @@ -0,0 +1,49 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; + +part 'exif.g.dart'; + +abstract class Exif implements Built { + factory Exif([void Function(ExifBuilder)? updates]) = _$Exif; + + Exif._(); + + @BuiltValueField(wireName: 'make') + String? get make; + + @BuiltValueField(wireName: 'model') + String? get model; + + @BuiltValueField(wireName: 'exposure_time') + String? get exposureTime; + + @BuiltValueField(wireName: 'aperture') + String? get aperture; + + @BuiltValueField(wireName: 'focal_length') + String? get focalLength; + + @BuiltValueField(wireName: 'iso') + int? get iso; + + String toJson() { + return json.encode(serializers.serializeWith(Exif.serializer, this)); + } + + static Exif? fromJson(String jsonString) { + return serializers.deserializeWith( + Exif.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => _$exifSerializer; +} diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/exif.g.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/exif.g.dart new file mode 100644 index 0000000..1896c86 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/exif.g.dart @@ -0,0 +1,309 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'exif.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$exifSerializer = new _$ExifSerializer(); + +class _$ExifSerializer implements StructuredSerializer { + @override + final Iterable types = const [Exif, _$Exif]; + @override + final String wireName = 'Exif'; + + @override + Iterable serialize( + Serializers serializers, + Exif object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = []; + Object? value; + value = object.make; + if (value != null) { + result + ..add('make') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.model; + if (value != null) { + result + ..add('model') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.exposureTime; + if (value != null) { + result + ..add('exposure_time') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.aperture; + if (value != null) { + result + ..add('aperture') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.focalLength; + if (value != null) { + result + ..add('focal_length') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.iso; + if (value != null) { + result + ..add('iso') + ..add( + serializers.serialize(value, specifiedType: const FullType(int)), + ); + } + return result; + } + + @override + Exif deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new ExifBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'make': + result.make = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'model': + result.model = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'exposure_time': + result.exposureTime = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'aperture': + result.aperture = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'focal_length': + result.focalLength = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'iso': + result.iso = + serializers.deserialize( + value, + specifiedType: const FullType(int), + ) + as int?; + break; + } + } + + return result.build(); + } +} + +class _$Exif extends Exif { + @override + final String? make; + @override + final String? model; + @override + final String? exposureTime; + @override + final String? aperture; + @override + final String? focalLength; + @override + final int? iso; + + factory _$Exif([void Function(ExifBuilder)? updates]) => + (new ExifBuilder()..update(updates))._build(); + + _$Exif._({ + this.make, + this.model, + this.exposureTime, + this.aperture, + this.focalLength, + this.iso, + }) : super._(); + + @override + Exif rebuild(void Function(ExifBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + ExifBuilder toBuilder() => new ExifBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Exif && + make == other.make && + model == other.model && + exposureTime == other.exposureTime && + aperture == other.aperture && + focalLength == other.focalLength && + iso == other.iso; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, make.hashCode); + _$hash = $jc(_$hash, model.hashCode); + _$hash = $jc(_$hash, exposureTime.hashCode); + _$hash = $jc(_$hash, aperture.hashCode); + _$hash = $jc(_$hash, focalLength.hashCode); + _$hash = $jc(_$hash, iso.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'Exif') + ..add('make', make) + ..add('model', model) + ..add('exposureTime', exposureTime) + ..add('aperture', aperture) + ..add('focalLength', focalLength) + ..add('iso', iso)) + .toString(); + } +} + +class ExifBuilder implements Builder { + _$Exif? _$v; + + String? _make; + String? get make => _$this._make; + set make(String? make) => _$this._make = make; + + String? _model; + String? get model => _$this._model; + set model(String? model) => _$this._model = model; + + String? _exposureTime; + String? get exposureTime => _$this._exposureTime; + set exposureTime(String? exposureTime) => + _$this._exposureTime = exposureTime; + + String? _aperture; + String? get aperture => _$this._aperture; + set aperture(String? aperture) => _$this._aperture = aperture; + + String? _focalLength; + String? get focalLength => _$this._focalLength; + set focalLength(String? focalLength) => _$this._focalLength = focalLength; + + int? _iso; + int? get iso => _$this._iso; + set iso(int? iso) => _$this._iso = iso; + + ExifBuilder(); + + ExifBuilder get _$this { + final $v = _$v; + if ($v != null) { + _make = $v.make; + _model = $v.model; + _exposureTime = $v.exposureTime; + _aperture = $v.aperture; + _focalLength = $v.focalLength; + _iso = $v.iso; + _$v = null; + } + return this; + } + + @override + void replace(Exif other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$Exif; + } + + @override + void update(void Function(ExifBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + Exif build() => _build(); + + _$Exif _build() { + final _$result = + _$v ?? + new _$Exif._( + make: make, + model: model, + exposureTime: exposureTime, + aperture: aperture, + focalLength: focalLength, + iso: iso, + ); + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/links.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/links.dart new file mode 100644 index 0000000..20b1018 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/links.dart @@ -0,0 +1,43 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; + +part 'links.g.dart'; + +abstract class Links implements Built { + factory Links([void Function(LinksBuilder)? updates]) = _$Links; + + Links._(); + + @BuiltValueField(wireName: 'self') + String? get self; + + @BuiltValueField(wireName: 'html') + String? get html; + + @BuiltValueField(wireName: 'download') + String? get download; + + @BuiltValueField(wireName: 'download_location') + String? get downloadLocation; + + String toJson() { + return json.encode(serializers.serializeWith(Links.serializer, this)); + } + + static Links? fromJson(String jsonString) { + return serializers.deserializeWith( + Links.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => _$linksSerializer; +} diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/links.g.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/links.g.dart new file mode 100644 index 0000000..5645e7e --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/links.g.dart @@ -0,0 +1,246 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'links.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$linksSerializer = new _$LinksSerializer(); + +class _$LinksSerializer implements StructuredSerializer { + @override + final Iterable types = const [Links, _$Links]; + @override + final String wireName = 'Links'; + + @override + Iterable serialize( + Serializers serializers, + Links object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = []; + Object? value; + value = object.self; + if (value != null) { + result + ..add('self') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.html; + if (value != null) { + result + ..add('html') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.download; + if (value != null) { + result + ..add('download') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.downloadLocation; + if (value != null) { + result + ..add('download_location') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + return result; + } + + @override + Links deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new LinksBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'self': + result.self = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'html': + result.html = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'download': + result.download = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'download_location': + result.downloadLocation = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + } + } + + return result.build(); + } +} + +class _$Links extends Links { + @override + final String? self; + @override + final String? html; + @override + final String? download; + @override + final String? downloadLocation; + + factory _$Links([void Function(LinksBuilder)? updates]) => + (new LinksBuilder()..update(updates))._build(); + + _$Links._({this.self, this.html, this.download, this.downloadLocation}) + : super._(); + + @override + Links rebuild(void Function(LinksBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + LinksBuilder toBuilder() => new LinksBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Links && + self == other.self && + html == other.html && + download == other.download && + downloadLocation == other.downloadLocation; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, self.hashCode); + _$hash = $jc(_$hash, html.hashCode); + _$hash = $jc(_$hash, download.hashCode); + _$hash = $jc(_$hash, downloadLocation.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'Links') + ..add('self', self) + ..add('html', html) + ..add('download', download) + ..add('downloadLocation', downloadLocation)) + .toString(); + } +} + +class LinksBuilder implements Builder { + _$Links? _$v; + + String? _self; + String? get self => _$this._self; + set self(String? self) => _$this._self = self; + + String? _html; + String? get html => _$this._html; + set html(String? html) => _$this._html = html; + + String? _download; + String? get download => _$this._download; + set download(String? download) => _$this._download = download; + + String? _downloadLocation; + String? get downloadLocation => _$this._downloadLocation; + set downloadLocation(String? downloadLocation) => + _$this._downloadLocation = downloadLocation; + + LinksBuilder(); + + LinksBuilder get _$this { + final $v = _$v; + if ($v != null) { + _self = $v.self; + _html = $v.html; + _download = $v.download; + _downloadLocation = $v.downloadLocation; + _$v = null; + } + return this; + } + + @override + void replace(Links other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$Links; + } + + @override + void update(void Function(LinksBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + Links build() => _build(); + + _$Links _build() { + final _$result = + _$v ?? + new _$Links._( + self: self, + html: html, + download: download, + downloadLocation: downloadLocation, + ); + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/location.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/location.dart new file mode 100644 index 0000000..0a7ebac --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/location.dart @@ -0,0 +1,43 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; +import 'position.dart'; + +part 'location.g.dart'; + +abstract class Location implements Built { + factory Location([void Function(LocationBuilder)? updates]) = _$Location; + + Location._(); + + @BuiltValueField(wireName: 'city') + String? get city; + + @BuiltValueField(wireName: 'country') + String? get country; + + @BuiltValueField(wireName: 'position') + Position? get position; + + String toJson() { + return json.encode( + serializers.serializeWith(Location.serializer, this), + ); + } + + static Location? fromJson(String jsonString) { + return serializers.deserializeWith( + Location.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => _$locationSerializer; +} diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/location.g.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/location.g.dart new file mode 100644 index 0000000..7fe7b1c --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/location.g.dart @@ -0,0 +1,231 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'location.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$locationSerializer = new _$LocationSerializer(); + +class _$LocationSerializer implements StructuredSerializer { + @override + final Iterable types = const [Location, _$Location]; + @override + final String wireName = 'Location'; + + @override + Iterable serialize( + Serializers serializers, + Location object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = []; + Object? value; + value = object.city; + if (value != null) { + result + ..add('city') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.country; + if (value != null) { + result + ..add('country') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.position; + if (value != null) { + result + ..add('position') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(Position), + ), + ); + } + return result; + } + + @override + Location deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new LocationBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'city': + result.city = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'country': + result.country = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'position': + result.position.replace( + serializers.deserialize( + value, + specifiedType: const FullType(Position), + )! + as Position, + ); + break; + } + } + + return result.build(); + } +} + +class _$Location extends Location { + @override + final String? city; + @override + final String? country; + @override + final Position? position; + + factory _$Location([void Function(LocationBuilder)? updates]) => + (new LocationBuilder()..update(updates))._build(); + + _$Location._({this.city, this.country, this.position}) : super._(); + + @override + Location rebuild(void Function(LocationBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + LocationBuilder toBuilder() => new LocationBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Location && + city == other.city && + country == other.country && + position == other.position; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, city.hashCode); + _$hash = $jc(_$hash, country.hashCode); + _$hash = $jc(_$hash, position.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'Location') + ..add('city', city) + ..add('country', country) + ..add('position', position)) + .toString(); + } +} + +class LocationBuilder implements Builder { + _$Location? _$v; + + String? _city; + String? get city => _$this._city; + set city(String? city) => _$this._city = city; + + String? _country; + String? get country => _$this._country; + set country(String? country) => _$this._country = country; + + PositionBuilder? _position; + PositionBuilder get position => _$this._position ??= new PositionBuilder(); + set position(PositionBuilder? position) => _$this._position = position; + + LocationBuilder(); + + LocationBuilder get _$this { + final $v = _$v; + if ($v != null) { + _city = $v.city; + _country = $v.country; + _position = $v.position?.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(Location other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$Location; + } + + @override + void update(void Function(LocationBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + Location build() => _build(); + + _$Location _build() { + _$Location _$result; + try { + _$result = + _$v ?? + new _$Location._( + city: city, + country: country, + position: _position?.build(), + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'position'; + _position?.build(); + } catch (e) { + throw new BuiltValueNestedFieldError( + r'Location', + _$failedField, + e.toString(), + ); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/photo.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/photo.dart new file mode 100644 index 0000000..2e0de56 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/photo.dart @@ -0,0 +1,90 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_collection/built_collection.dart'; +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; +import 'current_user_collections.dart'; +import 'exif.dart'; +import 'links.dart'; +import 'location.dart'; +import 'tags.dart'; +import 'urls.dart'; +import 'user.dart'; + +part 'photo.g.dart'; + +abstract class Photo implements Built { + factory Photo([void Function(PhotoBuilder)? updates]) = _$Photo; + + Photo._(); + + @BuiltValueField(wireName: 'id') + String get id; + + @BuiltValueField(wireName: 'created_at') + String? get createdAt; + + @BuiltValueField(wireName: 'updated_at') + String? get updatedAt; + + @BuiltValueField(wireName: 'width') + int? get width; + + @BuiltValueField(wireName: 'height') + int? get height; + + @BuiltValueField(wireName: 'color') + String? get color; + + @BuiltValueField(wireName: 'downloads') + int? get downloads; + + @BuiltValueField(wireName: 'likes') + int? get likes; + + @BuiltValueField(wireName: 'liked_by_user') + bool? get likedByUser; + + @BuiltValueField(wireName: 'description') + String? get description; + + @BuiltValueField(wireName: 'exif') + Exif? get exif; + + @BuiltValueField(wireName: 'location') + Location? get location; + + @BuiltValueField(wireName: 'tags') + BuiltList? get tags; + + @BuiltValueField(wireName: 'current_user_collections') + BuiltList? get currentUserCollections; + + @BuiltValueField(wireName: 'urls') + Urls? get urls; + + @BuiltValueField(wireName: 'links') + Links? get links; + + @BuiltValueField(wireName: 'user') + User? get user; + + String toJson() { + return json.encode(serializers.serializeWith(Photo.serializer, this)); + } + + static Photo? fromJson(String jsonString) { + return serializers.deserializeWith( + Photo.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => _$photoSerializer; +} diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/photo.g.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/photo.g.dart new file mode 100644 index 0000000..c032a75 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/photo.g.dart @@ -0,0 +1,684 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'photo.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$photoSerializer = new _$PhotoSerializer(); + +class _$PhotoSerializer implements StructuredSerializer { + @override + final Iterable types = const [Photo, _$Photo]; + @override + final String wireName = 'Photo'; + + @override + Iterable serialize( + Serializers serializers, + Photo object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = [ + 'id', + serializers.serialize( + object.id, + specifiedType: const FullType(String), + ), + ]; + Object? value; + value = object.createdAt; + if (value != null) { + result + ..add('created_at') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.updatedAt; + if (value != null) { + result + ..add('updated_at') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.width; + if (value != null) { + result + ..add('width') + ..add( + serializers.serialize(value, specifiedType: const FullType(int)), + ); + } + value = object.height; + if (value != null) { + result + ..add('height') + ..add( + serializers.serialize(value, specifiedType: const FullType(int)), + ); + } + value = object.color; + if (value != null) { + result + ..add('color') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.downloads; + if (value != null) { + result + ..add('downloads') + ..add( + serializers.serialize(value, specifiedType: const FullType(int)), + ); + } + value = object.likes; + if (value != null) { + result + ..add('likes') + ..add( + serializers.serialize(value, specifiedType: const FullType(int)), + ); + } + value = object.likedByUser; + if (value != null) { + result + ..add('liked_by_user') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(bool), + ), + ); + } + value = object.description; + if (value != null) { + result + ..add('description') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.exif; + if (value != null) { + result + ..add('exif') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(Exif), + ), + ); + } + value = object.location; + if (value != null) { + result + ..add('location') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(Location), + ), + ); + } + value = object.tags; + if (value != null) { + result + ..add('tags') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(BuiltList, const [ + const FullType(Tags), + ]), + ), + ); + } + value = object.currentUserCollections; + if (value != null) { + result + ..add('current_user_collections') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(BuiltList, const [ + const FullType(CurrentUserCollections), + ]), + ), + ); + } + value = object.urls; + if (value != null) { + result + ..add('urls') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(Urls), + ), + ); + } + value = object.links; + if (value != null) { + result + ..add('links') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(Links), + ), + ); + } + value = object.user; + if (value != null) { + result + ..add('user') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(User), + ), + ); + } + return result; + } + + @override + Photo deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new PhotoBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'id': + result.id = + serializers.deserialize( + value, + specifiedType: const FullType(String), + )! + as String; + break; + case 'created_at': + result.createdAt = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'updated_at': + result.updatedAt = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'width': + result.width = + serializers.deserialize( + value, + specifiedType: const FullType(int), + ) + as int?; + break; + case 'height': + result.height = + serializers.deserialize( + value, + specifiedType: const FullType(int), + ) + as int?; + break; + case 'color': + result.color = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'downloads': + result.downloads = + serializers.deserialize( + value, + specifiedType: const FullType(int), + ) + as int?; + break; + case 'likes': + result.likes = + serializers.deserialize( + value, + specifiedType: const FullType(int), + ) + as int?; + break; + case 'liked_by_user': + result.likedByUser = + serializers.deserialize( + value, + specifiedType: const FullType(bool), + ) + as bool?; + break; + case 'description': + result.description = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'exif': + result.exif.replace( + serializers.deserialize( + value, + specifiedType: const FullType(Exif), + )! + as Exif, + ); + break; + case 'location': + result.location.replace( + serializers.deserialize( + value, + specifiedType: const FullType(Location), + )! + as Location, + ); + break; + case 'tags': + result.tags.replace( + serializers.deserialize( + value, + specifiedType: const FullType(BuiltList, const [ + const FullType(Tags), + ]), + )! + as BuiltList, + ); + break; + case 'current_user_collections': + result.currentUserCollections.replace( + serializers.deserialize( + value, + specifiedType: const FullType(BuiltList, const [ + const FullType(CurrentUserCollections), + ]), + )! + as BuiltList, + ); + break; + case 'urls': + result.urls.replace( + serializers.deserialize( + value, + specifiedType: const FullType(Urls), + )! + as Urls, + ); + break; + case 'links': + result.links.replace( + serializers.deserialize( + value, + specifiedType: const FullType(Links), + )! + as Links, + ); + break; + case 'user': + result.user.replace( + serializers.deserialize( + value, + specifiedType: const FullType(User), + )! + as User, + ); + break; + } + } + + return result.build(); + } +} + +class _$Photo extends Photo { + @override + final String id; + @override + final String? createdAt; + @override + final String? updatedAt; + @override + final int? width; + @override + final int? height; + @override + final String? color; + @override + final int? downloads; + @override + final int? likes; + @override + final bool? likedByUser; + @override + final String? description; + @override + final Exif? exif; + @override + final Location? location; + @override + final BuiltList? tags; + @override + final BuiltList? currentUserCollections; + @override + final Urls? urls; + @override + final Links? links; + @override + final User? user; + + factory _$Photo([void Function(PhotoBuilder)? updates]) => + (new PhotoBuilder()..update(updates))._build(); + + _$Photo._({ + required this.id, + this.createdAt, + this.updatedAt, + this.width, + this.height, + this.color, + this.downloads, + this.likes, + this.likedByUser, + this.description, + this.exif, + this.location, + this.tags, + this.currentUserCollections, + this.urls, + this.links, + this.user, + }) : super._() { + BuiltValueNullFieldError.checkNotNull(id, r'Photo', 'id'); + } + + @override + Photo rebuild(void Function(PhotoBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + PhotoBuilder toBuilder() => new PhotoBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Photo && + id == other.id && + createdAt == other.createdAt && + updatedAt == other.updatedAt && + width == other.width && + height == other.height && + color == other.color && + downloads == other.downloads && + likes == other.likes && + likedByUser == other.likedByUser && + description == other.description && + exif == other.exif && + location == other.location && + tags == other.tags && + currentUserCollections == other.currentUserCollections && + urls == other.urls && + links == other.links && + user == other.user; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, id.hashCode); + _$hash = $jc(_$hash, createdAt.hashCode); + _$hash = $jc(_$hash, updatedAt.hashCode); + _$hash = $jc(_$hash, width.hashCode); + _$hash = $jc(_$hash, height.hashCode); + _$hash = $jc(_$hash, color.hashCode); + _$hash = $jc(_$hash, downloads.hashCode); + _$hash = $jc(_$hash, likes.hashCode); + _$hash = $jc(_$hash, likedByUser.hashCode); + _$hash = $jc(_$hash, description.hashCode); + _$hash = $jc(_$hash, exif.hashCode); + _$hash = $jc(_$hash, location.hashCode); + _$hash = $jc(_$hash, tags.hashCode); + _$hash = $jc(_$hash, currentUserCollections.hashCode); + _$hash = $jc(_$hash, urls.hashCode); + _$hash = $jc(_$hash, links.hashCode); + _$hash = $jc(_$hash, user.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'Photo') + ..add('id', id) + ..add('createdAt', createdAt) + ..add('updatedAt', updatedAt) + ..add('width', width) + ..add('height', height) + ..add('color', color) + ..add('downloads', downloads) + ..add('likes', likes) + ..add('likedByUser', likedByUser) + ..add('description', description) + ..add('exif', exif) + ..add('location', location) + ..add('tags', tags) + ..add('currentUserCollections', currentUserCollections) + ..add('urls', urls) + ..add('links', links) + ..add('user', user)) + .toString(); + } +} + +class PhotoBuilder implements Builder { + _$Photo? _$v; + + String? _id; + String? get id => _$this._id; + set id(String? id) => _$this._id = id; + + String? _createdAt; + String? get createdAt => _$this._createdAt; + set createdAt(String? createdAt) => _$this._createdAt = createdAt; + + String? _updatedAt; + String? get updatedAt => _$this._updatedAt; + set updatedAt(String? updatedAt) => _$this._updatedAt = updatedAt; + + int? _width; + int? get width => _$this._width; + set width(int? width) => _$this._width = width; + + int? _height; + int? get height => _$this._height; + set height(int? height) => _$this._height = height; + + String? _color; + String? get color => _$this._color; + set color(String? color) => _$this._color = color; + + int? _downloads; + int? get downloads => _$this._downloads; + set downloads(int? downloads) => _$this._downloads = downloads; + + int? _likes; + int? get likes => _$this._likes; + set likes(int? likes) => _$this._likes = likes; + + bool? _likedByUser; + bool? get likedByUser => _$this._likedByUser; + set likedByUser(bool? likedByUser) => _$this._likedByUser = likedByUser; + + String? _description; + String? get description => _$this._description; + set description(String? description) => _$this._description = description; + + ExifBuilder? _exif; + ExifBuilder get exif => _$this._exif ??= new ExifBuilder(); + set exif(ExifBuilder? exif) => _$this._exif = exif; + + LocationBuilder? _location; + LocationBuilder get location => _$this._location ??= new LocationBuilder(); + set location(LocationBuilder? location) => _$this._location = location; + + ListBuilder? _tags; + ListBuilder get tags => _$this._tags ??= new ListBuilder(); + set tags(ListBuilder? tags) => _$this._tags = tags; + + ListBuilder? _currentUserCollections; + ListBuilder get currentUserCollections => + _$this._currentUserCollections ??= + new ListBuilder(); + set currentUserCollections( + ListBuilder? currentUserCollections, + ) => _$this._currentUserCollections = currentUserCollections; + + UrlsBuilder? _urls; + UrlsBuilder get urls => _$this._urls ??= new UrlsBuilder(); + set urls(UrlsBuilder? urls) => _$this._urls = urls; + + LinksBuilder? _links; + LinksBuilder get links => _$this._links ??= new LinksBuilder(); + set links(LinksBuilder? links) => _$this._links = links; + + UserBuilder? _user; + UserBuilder get user => _$this._user ??= new UserBuilder(); + set user(UserBuilder? user) => _$this._user = user; + + PhotoBuilder(); + + PhotoBuilder get _$this { + final $v = _$v; + if ($v != null) { + _id = $v.id; + _createdAt = $v.createdAt; + _updatedAt = $v.updatedAt; + _width = $v.width; + _height = $v.height; + _color = $v.color; + _downloads = $v.downloads; + _likes = $v.likes; + _likedByUser = $v.likedByUser; + _description = $v.description; + _exif = $v.exif?.toBuilder(); + _location = $v.location?.toBuilder(); + _tags = $v.tags?.toBuilder(); + _currentUserCollections = $v.currentUserCollections?.toBuilder(); + _urls = $v.urls?.toBuilder(); + _links = $v.links?.toBuilder(); + _user = $v.user?.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(Photo other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$Photo; + } + + @override + void update(void Function(PhotoBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + Photo build() => _build(); + + _$Photo _build() { + _$Photo _$result; + try { + _$result = + _$v ?? + new _$Photo._( + id: BuiltValueNullFieldError.checkNotNull(id, r'Photo', 'id'), + createdAt: createdAt, + updatedAt: updatedAt, + width: width, + height: height, + color: color, + downloads: downloads, + likes: likes, + likedByUser: likedByUser, + description: description, + exif: _exif?.build(), + location: _location?.build(), + tags: _tags?.build(), + currentUserCollections: _currentUserCollections?.build(), + urls: _urls?.build(), + links: _links?.build(), + user: _user?.build(), + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'exif'; + _exif?.build(); + _$failedField = 'location'; + _location?.build(); + _$failedField = 'tags'; + _tags?.build(); + _$failedField = 'currentUserCollections'; + _currentUserCollections?.build(); + _$failedField = 'urls'; + _urls?.build(); + _$failedField = 'links'; + _links?.build(); + _$failedField = 'user'; + _user?.build(); + } catch (e) { + throw new BuiltValueNestedFieldError( + r'Photo', + _$failedField, + e.toString(), + ); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/position.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/position.dart new file mode 100644 index 0000000..b57bb93 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/position.dart @@ -0,0 +1,39 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; + +part 'position.g.dart'; + +abstract class Position implements Built { + factory Position([void Function(PositionBuilder)? updates]) = _$Position; + + Position._(); + + @BuiltValueField(wireName: 'latitude') + double get latitude; + + @BuiltValueField(wireName: 'longitude') + double get longitude; + + String toJson() { + return json.encode( + serializers.serializeWith(Position.serializer, this), + ); + } + + static Position? fromJson(String jsonString) { + return serializers.deserializeWith( + Position.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => _$positionSerializer; +} diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/position.g.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/position.g.dart new file mode 100644 index 0000000..c8619d5 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/position.g.dart @@ -0,0 +1,192 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'position.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$positionSerializer = new _$PositionSerializer(); + +class _$PositionSerializer implements StructuredSerializer { + @override + final Iterable types = const [Position, _$Position]; + @override + final String wireName = 'Position'; + + @override + Iterable serialize( + Serializers serializers, + Position object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = [ + 'latitude', + serializers.serialize( + object.latitude, + specifiedType: const FullType(double), + ), + 'longitude', + serializers.serialize( + object.longitude, + specifiedType: const FullType(double), + ), + ]; + + return result; + } + + @override + Position deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new PositionBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'latitude': + result.latitude = + serializers.deserialize( + value, + specifiedType: const FullType(double), + )! + as double; + break; + case 'longitude': + result.longitude = + serializers.deserialize( + value, + specifiedType: const FullType(double), + )! + as double; + break; + } + } + + return result.build(); + } +} + +class _$Position extends Position { + @override + final double latitude; + @override + final double longitude; + + factory _$Position([void Function(PositionBuilder)? updates]) => + (new PositionBuilder()..update(updates))._build(); + + _$Position._({required this.latitude, required this.longitude}) : super._() { + BuiltValueNullFieldError.checkNotNull( + latitude, + r'Position', + 'latitude', + ); + BuiltValueNullFieldError.checkNotNull( + longitude, + r'Position', + 'longitude', + ); + } + + @override + Position rebuild(void Function(PositionBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + PositionBuilder toBuilder() => new PositionBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Position && + latitude == other.latitude && + longitude == other.longitude; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, latitude.hashCode); + _$hash = $jc(_$hash, longitude.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'Position') + ..add('latitude', latitude) + ..add('longitude', longitude)) + .toString(); + } +} + +class PositionBuilder implements Builder { + _$Position? _$v; + + double? _latitude; + double? get latitude => _$this._latitude; + set latitude(double? latitude) => _$this._latitude = latitude; + + double? _longitude; + double? get longitude => _$this._longitude; + set longitude(double? longitude) => _$this._longitude = longitude; + + PositionBuilder(); + + PositionBuilder get _$this { + final $v = _$v; + if ($v != null) { + _latitude = $v.latitude; + _longitude = $v.longitude; + _$v = null; + } + return this; + } + + @override + void replace(Position other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$Position; + } + + @override + void update(void Function(PositionBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + Position build() => _build(); + + _$Position _build() { + final _$result = + _$v ?? + new _$Position._( + latitude: BuiltValueNullFieldError.checkNotNull( + latitude, + r'Position', + 'latitude', + ), + longitude: BuiltValueNullFieldError.checkNotNull( + longitude, + r'Position', + 'longitude', + ), + ); + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/search_photos_response.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/search_photos_response.dart new file mode 100644 index 0000000..7ccc4ea --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/search_photos_response.dart @@ -0,0 +1,48 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_collection/built_collection.dart'; +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; +import 'photo.dart'; + +part 'search_photos_response.g.dart'; + +abstract class SearchPhotosResponse + implements Built { + factory SearchPhotosResponse([ + void Function(SearchPhotosResponseBuilder)? updates, + ]) = _$SearchPhotosResponse; + + SearchPhotosResponse._(); + + @BuiltValueField(wireName: 'total') + int? get total; + + @BuiltValueField(wireName: 'total_pages') + int? get totalPages; + + @BuiltValueField(wireName: 'results') + BuiltList get results; + + String toJson() { + return json.encode( + serializers.serializeWith(SearchPhotosResponse.serializer, this), + ); + } + + static SearchPhotosResponse? fromJson(String jsonString) { + return serializers.deserializeWith( + SearchPhotosResponse.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => + _$searchPhotosResponseSerializer; +} diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/search_photos_response.g.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/search_photos_response.g.dart new file mode 100644 index 0000000..65cbb64 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/search_photos_response.g.dart @@ -0,0 +1,244 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'search_photos_response.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$searchPhotosResponseSerializer = + new _$SearchPhotosResponseSerializer(); + +class _$SearchPhotosResponseSerializer + implements StructuredSerializer { + @override + final Iterable types = const [ + SearchPhotosResponse, + _$SearchPhotosResponse, + ]; + @override + final String wireName = 'SearchPhotosResponse'; + + @override + Iterable serialize( + Serializers serializers, + SearchPhotosResponse object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = [ + 'results', + serializers.serialize( + object.results, + specifiedType: const FullType(BuiltList, const [ + const FullType(Photo), + ]), + ), + ]; + Object? value; + value = object.total; + if (value != null) { + result + ..add('total') + ..add( + serializers.serialize(value, specifiedType: const FullType(int)), + ); + } + value = object.totalPages; + if (value != null) { + result + ..add('total_pages') + ..add( + serializers.serialize(value, specifiedType: const FullType(int)), + ); + } + return result; + } + + @override + SearchPhotosResponse deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new SearchPhotosResponseBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'total': + result.total = + serializers.deserialize( + value, + specifiedType: const FullType(int), + ) + as int?; + break; + case 'total_pages': + result.totalPages = + serializers.deserialize( + value, + specifiedType: const FullType(int), + ) + as int?; + break; + case 'results': + result.results.replace( + serializers.deserialize( + value, + specifiedType: const FullType(BuiltList, const [ + const FullType(Photo), + ]), + )! + as BuiltList, + ); + break; + } + } + + return result.build(); + } +} + +class _$SearchPhotosResponse extends SearchPhotosResponse { + @override + final int? total; + @override + final int? totalPages; + @override + final BuiltList results; + + factory _$SearchPhotosResponse([ + void Function(SearchPhotosResponseBuilder)? updates, + ]) => (new SearchPhotosResponseBuilder()..update(updates))._build(); + + _$SearchPhotosResponse._({ + this.total, + this.totalPages, + required this.results, + }) : super._() { + BuiltValueNullFieldError.checkNotNull( + results, + r'SearchPhotosResponse', + 'results', + ); + } + + @override + SearchPhotosResponse rebuild( + void Function(SearchPhotosResponseBuilder) updates, + ) => (toBuilder()..update(updates)).build(); + + @override + SearchPhotosResponseBuilder toBuilder() => + new SearchPhotosResponseBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is SearchPhotosResponse && + total == other.total && + totalPages == other.totalPages && + results == other.results; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, total.hashCode); + _$hash = $jc(_$hash, totalPages.hashCode); + _$hash = $jc(_$hash, results.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'SearchPhotosResponse') + ..add('total', total) + ..add('totalPages', totalPages) + ..add('results', results)) + .toString(); + } +} + +class SearchPhotosResponseBuilder + implements Builder { + _$SearchPhotosResponse? _$v; + + int? _total; + int? get total => _$this._total; + set total(int? total) => _$this._total = total; + + int? _totalPages; + int? get totalPages => _$this._totalPages; + set totalPages(int? totalPages) => _$this._totalPages = totalPages; + + ListBuilder? _results; + ListBuilder get results => + _$this._results ??= new ListBuilder(); + set results(ListBuilder? results) => _$this._results = results; + + SearchPhotosResponseBuilder(); + + SearchPhotosResponseBuilder get _$this { + final $v = _$v; + if ($v != null) { + _total = $v.total; + _totalPages = $v.totalPages; + _results = $v.results.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(SearchPhotosResponse other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$SearchPhotosResponse; + } + + @override + void update(void Function(SearchPhotosResponseBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + SearchPhotosResponse build() => _build(); + + _$SearchPhotosResponse _build() { + _$SearchPhotosResponse _$result; + try { + _$result = + _$v ?? + new _$SearchPhotosResponse._( + total: total, + totalPages: totalPages, + results: results.build(), + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'results'; + results.build(); + } catch (e) { + throw new BuiltValueNestedFieldError( + r'SearchPhotosResponse', + _$failedField, + e.toString(), + ); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/tags.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/tags.dart new file mode 100644 index 0000000..b366e52 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/tags.dart @@ -0,0 +1,34 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; + +part 'tags.g.dart'; + +abstract class Tags implements Built { + factory Tags([void Function(TagsBuilder)? updates]) = _$Tags; + + Tags._(); + + @BuiltValueField(wireName: 'title') + String get title; + + String toJson() { + return json.encode(serializers.serializeWith(Tags.serializer, this)); + } + + static Tags? fromJson(String jsonString) { + return serializers.deserializeWith( + Tags.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => _$tagsSerializer; +} diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/tags.g.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/tags.g.dart new file mode 100644 index 0000000..4a9ac21 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/tags.g.dart @@ -0,0 +1,154 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'tags.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$tagsSerializer = new _$TagsSerializer(); + +class _$TagsSerializer implements StructuredSerializer { + @override + final Iterable types = const [Tags, _$Tags]; + @override + final String wireName = 'Tags'; + + @override + Iterable serialize( + Serializers serializers, + Tags object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = [ + 'title', + serializers.serialize( + object.title, + specifiedType: const FullType(String), + ), + ]; + + return result; + } + + @override + Tags deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new TagsBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'title': + result.title = + serializers.deserialize( + value, + specifiedType: const FullType(String), + )! + as String; + break; + } + } + + return result.build(); + } +} + +class _$Tags extends Tags { + @override + final String title; + + factory _$Tags([void Function(TagsBuilder)? updates]) => + (new TagsBuilder()..update(updates))._build(); + + _$Tags._({required this.title}) : super._() { + BuiltValueNullFieldError.checkNotNull(title, r'Tags', 'title'); + } + + @override + Tags rebuild(void Function(TagsBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + TagsBuilder toBuilder() => new TagsBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Tags && title == other.title; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, title.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper( + r'Tags', + )..add('title', title)).toString(); + } +} + +class TagsBuilder implements Builder { + _$Tags? _$v; + + String? _title; + String? get title => _$this._title; + set title(String? title) => _$this._title = title; + + TagsBuilder(); + + TagsBuilder get _$this { + final $v = _$v; + if ($v != null) { + _title = $v.title; + _$v = null; + } + return this; + } + + @override + void replace(Tags other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$Tags; + } + + @override + void update(void Function(TagsBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + Tags build() => _build(); + + _$Tags _build() { + final _$result = + _$v ?? + new _$Tags._( + title: BuiltValueNullFieldError.checkNotNull( + title, + r'Tags', + 'title', + ), + ); + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/unsplash.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/unsplash.dart new file mode 100644 index 0000000..413f2a2 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/unsplash.dart @@ -0,0 +1,121 @@ +// 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. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:http/http.dart' as http; +import 'package:logging/logging.dart'; + +import 'api_error.dart'; +import 'photo.dart'; +import 'search_photos_response.dart'; + +final _unsplashBaseUrl = Uri.parse('https://api.unsplash.com/'); + +/// Unsplash API Client. Requires an +/// [Unsplash API](https://unsplash.com/developers) `accessKey` to make +/// requests to the Unsplash API. +class Unsplash { + Unsplash({required String accessKey, http.BaseClient? httpClient}) + : _accessKey = accessKey, + _client = httpClient ?? http.Client(); + + final String _accessKey; + final http.Client _client; + final _log = Logger('Unsplash'); + + Future searchPhotos({ + required String query, + num page = 1, + num perPage = 10, + List collections = const [], + SearchPhotosOrientation? orientation, + }) async { + final searchPhotosUrl = _unsplashBaseUrl.replace( + path: '/search/photos', + queryParameters: { + 'query': query, + if (page != 1) 'page': '$page', + if (perPage != 10) 'per_page': '$perPage', + if (collections.isNotEmpty) 'collections': collections.join(','), + if (orientation == SearchPhotosOrientation.landscape) + 'orientation': 'landscape', + if (orientation == SearchPhotosOrientation.portrait) + 'orientation': 'portrait', + if (orientation == SearchPhotosOrientation.squarish) + 'orientation': 'squarish', + }, + ); + _log.info('GET $searchPhotosUrl'); + + final response = await _client.get( + searchPhotosUrl, + headers: { + 'Accept-Version': 'v1', + 'Authorization': 'Client-ID $_accessKey', + }, + ); + + dynamic body; + try { + body = json.fuse(utf8).decode(response.bodyBytes); + } catch (e) { + throw UnsplashException('Invalid JSON received'); + } + + if (body is Map && + body['errors'] is List && + body['errors'].isNotEmpty as bool) { + final apiError = ApiError.fromJson(response.body)!; + throw UnsplashException(apiError.errors!.join(', ')); + } + + return SearchPhotosResponse.fromJson(json.encode(body)); + } + + Future download(Photo photo) async { + // For detail on how downloading photos from Unsplash, please see + // https://help.unsplash.com/en/articles/2511258-guideline-triggering-a-download + + _log.info('GET ${photo.urls!.full}'); + final futureBytes = http.readBytes( + Uri.parse(photo.urls!.full!), + headers: { + 'Accept-Version': 'v1', + 'Authorization': 'Client-ID $_accessKey', + }, + ); + + _log.info('GET ${photo.links!.downloadLocation}'); + unawaited( + http.get( + Uri.parse(photo.links!.downloadLocation!), + headers: { + 'Accept-Version': 'v1', + 'Authorization': 'Client-ID $_accessKey', + }, + ), + ); + + return futureBytes; + } +} + +enum SearchPhotosOrientation { landscape, portrait, squarish } + +class UnsplashException implements Exception { + UnsplashException([this.message]); + + final String? message; + + @override + String toString() { + if (message == null) { + return 'UnsplashException'; + } + return 'UnsplashException: $message'; + } +} diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/urls.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/urls.dart new file mode 100644 index 0000000..dc514c9 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/urls.dart @@ -0,0 +1,46 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; + +part 'urls.g.dart'; + +abstract class Urls implements Built { + factory Urls([void Function(UrlsBuilder b)? updates]) = _$Urls; + + Urls._(); + + @BuiltValueField(wireName: 'raw') + String? get raw; + + @BuiltValueField(wireName: 'full') + String? get full; + + @BuiltValueField(wireName: 'regular') + String? get regular; + + @BuiltValueField(wireName: 'small') + String? get small; + + @BuiltValueField(wireName: 'thumb') + String? get thumb; + + String toJson() { + return json.encode(serializers.serializeWith(Urls.serializer, this)); + } + + static Urls? fromJson(String jsonString) { + return serializers.deserializeWith( + Urls.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => _$urlsSerializer; +} diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/urls.g.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/urls.g.dart new file mode 100644 index 0000000..f8f5862 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/urls.g.dart @@ -0,0 +1,275 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'urls.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$urlsSerializer = new _$UrlsSerializer(); + +class _$UrlsSerializer implements StructuredSerializer { + @override + final Iterable types = const [Urls, _$Urls]; + @override + final String wireName = 'Urls'; + + @override + Iterable serialize( + Serializers serializers, + Urls object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = []; + Object? value; + value = object.raw; + if (value != null) { + result + ..add('raw') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.full; + if (value != null) { + result + ..add('full') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.regular; + if (value != null) { + result + ..add('regular') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.small; + if (value != null) { + result + ..add('small') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.thumb; + if (value != null) { + result + ..add('thumb') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + return result; + } + + @override + Urls deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new UrlsBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'raw': + result.raw = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'full': + result.full = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'regular': + result.regular = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'small': + result.small = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'thumb': + result.thumb = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + } + } + + return result.build(); + } +} + +class _$Urls extends Urls { + @override + final String? raw; + @override + final String? full; + @override + final String? regular; + @override + final String? small; + @override + final String? thumb; + + factory _$Urls([void Function(UrlsBuilder)? updates]) => + (new UrlsBuilder()..update(updates))._build(); + + _$Urls._({this.raw, this.full, this.regular, this.small, this.thumb}) + : super._(); + + @override + Urls rebuild(void Function(UrlsBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + UrlsBuilder toBuilder() => new UrlsBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Urls && + raw == other.raw && + full == other.full && + regular == other.regular && + small == other.small && + thumb == other.thumb; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, raw.hashCode); + _$hash = $jc(_$hash, full.hashCode); + _$hash = $jc(_$hash, regular.hashCode); + _$hash = $jc(_$hash, small.hashCode); + _$hash = $jc(_$hash, thumb.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'Urls') + ..add('raw', raw) + ..add('full', full) + ..add('regular', regular) + ..add('small', small) + ..add('thumb', thumb)) + .toString(); + } +} + +class UrlsBuilder implements Builder { + _$Urls? _$v; + + String? _raw; + String? get raw => _$this._raw; + set raw(String? raw) => _$this._raw = raw; + + String? _full; + String? get full => _$this._full; + set full(String? full) => _$this._full = full; + + String? _regular; + String? get regular => _$this._regular; + set regular(String? regular) => _$this._regular = regular; + + String? _small; + String? get small => _$this._small; + set small(String? small) => _$this._small = small; + + String? _thumb; + String? get thumb => _$this._thumb; + set thumb(String? thumb) => _$this._thumb = thumb; + + UrlsBuilder(); + + UrlsBuilder get _$this { + final $v = _$v; + if ($v != null) { + _raw = $v.raw; + _full = $v.full; + _regular = $v.regular; + _small = $v.small; + _thumb = $v.thumb; + _$v = null; + } + return this; + } + + @override + void replace(Urls other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$Urls; + } + + @override + void update(void Function(UrlsBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + Urls build() => _build(); + + _$Urls _build() { + final _$result = + _$v ?? + new _$Urls._( + raw: raw, + full: full, + regular: regular, + small: small, + thumb: thumb, + ); + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/user.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/user.dart new file mode 100644 index 0000000..98f0178 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/user.dart @@ -0,0 +1,65 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; +import 'links.dart'; + +part 'user.g.dart'; + +abstract class User implements Built { + factory User([void Function(UserBuilder)? updates]) = _$User; + + User._(); + + @BuiltValueField(wireName: 'id') + String get id; + + @BuiltValueField(wireName: 'updated_at') + String? get updatedAt; + + @BuiltValueField(wireName: 'username') + String get username; + + @BuiltValueField(wireName: 'name') + String get name; + + @BuiltValueField(wireName: 'portfolio_url') + String? get portfolioUrl; + + @BuiltValueField(wireName: 'bio') + String? get bio; + + @BuiltValueField(wireName: 'location') + String? get location; + + @BuiltValueField(wireName: 'total_likes') + int? get totalLikes; + + @BuiltValueField(wireName: 'total_photos') + int? get totalPhotos; + + @BuiltValueField(wireName: 'total_collections') + int? get totalCollections; + + @BuiltValueField(wireName: 'links') + Links? get links; + + String toJson() { + return json.encode(serializers.serializeWith(User.serializer, this)); + } + + static User? fromJson(String jsonString) { + return serializers.deserializeWith( + User.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => _$userSerializer; +} diff --git a/desktop_photo_search/fluent_ui/lib/src/unsplash/user.g.dart b/desktop_photo_search/fluent_ui/lib/src/unsplash/user.g.dart new file mode 100644 index 0000000..cf00d10 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/unsplash/user.g.dart @@ -0,0 +1,471 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$userSerializer = new _$UserSerializer(); + +class _$UserSerializer implements StructuredSerializer { + @override + final Iterable types = const [User, _$User]; + @override + final String wireName = 'User'; + + @override + Iterable serialize( + Serializers serializers, + User object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = [ + 'id', + serializers.serialize( + object.id, + specifiedType: const FullType(String), + ), + 'username', + serializers.serialize( + object.username, + specifiedType: const FullType(String), + ), + 'name', + serializers.serialize( + object.name, + specifiedType: const FullType(String), + ), + ]; + Object? value; + value = object.updatedAt; + if (value != null) { + result + ..add('updated_at') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.portfolioUrl; + if (value != null) { + result + ..add('portfolio_url') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.bio; + if (value != null) { + result + ..add('bio') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.location; + if (value != null) { + result + ..add('location') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.totalLikes; + if (value != null) { + result + ..add('total_likes') + ..add( + serializers.serialize(value, specifiedType: const FullType(int)), + ); + } + value = object.totalPhotos; + if (value != null) { + result + ..add('total_photos') + ..add( + serializers.serialize(value, specifiedType: const FullType(int)), + ); + } + value = object.totalCollections; + if (value != null) { + result + ..add('total_collections') + ..add( + serializers.serialize(value, specifiedType: const FullType(int)), + ); + } + value = object.links; + if (value != null) { + result + ..add('links') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(Links), + ), + ); + } + return result; + } + + @override + User deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new UserBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'id': + result.id = + serializers.deserialize( + value, + specifiedType: const FullType(String), + )! + as String; + break; + case 'updated_at': + result.updatedAt = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'username': + result.username = + serializers.deserialize( + value, + specifiedType: const FullType(String), + )! + as String; + break; + case 'name': + result.name = + serializers.deserialize( + value, + specifiedType: const FullType(String), + )! + as String; + break; + case 'portfolio_url': + result.portfolioUrl = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'bio': + result.bio = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'location': + result.location = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'total_likes': + result.totalLikes = + serializers.deserialize( + value, + specifiedType: const FullType(int), + ) + as int?; + break; + case 'total_photos': + result.totalPhotos = + serializers.deserialize( + value, + specifiedType: const FullType(int), + ) + as int?; + break; + case 'total_collections': + result.totalCollections = + serializers.deserialize( + value, + specifiedType: const FullType(int), + ) + as int?; + break; + case 'links': + result.links.replace( + serializers.deserialize( + value, + specifiedType: const FullType(Links), + )! + as Links, + ); + break; + } + } + + return result.build(); + } +} + +class _$User extends User { + @override + final String id; + @override + final String? updatedAt; + @override + final String username; + @override + final String name; + @override + final String? portfolioUrl; + @override + final String? bio; + @override + final String? location; + @override + final int? totalLikes; + @override + final int? totalPhotos; + @override + final int? totalCollections; + @override + final Links? links; + + factory _$User([void Function(UserBuilder)? updates]) => + (new UserBuilder()..update(updates))._build(); + + _$User._({ + required this.id, + this.updatedAt, + required this.username, + required this.name, + this.portfolioUrl, + this.bio, + this.location, + this.totalLikes, + this.totalPhotos, + this.totalCollections, + this.links, + }) : super._() { + BuiltValueNullFieldError.checkNotNull(id, r'User', 'id'); + BuiltValueNullFieldError.checkNotNull(username, r'User', 'username'); + BuiltValueNullFieldError.checkNotNull(name, r'User', 'name'); + } + + @override + User rebuild(void Function(UserBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + UserBuilder toBuilder() => new UserBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is User && + id == other.id && + updatedAt == other.updatedAt && + username == other.username && + name == other.name && + portfolioUrl == other.portfolioUrl && + bio == other.bio && + location == other.location && + totalLikes == other.totalLikes && + totalPhotos == other.totalPhotos && + totalCollections == other.totalCollections && + links == other.links; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, id.hashCode); + _$hash = $jc(_$hash, updatedAt.hashCode); + _$hash = $jc(_$hash, username.hashCode); + _$hash = $jc(_$hash, name.hashCode); + _$hash = $jc(_$hash, portfolioUrl.hashCode); + _$hash = $jc(_$hash, bio.hashCode); + _$hash = $jc(_$hash, location.hashCode); + _$hash = $jc(_$hash, totalLikes.hashCode); + _$hash = $jc(_$hash, totalPhotos.hashCode); + _$hash = $jc(_$hash, totalCollections.hashCode); + _$hash = $jc(_$hash, links.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'User') + ..add('id', id) + ..add('updatedAt', updatedAt) + ..add('username', username) + ..add('name', name) + ..add('portfolioUrl', portfolioUrl) + ..add('bio', bio) + ..add('location', location) + ..add('totalLikes', totalLikes) + ..add('totalPhotos', totalPhotos) + ..add('totalCollections', totalCollections) + ..add('links', links)) + .toString(); + } +} + +class UserBuilder implements Builder { + _$User? _$v; + + String? _id; + String? get id => _$this._id; + set id(String? id) => _$this._id = id; + + String? _updatedAt; + String? get updatedAt => _$this._updatedAt; + set updatedAt(String? updatedAt) => _$this._updatedAt = updatedAt; + + String? _username; + String? get username => _$this._username; + set username(String? username) => _$this._username = username; + + String? _name; + String? get name => _$this._name; + set name(String? name) => _$this._name = name; + + String? _portfolioUrl; + String? get portfolioUrl => _$this._portfolioUrl; + set portfolioUrl(String? portfolioUrl) => + _$this._portfolioUrl = portfolioUrl; + + String? _bio; + String? get bio => _$this._bio; + set bio(String? bio) => _$this._bio = bio; + + String? _location; + String? get location => _$this._location; + set location(String? location) => _$this._location = location; + + int? _totalLikes; + int? get totalLikes => _$this._totalLikes; + set totalLikes(int? totalLikes) => _$this._totalLikes = totalLikes; + + int? _totalPhotos; + int? get totalPhotos => _$this._totalPhotos; + set totalPhotos(int? totalPhotos) => _$this._totalPhotos = totalPhotos; + + int? _totalCollections; + int? get totalCollections => _$this._totalCollections; + set totalCollections(int? totalCollections) => + _$this._totalCollections = totalCollections; + + LinksBuilder? _links; + LinksBuilder get links => _$this._links ??= new LinksBuilder(); + set links(LinksBuilder? links) => _$this._links = links; + + UserBuilder(); + + UserBuilder get _$this { + final $v = _$v; + if ($v != null) { + _id = $v.id; + _updatedAt = $v.updatedAt; + _username = $v.username; + _name = $v.name; + _portfolioUrl = $v.portfolioUrl; + _bio = $v.bio; + _location = $v.location; + _totalLikes = $v.totalLikes; + _totalPhotos = $v.totalPhotos; + _totalCollections = $v.totalCollections; + _links = $v.links?.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(User other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$User; + } + + @override + void update(void Function(UserBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + User build() => _build(); + + _$User _build() { + _$User _$result; + try { + _$result = + _$v ?? + new _$User._( + id: BuiltValueNullFieldError.checkNotNull(id, r'User', 'id'), + updatedAt: updatedAt, + username: BuiltValueNullFieldError.checkNotNull( + username, + r'User', + 'username', + ), + name: BuiltValueNullFieldError.checkNotNull( + name, + r'User', + 'name', + ), + portfolioUrl: portfolioUrl, + bio: bio, + location: location, + totalLikes: totalLikes, + totalPhotos: totalPhotos, + totalCollections: totalCollections, + links: _links?.build(), + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'links'; + _links?.build(); + } catch (e) { + throw new BuiltValueNestedFieldError( + r'User', + _$failedField, + e.toString(), + ); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/fluent_ui/lib/src/widgets/photo_details.dart b/desktop_photo_search/fluent_ui/lib/src/widgets/photo_details.dart new file mode 100644 index 0000000..fc55e98 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/widgets/photo_details.dart @@ -0,0 +1,119 @@ +// 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. + +import 'package:fluent_ui/fluent_ui.dart' hide Card, FluentIcons; +import 'package:fluentui_system_icons/fluentui_system_icons.dart'; +import 'package:flutter/material.dart' show Card, BorderSide; +import 'package:transparent_image/transparent_image.dart'; +import 'package:url_launcher/link.dart'; + +import '../../unsplash_access_key.dart'; +import '../unsplash/photo.dart'; + +final _unsplashHomepage = Uri.parse( + 'https://unsplash.com/?utm_source=$unsplashAppName&utm_medium=referral', +); + +typedef PhotoDetailsPhotoSaveCallback = void Function(Photo); + +class PhotoDetails extends StatefulWidget { + const PhotoDetails({ + required this.photo, + required this.onPhotoSave, + super.key, + }); + final Photo photo; + final PhotoDetailsPhotoSaveCallback onPhotoSave; + + @override + State createState() => _PhotoDetailsState(); +} + +class _PhotoDetailsState extends State { + Widget _buildPhotoAttribution(BuildContext context) { + return Row( + children: [ + const Text('Photo by'), + Link( + uri: Uri.parse( + 'https://unsplash.com/@${widget.photo.user!.username}?utm_source=$unsplashAppName&utm_medium=referral', + ), + builder: (context, followLink) => HyperlinkButton( + onPressed: followLink, + child: Text(widget.photo.user!.name), + ), + ), + const Text('on'), + Link( + uri: _unsplashHomepage, + builder: (context, followLink) => HyperlinkButton( + onPressed: followLink, + child: const Text('Unsplash'), + ), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + return Scrollbar( + child: SingleChildScrollView( + primary: true, + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 16), + Card( + shape: ContinuousRectangleBorder( + side: const BorderSide(color: Colors.black), + borderRadius: BorderRadius.circular(4), + ), + child: AnimatedSize( + duration: const Duration(milliseconds: 750), + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 40), + child: ConstrainedBox( + constraints: const BoxConstraints( + minWidth: 400, + minHeight: 400, + ), + child: FadeInImage.memoryNetwork( + placeholder: kTransparentImage, + imageSemanticLabel: widget.photo.description, + image: widget.photo.urls!.small!, + ), + ), + ), + ), + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.only(left: 4), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + _buildPhotoAttribution(context), + const SizedBox(width: 8), + IconButton( + icon: const Icon( + FluentIcons.arrow_download_20_regular, + size: 20, + ), + onPressed: () => widget.onPhotoSave(widget.photo), + ), + ], + ), + ), + const SizedBox(height: 48), + ], + ), + ), + ), + ); + } +} diff --git a/desktop_photo_search/fluent_ui/lib/src/widgets/photo_search_dialog.dart b/desktop_photo_search/fluent_ui/lib/src/widgets/photo_search_dialog.dart new file mode 100644 index 0000000..fa83f81 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/widgets/photo_search_dialog.dart @@ -0,0 +1,67 @@ +// 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. + +import 'package:fluent_ui/fluent_ui.dart'; + +typedef PhotoSearchDialogCallback = void Function(String searchQuery); + +class PhotoSearchDialog extends StatefulWidget { + const PhotoSearchDialog({required this.callback, super.key}); + final PhotoSearchDialogCallback callback; + @override + State createState() => _PhotoSearchDialogState(); +} + +class _PhotoSearchDialogState extends State { + final _controller = TextEditingController(); + bool _searchEnabled = false; + + @override + void initState() { + super.initState(); + _controller.addListener(() { + setState(() { + _searchEnabled = _controller.text.isNotEmpty; + }); + }); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => ContentDialog( + title: const Text('Photo Search'), + content: TextBox( + autofocus: true, + controller: _controller, + onSubmitted: (content) { + if (content.isNotEmpty) { + widget.callback(content); + Navigator.of(context).pop(); + } + }, + ), + actions: [ + FilledButton( + onPressed: _searchEnabled + ? () { + widget.callback(_controller.text); + Navigator.of(context).pop(); + } + : null, + child: const Text('Search'), + ), + Button( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Cancel'), + ), + ], + ); +} diff --git a/desktop_photo_search/fluent_ui/lib/src/widgets/policy_dialog.dart b/desktop_photo_search/fluent_ui/lib/src/widgets/policy_dialog.dart new file mode 100644 index 0000000..add7f3d --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/widgets/policy_dialog.dart @@ -0,0 +1,80 @@ +// 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. + +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter/gestures.dart'; +import 'package:url_launcher/url_launcher.dart' as url_launcher; + +class PolicyDialog extends StatelessWidget { + const PolicyDialog({super.key}); + + @override + Widget build(BuildContext context) { + return ContentDialog( + title: const Text('Terms & Conditions'), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RichText( + textAlign: TextAlign.left, + text: TextSpan( + text: 'β€’ ', + style: const TextStyle(color: Colors.black, fontSize: 18), + children: [ + TextSpan( + text: 'https://policies.google.com/terms', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.blue.normal, + ), + recognizer: TapGestureRecognizer() + ..onTap = () async { + final url = Uri.parse( + 'https://policies.google.com/terms', + ); + if (await url_launcher.canLaunchUrl(url)) { + await url_launcher.launchUrl(url); + } + }, + ), + ], + ), + ), + RichText( + textAlign: TextAlign.left, + text: TextSpan( + text: 'β€’ ', + style: const TextStyle(color: Colors.black, fontSize: 18), + children: [ + TextSpan( + text: 'https://unsplash.com/terms', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.blue.normal, + ), + recognizer: TapGestureRecognizer() + ..onTap = () async { + final url = Uri.parse('https://unsplash.com/terms'); + if (await url_launcher.canLaunchUrl(url)) { + await url_launcher.launchUrl(url); + } + }, + ), + ], + ), + ), + ], + ), + actions: [ + FilledButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Accept'), + ), + ], + ); + } +} diff --git a/desktop_photo_search/fluent_ui/lib/src/widgets/split.dart b/desktop_photo_search/fluent_ui/lib/src/widgets/split.dart new file mode 100644 index 0000000..2296019 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/widgets/split.dart @@ -0,0 +1,181 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:math'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +/// A widget that takes two children, lays them out along [axis], and allows +/// the user to resize them. +/// +/// The user can customize the amount of space allocated to each child by +/// dragging a divider between them. +/// +/// [initialFirstFraction] defines how much space to give the [firstChild] +/// when first building this widget. [secondChild] will take the remaining +/// space. +/// +/// The user can drag the widget with key [dividerKey] to change +/// the space allocated between [firstChild] and [secondChild]. +class Split extends StatefulWidget { + /// Builds a split oriented along [axis]. + const Split({ + super.key, + required this.axis, + required this.firstChild, + required this.secondChild, + double? initialFirstFraction, + }) : initialFirstFraction = initialFirstFraction ?? 0.5; + + /// The main axis the children will lay out on. + /// + /// If [Axis.horizontal], the children will be placed in a [Row] + /// and they will be horizontally resizable. + /// + /// If [Axis.vertical], the children will be placed in a [Column] + /// and they will be vertically resizable. + /// + /// Cannot be null. + final Axis axis; + + /// The child that will be laid out first along [axis]. + final Widget firstChild; + + /// The child that will be laid out last along [axis]. + final Widget secondChild; + + /// The fraction of the layout to allocate to [firstChild]. + /// + /// [secondChild] will receive a fraction of `1 - initialFirstFraction`. + final double initialFirstFraction; + + /// The key passed to the divider between [firstChild] and [secondChild]. + /// + /// Visible to grab it in tests. + @visibleForTesting + Key get dividerKey => Key('$this dividerKey'); + + /// The size of the divider between [firstChild] and [secondChild] in + /// logical pixels (dp, not px). + static const double dividerMainAxisSize = 10; + + static Axis axisFor(BuildContext context, double horizontalAspectRatio) { + final screenSize = MediaQuery.of(context).size; + final aspectRatio = screenSize.width / screenSize.height; + if (aspectRatio >= horizontalAspectRatio) { + return Axis.horizontal; + } + return Axis.vertical; + } + + @override + State createState() => _SplitState(); +} + +class _SplitState extends State { + late double firstFraction; + + double get secondFraction => 1 - firstFraction; + + bool get isHorizontal => widget.axis == Axis.horizontal; + + @override + void initState() { + super.initState(); + firstFraction = widget.initialFirstFraction; + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder(builder: _buildLayout); + } + + Widget _buildLayout(BuildContext context, BoxConstraints constraints) { + final width = constraints.maxWidth; + final height = constraints.maxHeight; + final axisSize = isHorizontal ? width : height; + final crossAxisSize = isHorizontal ? height : width; + const halfDivider = Split.dividerMainAxisSize / 2.0; + + // Determine what fraction to give each child, including enough space to + // display the divider. + var firstSize = axisSize * firstFraction; + var secondSize = axisSize * secondFraction; + + // Clamp the sizes to be sure there is enough space for the dividers. + firstSize = firstSize.clamp(halfDivider, axisSize - halfDivider); + secondSize = secondSize.clamp(halfDivider, axisSize - halfDivider); + + // Remove space from each child to place the divider in the middle. + firstSize = firstSize - halfDivider; + secondSize = secondSize - halfDivider; + + void updateSpacing(DragUpdateDetails dragDetails) { + final delta = isHorizontal ? dragDetails.delta.dx : dragDetails.delta.dy; + final fractionalDelta = delta / axisSize; + setState(() { + // Update the fraction of space consumed by the children, + // being sure not to allocate any negative space. + firstFraction += fractionalDelta; + firstFraction = firstFraction.clamp(0.0, 1.0); + }); + } + + final dragIndicator = Flex( + direction: isHorizontal ? Axis.vertical : Axis.horizontal, + mainAxisSize: MainAxisSize.min, + children: [ + for (var i = 0; i < min(crossAxisSize / 6.0, 3).floor(); i++) + Padding( + padding: EdgeInsets.symmetric( + vertical: isHorizontal ? 2.0 : 0.0, + horizontal: isHorizontal ? 0.0 : 2.0, + ), + child: DecoratedBox( + decoration: BoxDecoration( + color: Theme.of(context).dividerColor, + borderRadius: BorderRadius.circular( + Split.dividerMainAxisSize, + ), + ), + child: SizedBox( + height: isHorizontal ? 2.0 : Split.dividerMainAxisSize - 2.0, + width: isHorizontal ? Split.dividerMainAxisSize - 2.0 : 2.0, + ), + ), + ), + ], + ); + + final children = [ + SizedBox( + width: isHorizontal ? firstSize : width, + height: isHorizontal ? height : firstSize, + child: widget.firstChild, + ), + GestureDetector( + key: widget.dividerKey, + behavior: HitTestBehavior.translucent, + onHorizontalDragUpdate: isHorizontal ? updateSpacing : null, + onVerticalDragUpdate: isHorizontal ? null : updateSpacing, + // DartStartBehavior.down is needed to keep the mouse pointer stuck to + // the drag bar. There still appears to be a few frame lag before the + // drag action triggers which is't ideal but isn't a launch blocker. + dragStartBehavior: DragStartBehavior.down, + child: SizedBox( + width: isHorizontal ? Split.dividerMainAxisSize : width, + height: isHorizontal ? height : Split.dividerMainAxisSize, + child: Center(child: dragIndicator), + ), + ), + SizedBox( + width: isHorizontal ? secondSize : width, + height: isHorizontal ? height : secondSize, + child: widget.secondChild, + ), + ]; + return Flex(direction: widget.axis, children: children); + } +} diff --git a/desktop_photo_search/fluent_ui/lib/src/widgets/unsplash_notice.dart b/desktop_photo_search/fluent_ui/lib/src/widgets/unsplash_notice.dart new file mode 100644 index 0000000..074eebb --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/widgets/unsplash_notice.dart @@ -0,0 +1,115 @@ +// Copyright 2022 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. + +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter/gestures.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../../unsplash_access_key.dart'; + +final _unsplashHomepage = Uri.parse( + 'https://unsplash.com/?utm_source=${Uri.encodeFull(unsplashAppName)}&utm_medium=referral', +); +final _unsplashPrivacyPolicy = Uri.parse( + 'https://unsplash.com/privacy?utm_source=${Uri.encodeFull(unsplashAppName)}&utm_medium=referral', +); + +class UnsplashNotice extends StatefulWidget { + const UnsplashNotice({super.key, required this.child}); + final Widget child; + + @override + State createState() => _UnsplashNoticeState(); +} + +class _UnsplashNoticeState extends State { + bool noticeAccepted = false; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + showDialog( + context: context, + builder: (context) { + return _UnsplashDialog( + accepted: () { + setState(() { + noticeAccepted = true; + }); + }, + ); + }, + ); + }); + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +} + +class _UnsplashDialog extends StatelessWidget { + const _UnsplashDialog({required this.accepted}); + final Function accepted; + + @override + Widget build(BuildContext context) { + return ContentDialog( + title: const Text('Unsplash Notice'), + content: RichText( + text: TextSpan( + text: + 'This is a sample desktop application provided by Google' + ' that enables you to search ', + style: const TextStyle(color: Colors.grey), + children: [ + TextSpan( + text: 'Unsplash', + recognizer: TapGestureRecognizer() + ..onTap = () async { + if (!await launchUrl(_unsplashHomepage)) { + throw 'Could not launch $_unsplashHomepage'; + } + }, + style: TextStyle(color: Colors.blue), + ), + const TextSpan( + text: + ' for photographs that interest you. When you search' + ' for and interact with photos, Unsplash will collect' + ' information about you and your use of the Unsplash' + ' services. Learn more about ', + style: TextStyle(color: Colors.grey), + ), + TextSpan( + text: 'how Unsplash collects and uses data', + recognizer: TapGestureRecognizer() + ..onTap = () async { + if (!await launchUrl(_unsplashPrivacyPolicy)) { + throw 'Could not launch $_unsplashPrivacyPolicy'; + } + }, + style: TextStyle(color: Colors.blue), + ), + const TextSpan( + text: '.', + style: TextStyle(color: Colors.grey), + ), + ], + ), + ), + actions: [ + Button( + child: const Text('Got it'), + onPressed: () { + accepted(); + Navigator.pop(context); + }, + ), + ], + ); + } +} diff --git a/desktop_photo_search/fluent_ui/lib/src/widgets/unsplash_search_content.dart b/desktop_photo_search/fluent_ui/lib/src/widgets/unsplash_search_content.dart new file mode 100644 index 0000000..a805cad --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/src/widgets/unsplash_search_content.dart @@ -0,0 +1,108 @@ +// Copyright 2022 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. + +import 'package:file_selector/file_selector.dart'; +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:provider/provider.dart'; + +import '../model/photo_search_model.dart'; +import '../unsplash/photo.dart'; +import '../widgets/photo_details.dart'; +import '../widgets/split.dart' as split; + +class UnsplashSearchContent extends StatefulWidget { + const UnsplashSearchContent({super.key}); + + @override + State createState() => _UnsplashSearchContentState(); +} + +class _UnsplashSearchContentState extends State { + final _treeViewScrollController = ScrollController(); + + @override + dispose() { + _treeViewScrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final photoSearchModel = Provider.of(context); + + return split.Split( + axis: Axis.horizontal, + initialFirstFraction: 0.4, + firstChild: Scrollbar( + controller: _treeViewScrollController, + child: SingleChildScrollView( + controller: _treeViewScrollController, + child: TreeView( + items: photoSearchModel.entries.map(_buildSearchEntry).toList(), + ), + ), + ), + secondChild: Center( + child: photoSearchModel.selectedPhoto != null + ? PhotoDetails( + photo: photoSearchModel.selectedPhoto!, + onPhotoSave: (photo) async { + final saveLocation = await getSaveLocation( + suggestedName: '${photo.id}.jpg', + acceptedTypeGroups: [ + const XTypeGroup( + label: 'JPG', + extensions: ['jpg'], + mimeTypes: ['image/jpeg'], + ), + ], + ); + if (saveLocation != null) { + final fileData = await photoSearchModel.download( + photo: photo, + ); + final photoFile = XFile.fromData( + fileData, + mimeType: 'image/jpeg', + ); + await photoFile.saveTo(saveLocation.path); + } + }, + ) + : Container(), + ), + ); + } + + TreeViewItem _buildSearchEntry(SearchEntry searchEntry) { + void selectPhoto(Photo photo) { + searchEntry.model.selectedPhoto = photo; + } + + String labelForPhoto(Photo photo) => 'Photo by ${photo.user!.name}'; + + return TreeViewItem( + content: Text(searchEntry.query), + children: searchEntry.photos + .map( + (photo) => TreeViewItem( + content: Semantics( + button: true, + onTap: () => selectPhoto(photo), + label: labelForPhoto(photo), + excludeSemantics: true, + child: GestureDetector( + onTap: () => selectPhoto(photo), + child: Text( + labelForPhoto(photo), + style: const TextStyle(color: Colors.black), + ), + ), + ), + ), + ) + .toList(), + ); + } +} diff --git a/desktop_photo_search/fluent_ui/lib/unsplash_access_key.dart b/desktop_photo_search/fluent_ui/lib/unsplash_access_key.dart new file mode 100644 index 0000000..9f29f46 --- /dev/null +++ b/desktop_photo_search/fluent_ui/lib/unsplash_access_key.dart @@ -0,0 +1,9 @@ +// 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. + +// To use this sample: Retrieve an API Access Key from https://unsplash.com/developers +const String unsplashAccessKey = ''; + +// The application name for the above API Access Key. +const unsplashAppName = ''; diff --git a/desktop_photo_search/fluent_ui/linux/.gitignore b/desktop_photo_search/fluent_ui/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/desktop_photo_search/fluent_ui/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/desktop_photo_search/fluent_ui/linux/CMakeLists.txt b/desktop_photo_search/fluent_ui/linux/CMakeLists.txt new file mode 100644 index 0000000..d0728bd --- /dev/null +++ b/desktop_photo_search/fluent_ui/linux/CMakeLists.txt @@ -0,0 +1,145 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "desktop_photo_search") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.desktop_photo_search") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/desktop_photo_search/fluent_ui/linux/flutter/CMakeLists.txt b/desktop_photo_search/fluent_ui/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/desktop_photo_search/fluent_ui/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/desktop_photo_search/fluent_ui/linux/flutter/generated_plugin_registrant.cc b/desktop_photo_search/fluent_ui/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..cab1d27 --- /dev/null +++ b/desktop_photo_search/fluent_ui/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,23 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); + g_autoptr(FlPluginRegistrar) menubar_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "MenubarPlugin"); + menubar_plugin_register_with_registrar(menubar_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/desktop_photo_search/fluent_ui/linux/flutter/generated_plugin_registrant.h b/desktop_photo_search/fluent_ui/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/desktop_photo_search/fluent_ui/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/desktop_photo_search/fluent_ui/linux/flutter/generated_plugins.cmake b/desktop_photo_search/fluent_ui/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..a862141 --- /dev/null +++ b/desktop_photo_search/fluent_ui/linux/flutter/generated_plugins.cmake @@ -0,0 +1,26 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux + menubar + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/desktop_photo_search/fluent_ui/linux/main.cc b/desktop_photo_search/fluent_ui/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/desktop_photo_search/fluent_ui/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/desktop_photo_search/fluent_ui/linux/my_application.cc b/desktop_photo_search/fluent_ui/linux/my_application.cc new file mode 100644 index 0000000..1bc867f --- /dev/null +++ b/desktop_photo_search/fluent_ui/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "desktop_photo_search"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "desktop_photo_search"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/desktop_photo_search/fluent_ui/linux/my_application.h b/desktop_photo_search/fluent_ui/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/desktop_photo_search/fluent_ui/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/desktop_photo_search/fluent_ui/macos/.gitignore b/desktop_photo_search/fluent_ui/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/desktop_photo_search/fluent_ui/macos/Flutter/Flutter-Debug.xcconfig b/desktop_photo_search/fluent_ui/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/desktop_photo_search/fluent_ui/macos/Flutter/Flutter-Release.xcconfig b/desktop_photo_search/fluent_ui/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/desktop_photo_search/fluent_ui/macos/Flutter/GeneratedPluginRegistrant.swift b/desktop_photo_search/fluent_ui/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..a6ebf3c --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,16 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import file_selector_macos +import menubar +import url_launcher_macos + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) + MenubarPlugin.register(with: registry.registrar(forPlugin: "MenubarPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) +} diff --git a/desktop_photo_search/fluent_ui/macos/Podfile b/desktop_photo_search/fluent_ui/macos/Podfile new file mode 100644 index 0000000..c795730 --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/desktop_photo_search/fluent_ui/macos/Runner.xcodeproj/project.pbxproj b/desktop_photo_search/fluent_ui/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..75062ff --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,791 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 5338CCEA202DE1A124991C49 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 10C47C9C15752FE372FF2CDC /* Pods_RunnerTests.framework */; }; + 77F5965C35CDF5C41CB7B903 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7209208C496CE7646ED7B1C /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 089E3F387724FC6ACEE208BB /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 10C47C9C15752FE372FF2CDC /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3287A54F487A9CDCF32D0B46 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* desktop_photo_search.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = desktop_photo_search.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 41F2FB3D8E017B613967822C /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9628514E18CED01B58493966 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + A198730F036570B6263D1758 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + A7209208C496CE7646ED7B1C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CEDA18B700CC21D86FE950A1 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5338CCEA202DE1A124991C49 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 77F5965C35CDF5C41CB7B903 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + B357E0D4BD0F93B773AE2F88 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* desktop_photo_search.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + B357E0D4BD0F93B773AE2F88 /* Pods */ = { + isa = PBXGroup; + children = ( + 3287A54F487A9CDCF32D0B46 /* Pods-Runner.debug.xcconfig */, + A198730F036570B6263D1758 /* Pods-Runner.release.xcconfig */, + 089E3F387724FC6ACEE208BB /* Pods-Runner.profile.xcconfig */, + CEDA18B700CC21D86FE950A1 /* Pods-RunnerTests.debug.xcconfig */, + 41F2FB3D8E017B613967822C /* Pods-RunnerTests.release.xcconfig */, + 9628514E18CED01B58493966 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A7209208C496CE7646ED7B1C /* Pods_Runner.framework */, + 10C47C9C15752FE372FF2CDC /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 780C18E3312D0BA7833E617F /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + EC8EEAE7E4E25AF7CDF53458 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + 40D98D982D3C9F50CF1E0699 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* desktop_photo_search.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 40D98D982D3C9F50CF1E0699 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 780C18E3312D0BA7833E617F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + EC8EEAE7E4E25AF7CDF53458 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CEDA18B700CC21D86FE950A1 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.desktopPhotoSearch.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/desktop_photo_search.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/desktop_photo_search"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 41F2FB3D8E017B613967822C /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.desktopPhotoSearch.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/desktop_photo_search.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/desktop_photo_search"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9628514E18CED01B58493966 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.desktopPhotoSearch.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/desktop_photo_search.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/desktop_photo_search"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/desktop_photo_search/fluent_ui/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/desktop_photo_search/fluent_ui/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/desktop_photo_search/fluent_ui/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/desktop_photo_search/fluent_ui/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..1bc8277 --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/desktop_photo_search/fluent_ui/macos/Runner.xcworkspace/contents.xcworkspacedata b/desktop_photo_search/fluent_ui/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/desktop_photo_search/fluent_ui/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/desktop_photo_search/fluent_ui/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/desktop_photo_search/fluent_ui/macos/Runner/AppDelegate.swift b/desktop_photo_search/fluent_ui/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..d53ef64 --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/desktop_photo_search/fluent_ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/desktop_photo_search/fluent_ui/macos/Runner/Base.lproj/MainMenu.xib b/desktop_photo_search/fluent_ui/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/desktop_photo_search/fluent_ui/macos/Runner/Configs/AppInfo.xcconfig b/desktop_photo_search/fluent_ui/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..59146e6 --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = desktop_photo_search + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.desktopPhotoSearch + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright Β© 2023 com.example. All rights reserved. diff --git a/desktop_photo_search/fluent_ui/macos/Runner/Configs/Debug.xcconfig b/desktop_photo_search/fluent_ui/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/desktop_photo_search/fluent_ui/macos/Runner/Configs/Release.xcconfig b/desktop_photo_search/fluent_ui/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/desktop_photo_search/fluent_ui/macos/Runner/Configs/Warnings.xcconfig b/desktop_photo_search/fluent_ui/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/desktop_photo_search/fluent_ui/macos/Runner/DebugProfile.entitlements b/desktop_photo_search/fluent_ui/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..0eaccf1 --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,16 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.files.user-selected.read-write + + com.apple.security.network.client + + com.apple.security.network.server + + + diff --git a/desktop_photo_search/fluent_ui/macos/Runner/Info.plist b/desktop_photo_search/fluent_ui/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/desktop_photo_search/fluent_ui/macos/Runner/MainFlutterWindow.swift b/desktop_photo_search/fluent_ui/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/desktop_photo_search/fluent_ui/macos/Runner/Release.entitlements b/desktop_photo_search/fluent_ui/macos/Runner/Release.entitlements new file mode 100644 index 0000000..a046386 --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/Runner/Release.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-write + + com.apple.security.network.client + + + diff --git a/desktop_photo_search/fluent_ui/macos/RunnerTests/RunnerTests.swift b/desktop_photo_search/fluent_ui/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..5418c9f --- /dev/null +++ b/desktop_photo_search/fluent_ui/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import FlutterMacOS +import Cocoa +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/desktop_photo_search/fluent_ui/pubspec.yaml b/desktop_photo_search/fluent_ui/pubspec.yaml new file mode 100644 index 0000000..4ec96ad --- /dev/null +++ b/desktop_photo_search/fluent_ui/pubspec.yaml @@ -0,0 +1,55 @@ +name: desktop_photo_search +description: Search for Photos, using the Unsplash API. +publish_to: 'none' +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + built_collection: ^5.1.1 + built_value: ^8.6.1 + cupertino_icons: ^1.0.5 + file_selector: ^1.0.0 + fluent_ui: ^4.7.2 + fluentui_system_icons: ^1.1.208 + flutter: + sdk: flutter + http: ^1.2.1 + logging: ^1.2.0 + menubar: + git: + url: https://github.com/google/flutter-desktop-embedding.git + path: plugins/menubar + provider: ^6.0.5 + transparent_image: ^2.0.1 + url_launcher: ^6.1.12 + uuid: ^4.0.0 + +dev_dependencies: + analysis_defaults: + path: ../../analysis_defaults + async: ^2.11.0 + build: ^3.0.0 + build_runner: ^2.4.6 + built_value_generator: ^8.6.1 + flutter_test: + sdk: flutter + grinder: ^0.9.4 + msix: ^3.16.1 + +flutter: + uses-material-design: true + +msix_config: + display_name: Flutter Desktop Photo Search + publisher_display_name: flutter.dev + store: false # Set to true to deploy to Microsoft Store + publisher: CN=01A6D5C0-D51A-4EEE-8DD0-F134DDD378F7 + identity_name: 16354flutter.dev.FlutterDesktopPhotoSearch + msix_version: 1.0.0.1 + icons_background_color: "#ffffff" + architecture: x64 + # See https://docs.microsoft.com/en-us/windows/uwp/packaging/app-capability-declarations + capabilities: "internetClient" diff --git a/desktop_photo_search/fluent_ui/test/unsplash_test.dart b/desktop_photo_search/fluent_ui/test/unsplash_test.dart new file mode 100644 index 0000000..5d0d5bd --- /dev/null +++ b/desktop_photo_search/fluent_ui/test/unsplash_test.dart @@ -0,0 +1,538 @@ +// 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. + +import 'package:desktop_photo_search/src/unsplash/photo.dart'; +import 'package:desktop_photo_search/src/unsplash/search_photos_response.dart'; +import 'package:desktop_photo_search/src/unsplash/unsplash.dart'; +import 'package:desktop_photo_search/src/unsplash/user.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart'; +import 'package:http/testing.dart'; + +void main() { + test('Photo.fromJson', () { + const input = ''' +{ + "id": "Dwu85P9SOIk", + "created_at": "2016-05-03T11:00:28-04:00", + "updated_at": "2016-07-10T11:00:01-05:00", + "width": 2448, + "height": 3264, + "color": "#6E633A", + "downloads": 1345, + "likes": 24, + "liked_by_user": false, + "description": "A man drinking a coffee.", + "exif": { + "make": "Canon", + "model": "Canon EOS 40D", + "exposure_time": "0.011111111111111112", + "aperture": "4.970854", + "focal_length": "37", + "iso": 100 + }, + "location": { + "city": "Montreal", + "country": "Canada", + "position": { + "latitude": 45.4732984, + "longitude": -73.6384879 + } + }, + "tags": [ + { "title": "man" }, + { "title": "drinking" }, + { "title": "coffee" } + ], + "current_user_collections": [ + { + "id": 206, + "title": "Makers: Cat and Ben", + "published_at": "2016-01-12T18:16:09-05:00", + "updated_at": "2016-07-10T11:00:01-05:00", + "cover_photo": null, + "user": null + } + ], + "urls": { + "raw": "https://images.unsplash.com/photo-1417325384643-aac51acc9e5d", + "full": "https://images.unsplash.com/photo-1417325384643-aac51acc9e5d?q=75&fm=jpg", + "regular": "https://images.unsplash.com/photo-1417325384643-aac51acc9e5d?q=75&fm=jpg&w=1080&fit=max", + "small": "https://images.unsplash.com/photo-1417325384643-aac51acc9e5d?q=75&fm=jpg&w=400&fit=max", + "thumb": "https://images.unsplash.com/photo-1417325384643-aac51acc9e5d?q=75&fm=jpg&w=200&fit=max" + }, + "links": { + "self": "https://api.unsplash.com/photos/Dwu85P9SOIk", + "html": "https://unsplash.com/photos/Dwu85P9SOIk", + "download": "https://unsplash.com/photos/Dwu85P9SOIk/download", + "download_location": "https://api.unsplash.com/photos/Dwu85P9SOIk/download" + }, + "user": { + "id": "QPxL2MGqfrw", + "updated_at": "2016-07-10T11:00:01-05:00", + "username": "exampleuser", + "name": "Joe Example", + "portfolio_url": "https://example.com/", + "bio": "Just an everyday Joe", + "location": "Montreal", + "total_likes": 5, + "total_photos": 10, + "total_collections": 13, + "links": { + "self": "https://api.unsplash.com/users/exampleuser", + "html": "https://unsplash.com/exampleuser", + "photos": "https://api.unsplash.com/users/exampleuser/photos", + "likes": "https://api.unsplash.com/users/exampleuser/likes", + "portfolio": "https://api.unsplash.com/users/exampleuser/portfolio" + } + } +} + '''; + + final photo = Photo.fromJson(input)!; + expect(photo.id, 'Dwu85P9SOIk'); + expect(photo.createdAt, '2016-05-03T11:00:28-04:00'); + expect(photo.updatedAt, '2016-07-10T11:00:01-05:00'); + expect(photo.width, 2448); + expect(photo.height, 3264); + expect(photo.color, '#6E633A'); + expect(photo.downloads, 1345); + expect(photo.likedByUser, false); + expect(photo.exif!.make, 'Canon'); + expect(photo.exif!.iso, 100); + expect(photo.location!.city, 'Montreal'); + expect(photo.location!.country, 'Canada'); + expect(photo.location!.position!.latitude, 45.4732984); + expect(photo.location!.position!.longitude, -73.6384879); + }); + + test('User.fromJson', () { + const input = ''' +{ + "id": "pXhwzz1JtQU", + "updated_at": "2016-07-10T11:00:01-05:00", + "username": "jimmyexample", + "name": "James Example", + "first_name": "James", + "last_name": "Example", + "instagram_username": "instantgrammer", + "twitter_username": "jimmy", + "portfolio_url": null, + "bio": "The user's bio", + "location": "Montreal, Qc", + "total_likes": 20, + "total_photos": 10, + "total_collections": 5, + "followed_by_user": false, + "followers_count": 300, + "following_count": 25, + "downloads": 225974, + "profile_image": { + "small": "https://images.unsplash.com/face-springmorning.jpg?q=80&fm=jpg&crop=faces&fit=crop&h=32&w=32", + "medium": "https://images.unsplash.com/face-springmorning.jpg?q=80&fm=jpg&crop=faces&fit=crop&h=64&w=64", + "large": "https://images.unsplash.com/face-springmorning.jpg?q=80&fm=jpg&crop=faces&fit=crop&h=128&w=128" + }, + "badge": { + "title": "Book contributor", + "primary": true, + "slug": "book-contributor", + "link": "https://book.unsplash.com" + }, + "links": { + "self": "https://api.unsplash.com/users/jimmyexample", + "html": "https://unsplash.com/jimmyexample", + "photos": "https://api.unsplash.com/users/jimmyexample/photos", + "likes": "https://api.unsplash.com/users/jimmyexample/likes", + "portfolio": "https://api.unsplash.com/users/jimmyexample/portfolio" + } +} + '''; + + final user = User.fromJson(input)!; + expect(user.id, 'pXhwzz1JtQU'); + }); + + test('SearchPhotosResponse.fromJson', () { + const input = ''' +{ + "total": 133, + "total_pages": 7, + "results": [ + { + "id": "eOLpJytrbsQ", + "created_at": "2014-11-18T14:35:36-05:00", + "width": 4000, + "height": 3000, + "color": "#A7A2A1", + "likes": 286, + "liked_by_user": false, + "description": "A man drinking a coffee.", + "user": { + "id": "Ul0QVz12Goo", + "username": "ugmonk", + "name": "Jeff Sheldon", + "first_name": "Jeff", + "last_name": "Sheldon", + "instagram_username": "instantgrammer", + "twitter_username": "ugmonk", + "portfolio_url": "http://ugmonk.com/", + "profile_image": { + "small": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=32&w=32&s=7cfe3b93750cb0c93e2f7caec08b5a41", + "medium": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=64&w=64&s=5a9dc749c43ce5bd60870b129a40902f", + "large": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=128&w=128&s=32085a077889586df88bfbe406692202" + }, + "links": { + "self": "https://api.unsplash.com/users/ugmonk", + "html": "http://unsplash.com/@ugmonk", + "photos": "https://api.unsplash.com/users/ugmonk/photos", + "likes": "https://api.unsplash.com/users/ugmonk/likes" + } + }, + "current_user_collections": [], + "urls": { + "raw": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f", + "full": "https://hd.unsplash.com/photo-1416339306562-f3d12fefd36f", + "regular": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=92f3e02f63678acc8416d044e189f515", + "small": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&s=263af33585f9d32af39d165b000845eb", + "thumb": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&s=8aae34cf35df31a592f0bef16e6342ef" + }, + "links": { + "self": "https://api.unsplash.com/photos/eOLpJytrbsQ", + "html": "http://unsplash.com/photos/eOLpJytrbsQ", + "download": "http://unsplash.com/photos/eOLpJytrbsQ/download" + } + } + ] +} + '''; + + final response = SearchPhotosResponse.fromJson(input)!; + expect(response.total, 133); + expect(response.totalPages, 7); + expect(response.results[0].id, 'eOLpJytrbsQ'); + expect(response.results[0].user!.id, 'Ul0QVz12Goo'); + }); + + group('Unsplash API client', () { + test('handles success', () async { + const searchPhotosResponseBody = ''' +{ + "total": 133, + "total_pages": 7, + "results": [ + { + "id": "eOLpJytrbsQ", + "created_at": "2014-11-18T14:35:36-05:00", + "width": 4000, + "height": 3000, + "color": "#A7A2A1", + "likes": 286, + "liked_by_user": false, + "description": "A man drinking a coffee.", + "user": { + "id": "Ul0QVz12Goo", + "username": "ugmonk", + "name": "Jeff Sheldon", + "first_name": "Jeff", + "last_name": "Sheldon", + "instagram_username": "instantgrammer", + "twitter_username": "ugmonk", + "portfolio_url": "http://ugmonk.com/", + "profile_image": { + "small": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=32&w=32&s=7cfe3b93750cb0c93e2f7caec08b5a41", + "medium": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=64&w=64&s=5a9dc749c43ce5bd60870b129a40902f", + "large": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=128&w=128&s=32085a077889586df88bfbe406692202" + }, + "links": { + "self": "https://api.unsplash.com/users/ugmonk", + "html": "http://unsplash.com/@ugmonk", + "photos": "https://api.unsplash.com/users/ugmonk/photos", + "likes": "https://api.unsplash.com/users/ugmonk/likes" + } + }, + "current_user_collections": [], + "urls": { + "raw": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f", + "full": "https://hd.unsplash.com/photo-1416339306562-f3d12fefd36f", + "regular": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=92f3e02f63678acc8416d044e189f515", + "small": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&s=263af33585f9d32af39d165b000845eb", + "thumb": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&s=8aae34cf35df31a592f0bef16e6342ef" + }, + "links": { + "self": "https://api.unsplash.com/photos/eOLpJytrbsQ", + "html": "http://unsplash.com/photos/eOLpJytrbsQ", + "download": "http://unsplash.com/photos/eOLpJytrbsQ/download" + } + } + ] +} + '''; + + final httpClient = MockClient((req) async { + return Response( + searchPhotosResponseBody, + 200, + request: req, + headers: {'content-type': 'application/json'}, + ); + }); + + final unsplashClient = Unsplash( + accessKey: 'not-an-access-key', + httpClient: httpClient, + ); + + final response = (await unsplashClient.searchPhotos(query: 'red'))!; + + expect(response.total, 133); + expect(response.totalPages, 7); + expect(response.results[0].id, 'eOLpJytrbsQ'); + expect(response.results[0].user!.id, 'Ul0QVz12Goo'); + }); + + test('handles failure', () async { + const apiError = + '{"errors":["OAuth error: The access token is invalid"]}'; + + final httpClient = MockClient((req) async { + return Response( + apiError, + 401, + request: req, + headers: {'content-type': 'application/json'}, + ); + }); + + final unsplashClient = Unsplash( + accessKey: 'not-an-access-key', + httpClient: httpClient, + ); + + try { + await unsplashClient.searchPhotos(query: 'red'); + fail('UnsplashException should have been thrown'); + } on UnsplashException catch (e) { + expect( + e.toString(), + 'UnsplashException: OAuth error: The access token is invalid', + ); + } + }); + + test('handles broken JSON', () async { + const apiError = '{"tot'; // truncated JSON. + + final httpClient = MockClient((req) async { + return Response( + apiError, + 401, + request: req, + headers: {'content-type': 'application/json'}, + ); + }); + + final unsplashClient = Unsplash( + accessKey: 'not-an-access-key', + httpClient: httpClient, + ); + + try { + await unsplashClient.searchPhotos(query: 'red'); + fail('UnsplashException should have been thrown'); + } on UnsplashException catch (e) { + expect(e.toString(), 'UnsplashException: Invalid JSON received'); + } + }); + }); + + test('handles utf8 content in JSON', () async { + const searchPhotosResponseBody = ''' +{ + "total": 22395, + "total_pages": 2240, + "results": [ + { + "id": "E4u_Zo9PET8", + "created_at": "2019-12-29T13:45:28-05:00", + "updated_at": "2020-11-05T17:12:18-05:00", + "promoted_at": null, + "width": 2598, + "height": 4618, + "color": "#A53E40", + "blur_hash": "LlO{8lL#XSbu*Jtla0jZOrb^ozjF", + "description": null, + "alt_description": "red apparel", + "urls": { + "raw": "https://images.unsplash.com/photo-1577645113639-32537a4a938b?ixlib=rb-1.2.1\u0026ixid=eyJhcHBfaWQiOjM5NTU5fQ", + "full": "https://images.unsplash.com/photo-1577645113639-32537a4a938b?ixlib=rb-1.2.1\u0026q=85\u0026fm=jpg\u0026crop=entropy\u0026cs=srgb\u0026ixid=eyJhcHBfaWQiOjM5NTU5fQ", + "regular": "https://images.unsplash.com/photo-1577645113639-32537a4a938b?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=entropy\u0026cs=tinysrgb\u0026w=1080\u0026fit=max\u0026ixid=eyJhcHBfaWQiOjM5NTU5fQ", + "small": "https://images.unsplash.com/photo-1577645113639-32537a4a938b?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=entropy\u0026cs=tinysrgb\u0026w=400\u0026fit=max\u0026ixid=eyJhcHBfaWQiOjM5NTU5fQ", + "thumb": "https://images.unsplash.com/photo-1577645113639-32537a4a938b?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=entropy\u0026cs=tinysrgb\u0026w=200\u0026fit=max\u0026ixid=eyJhcHBfaWQiOjM5NTU5fQ" + }, + "links": { + "self": "https://api.unsplash.com/photos/E4u_Zo9PET8", + "html": "https://unsplash.com/photos/E4u_Zo9PET8", + "download": "https://unsplash.com/photos/E4u_Zo9PET8/download", + "download_location": "https://api.unsplash.com/photos/E4u_Zo9PET8/download" + }, + "categories": [], + "likes": 132, + "liked_by_user": false, + "current_user_collections": [], + "sponsorship": null, + "user": { + "id": "_2nQcPrbyuE", + "updated_at": "2020-11-06T01:37:51-05:00", + "username": "svalenas", + "name": "Sergiu VΔƒlenaΘ™", + "first_name": "Sergiu", + "last_name": "VΔƒlenaΘ™", + "twitter_username": null, + "portfolio_url": null, + "bio": "Gifted psychologist and enthusiast photographer", + "location": "Cluj-Napoca, Romania", + "links": { + "self": "https://api.unsplash.com/users/svalenas", + "html": "https://unsplash.com/@svalenas", + "photos": "https://api.unsplash.com/users/svalenas/photos", + "likes": "https://api.unsplash.com/users/svalenas/likes", + "portfolio": "https://api.unsplash.com/users/svalenas/portfolio", + "following": "https://api.unsplash.com/users/svalenas/following", + "followers": "https://api.unsplash.com/users/svalenas/followers" + }, + "profile_image": { + "small": "https://images.unsplash.com/profile-1597067601066-d43176d68553image?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=faces\u0026cs=tinysrgb\u0026fit=crop\u0026h=32\u0026w=32", + "medium": "https://images.unsplash.com/profile-1597067601066-d43176d68553image?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=faces\u0026cs=tinysrgb\u0026fit=crop\u0026h=64\u0026w=64", + "large": "https://images.unsplash.com/profile-1597067601066-d43176d68553image?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=faces\u0026cs=tinysrgb\u0026fit=crop\u0026h=128\u0026w=128" + }, + "instagram_username": "svalenas", + "total_collections": 0, + "total_likes": 413, + "total_photos": 129, + "accepted_tos": true + }, + "tags": [ + { + "type": "landing_page", + "title": "red", + "source": { + "ancestry": { + "type": { + "slug": "wallpapers", + "pretty_slug": "HD Wallpapers" + }, + "category": { + "slug": "colors", + "pretty_slug": "Color" + }, + "subcategory": { + "slug": "red", + "pretty_slug": "Red" + } + }, + "title": "HD Red Wallpapers", + "subtitle": "Download Free Red Wallpapers", + "description": "Choose from a curated selection of red wallpapers for your mobile and desktop screens. Always free on Unsplash.", + "meta_title": "Red Wallpapers: Free HD Download [500+ HQ] | Unsplash", + "meta_description": "Choose from hundreds of free red wallpapers. Download HD wallpapers for free on Unsplash.", + "cover_photo": { + "id": "HyBXy5PHQR8", + "created_at": "2018-02-17T13:44:58-05:00", + "updated_at": "2020-10-21T01:07:42-04:00", + "promoted_at": null, + "width": 3024, + "height": 4032, + "color": "#C51918", + "blur_hash": "L9Bmx_o1o1Jl|cwxWpWpN]\$5N]sU", + "description": null, + "alt_description": "red textile", + "urls": { + "raw": "https://images.unsplash.com/photo-1518893063132-36e46dbe2428?ixlib=rb-1.2.1", + "full": "https://images.unsplash.com/photo-1518893063132-36e46dbe2428?ixlib=rb-1.2.1\u0026q=85\u0026fm=jpg\u0026crop=entropy\u0026cs=srgb", + "regular": "https://images.unsplash.com/photo-1518893063132-36e46dbe2428?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=entropy\u0026cs=tinysrgb\u0026w=1080\u0026fit=max", + "small": "https://images.unsplash.com/photo-1518893063132-36e46dbe2428?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=entropy\u0026cs=tinysrgb\u0026w=400\u0026fit=max", + "thumb": "https://images.unsplash.com/photo-1518893063132-36e46dbe2428?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=entropy\u0026cs=tinysrgb\u0026w=200\u0026fit=max" + }, + "links": { + "self": "https://api.unsplash.com/photos/HyBXy5PHQR8", + "html": "https://unsplash.com/photos/HyBXy5PHQR8", + "download": "https://unsplash.com/photos/HyBXy5PHQR8/download", + "download_location": "https://api.unsplash.com/photos/HyBXy5PHQR8/download" + }, + "categories": [], + "likes": 1243, + "liked_by_user": false, + "current_user_collections": [], + "sponsorship": null, + "user": { + "id": "6nkkrwW9M-s", + "updated_at": "2020-10-22T20:44:54-04:00", + "username": "montylov", + "name": "MontyLov", + "first_name": "MontyLov", + "last_name": null, + "twitter_username": "MontyLov", + "portfolio_url": "http://montylov.com", + "bio": "Stay humble and innovate.", + "location": null, + "links": { + "self": "https://api.unsplash.com/users/montylov", + "html": "https://unsplash.com/@montylov", + "photos": "https://api.unsplash.com/users/montylov/photos", + "likes": "https://api.unsplash.com/users/montylov/likes", + "portfolio": "https://api.unsplash.com/users/montylov/portfolio", + "following": "https://api.unsplash.com/users/montylov/following", + "followers": "https://api.unsplash.com/users/montylov/followers" + }, + "profile_image": { + "small": "https://images.unsplash.com/profile-1588478301600-b5e5203a574aimage?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=faces\u0026cs=tinysrgb\u0026fit=crop\u0026h=32\u0026w=32", + "medium": "https://images.unsplash.com/profile-1588478301600-b5e5203a574aimage?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=faces\u0026cs=tinysrgb\u0026fit=crop\u0026h=64\u0026w=64", + "large": "https://images.unsplash.com/profile-1588478301600-b5e5203a574aimage?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=faces\u0026cs=tinysrgb\u0026fit=crop\u0026h=128\u0026w=128" + }, + "instagram_username": "montylov", + "total_collections": 1, + "total_likes": 0, + "total_photos": 79, + "accepted_tos": false + } + } + } + }, + { + "type": "search", + "title": "rug" + }, + { + "type": "search", + "title": "plant" + } + ] + } + ] +} + '''; + + final httpClient = MockClient((req) async { + return Response( + searchPhotosResponseBody, + 200, + request: req, + headers: {'content-type': 'application/json; charset=utf-8'}, + ); + }); + + final unsplashClient = Unsplash( + accessKey: 'not-an-access-key', + httpClient: httpClient, + ); + + final response = (await unsplashClient.searchPhotos(query: 'red'))!; + + expect(response.total, 22395); + expect(response.totalPages, 2240); + expect(response.results[0].id, 'E4u_Zo9PET8'); + expect(response.results[0].user!.id, '_2nQcPrbyuE'); + expect(response.results[0].user!.name, 'Sergiu VΔƒlenaΘ™'); + }); +} diff --git a/desktop_photo_search/fluent_ui/test/widget_test.dart b/desktop_photo_search/fluent_ui/test/widget_test.dart new file mode 100644 index 0000000..d169c14 --- /dev/null +++ b/desktop_photo_search/fluent_ui/test/widget_test.dart @@ -0,0 +1,134 @@ +// 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. + +import 'dart:typed_data'; + +import 'package:desktop_photo_search/src/model/photo_search_model.dart'; +import 'package:desktop_photo_search/src/unsplash/photo.dart'; +import 'package:desktop_photo_search/src/unsplash/search_photos_response.dart'; +import 'package:desktop_photo_search/src/unsplash/unsplash.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; + +class FakeUnsplash implements Unsplash { + static const searchPhotosResponse = ''' +{ + "total": 133, + "total_pages": 7, + "results": [ + { + "id": "eOLpJytrbsQ", + "created_at": "2014-11-18T14:35:36-05:00", + "width": 4000, + "height": 3000, + "color": "#A7A2A1", + "likes": 286, + "liked_by_user": false, + "description": "A man drinking a coffee.", + "user": { + "id": "Ul0QVz12Goo", + "username": "ugmonk", + "name": "Jeff Sheldon", + "first_name": "Jeff", + "last_name": "Sheldon", + "instagram_username": "instantgrammer", + "twitter_username": "ugmonk", + "portfolio_url": "http://ugmonk.com/", + "profile_image": { + "small": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=32&w=32&s=7cfe3b93750cb0c93e2f7caec08b5a41", + "medium": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=64&w=64&s=5a9dc749c43ce5bd60870b129a40902f", + "large": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=128&w=128&s=32085a077889586df88bfbe406692202" + }, + "links": { + "self": "https://api.unsplash.com/users/ugmonk", + "html": "http://unsplash.com/@ugmonk", + "photos": "https://api.unsplash.com/users/ugmonk/photos", + "likes": "https://api.unsplash.com/users/ugmonk/likes" + } + }, + "current_user_collections": [], + "urls": { + "raw": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f", + "full": "https://hd.unsplash.com/photo-1416339306562-f3d12fefd36f", + "regular": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=92f3e02f63678acc8416d044e189f515", + "small": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&s=263af33585f9d32af39d165b000845eb", + "thumb": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&s=8aae34cf35df31a592f0bef16e6342ef" + }, + "links": { + "self": "https://api.unsplash.com/photos/eOLpJytrbsQ", + "html": "http://unsplash.com/photos/eOLpJytrbsQ", + "download": "http://unsplash.com/photos/eOLpJytrbsQ/download" + } + } + ] +} + '''; + + @override + Future searchPhotos({ + String? query, + num page = 1, + num perPage = 10, + List collections = const [], + SearchPhotosOrientation? orientation, + }) async { + return SearchPhotosResponse.fromJson(searchPhotosResponse); + } + + @override + Future download(Photo photo) { + throw UnimplementedError(); + } +} + +const fabKey = Key('fab'); + +class PhotoSearchModelTester extends StatelessWidget { + const PhotoSearchModelTester({required this.query, super.key}); + final String query; + @override + Widget build(BuildContext context) { + return MaterialApp( + home: TextButton( + key: fabKey, + onPressed: () async { + await Provider.of( + context, + listen: false, + ).addSearch(query); + }, + child: const Text('Search for a Photo'), + ), + ); + } +} + +void main() { + group('search_list', () { + testWidgets('starts empty', (tester) async { + final unsplashSearches = PhotoSearchModel(FakeUnsplash()); + final testWidget = ChangeNotifierProvider( + create: (context) => unsplashSearches, + child: const PhotoSearchModelTester(query: 'clouds'), + ); + await tester.pumpWidget(testWidget); + expect(unsplashSearches.entries.length, 0); + }); + + testWidgets('addSearch adds searches', (tester) async { + final unsplashSearches = PhotoSearchModel(FakeUnsplash()); + final testWidget = ChangeNotifierProvider( + create: (context) => unsplashSearches, + child: const PhotoSearchModelTester(query: 'clouds'), + ); + await tester.pumpWidget(testWidget); + await tester.tap(find.byKey(fabKey)); + await tester.tap(find.byKey(fabKey)); + await tester.pumpAndSettle(); + + expect(unsplashSearches.entries.length, 2); + }); + }); +} diff --git a/desktop_photo_search/fluent_ui/tool/grind.dart b/desktop_photo_search/fluent_ui/tool/grind.dart new file mode 100644 index 0000000..a672fec --- /dev/null +++ b/desktop_photo_search/fluent_ui/tool/grind.dart @@ -0,0 +1,55 @@ +// 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. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:async/async.dart' show StreamGroup; +import 'package:grinder/grinder.dart'; + +void main(List args) => grind(args); + +@DefaultTask() +@Depends(pubGet, generateJsonBindings, analyzeSource, test) +void build() {} + +@Task() +Future pubGet() async => + _logProcessOutput(Process.start('flutter', ['pub', 'get'])); + +@Task() +Future generateJsonBindings() async => _logProcessOutput( + Process.start('flutter', [ + 'pub', + 'run', + 'build_runner', + 'build', + '--delete-conflicting-outputs', + ]), +); + +@Task() +Future watch() async => _logProcessOutput( + Process.start('dart', ['run', 'build_runner', 'watch']), +); + +@Task() +Future analyzeSource() async => + _logProcessOutput(Process.start('flutter', ['analyze'])); + +@Task() +Future test() async => + _logProcessOutput(Process.start('flutter', ['test'])); + +@Task() +Future clean() => _logProcessOutput(Process.start('flutter', ['clean'])); + +Future _logProcessOutput(Future proc) async { + final process = await proc; + final output = StreamGroup.merge([process.stdout, process.stderr]); + await for (final message in output) { + log(utf8.decode(message)); + } +} diff --git a/desktop_photo_search/fluent_ui/windows/.gitignore b/desktop_photo_search/fluent_ui/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/desktop_photo_search/fluent_ui/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/desktop_photo_search/fluent_ui/windows/CMakeLists.txt b/desktop_photo_search/fluent_ui/windows/CMakeLists.txt new file mode 100644 index 0000000..f3415cf --- /dev/null +++ b/desktop_photo_search/fluent_ui/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(desktop_photo_search LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "desktop_photo_search") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/desktop_photo_search/fluent_ui/windows/flutter/CMakeLists.txt b/desktop_photo_search/fluent_ui/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/desktop_photo_search/fluent_ui/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/desktop_photo_search/fluent_ui/windows/flutter/generated_plugin_registrant.cc b/desktop_photo_search/fluent_ui/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..b16c80c --- /dev/null +++ b/desktop_photo_search/fluent_ui/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,20 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); + MenubarPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("MenubarPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/desktop_photo_search/fluent_ui/windows/flutter/generated_plugin_registrant.h b/desktop_photo_search/fluent_ui/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/desktop_photo_search/fluent_ui/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/desktop_photo_search/fluent_ui/windows/flutter/generated_plugins.cmake b/desktop_photo_search/fluent_ui/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..31159ac --- /dev/null +++ b/desktop_photo_search/fluent_ui/windows/flutter/generated_plugins.cmake @@ -0,0 +1,26 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + file_selector_windows + menubar + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/desktop_photo_search/fluent_ui/windows/runner/CMakeLists.txt b/desktop_photo_search/fluent_ui/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/desktop_photo_search/fluent_ui/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/desktop_photo_search/fluent_ui/windows/runner/Runner.rc b/desktop_photo_search/fluent_ui/windows/runner/Runner.rc new file mode 100644 index 0000000..fc74684 --- /dev/null +++ b/desktop_photo_search/fluent_ui/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "desktop_photo_search" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "desktop_photo_search" "\0" + VALUE "LegalCopyright", "Copyright (C) 2023 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "desktop_photo_search.exe" "\0" + VALUE "ProductName", "desktop_photo_search" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/desktop_photo_search/fluent_ui/windows/runner/flutter_window.cpp b/desktop_photo_search/fluent_ui/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/desktop_photo_search/fluent_ui/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/desktop_photo_search/fluent_ui/windows/runner/flutter_window.h b/desktop_photo_search/fluent_ui/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/desktop_photo_search/fluent_ui/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/desktop_photo_search/fluent_ui/windows/runner/main.cpp b/desktop_photo_search/fluent_ui/windows/runner/main.cpp new file mode 100644 index 0000000..23aadea --- /dev/null +++ b/desktop_photo_search/fluent_ui/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"desktop_photo_search", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/desktop_photo_search/fluent_ui/windows/runner/resource.h b/desktop_photo_search/fluent_ui/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/desktop_photo_search/fluent_ui/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/desktop_photo_search/fluent_ui/windows/runner/resources/app_icon.ico b/desktop_photo_search/fluent_ui/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/desktop_photo_search/fluent_ui/windows/runner/resources/app_icon.ico differ diff --git a/desktop_photo_search/fluent_ui/windows/runner/runner.exe.manifest b/desktop_photo_search/fluent_ui/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..a42ea76 --- /dev/null +++ b/desktop_photo_search/fluent_ui/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/desktop_photo_search/fluent_ui/windows/runner/utils.cpp b/desktop_photo_search/fluent_ui/windows/runner/utils.cpp new file mode 100644 index 0000000..b2b0873 --- /dev/null +++ b/desktop_photo_search/fluent_ui/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/desktop_photo_search/fluent_ui/windows/runner/utils.h b/desktop_photo_search/fluent_ui/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/desktop_photo_search/fluent_ui/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/desktop_photo_search/fluent_ui/windows/runner/win32_window.cpp b/desktop_photo_search/fluent_ui/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/desktop_photo_search/fluent_ui/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/desktop_photo_search/fluent_ui/windows/runner/win32_window.h b/desktop_photo_search/fluent_ui/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/desktop_photo_search/fluent_ui/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/desktop_photo_search/material/.gitignore b/desktop_photo_search/material/.gitignore new file mode 100644 index 0000000..0fa6b67 --- /dev/null +++ b/desktop_photo_search/material/.gitignore @@ -0,0 +1,46 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/desktop_photo_search/material/.metadata b/desktop_photo_search/material/.metadata new file mode 100644 index 0000000..16c0689 --- /dev/null +++ b/desktop_photo_search/material/.metadata @@ -0,0 +1,36 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "db7ef5bf9f59442b0e200a90587e8fa5e0c6336a" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: linux + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: macos + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: windows + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/desktop_photo_search/material/README.md b/desktop_photo_search/material/README.md new file mode 100644 index 0000000..73ccda0 --- /dev/null +++ b/desktop_photo_search/material/README.md @@ -0,0 +1,64 @@ +# Photo Search app + +This desktop application enables you to search +[Unsplash](https://unsplash.com/) for photographs that interest you. +To use it, you need to add an **Access Key** from +[Unsplash API](https://unsplash.com/developers) to +`lib/unsplash_access_key.dart`. + +This sample works on Windows, macOS and Linux. + +## A quick tour of the code + +This Flutter project builds a desktop application. It utilises the following +desktop specific plugins: + + - [file_chooser] to enable the application user to select where to save a photo + from the Unsplash API. + - [menubar] for exposing Image Search functionality through the menu bar. + - [url_launcher] to open external links. + +The Unsplash API client entry point is in the [Unsplash] class, and is built +atop [http], [built_value] and [built_collection] for JSON Rest API access. + +## Deploying to the Microsoft Store + +This sample uses Yehuda Kremer's [MSIX pub package] to bundle up the Windows +release build for distribution to the [Microsoft Store]. Microsoft maintains +a [plethora of documentation][ms_store_publishing_doc] on deploying to the +Microsoft Store. + +See the `msix_config` stanza in this sample's `pubspec.yaml` for an +example configuration. Make sure the `display_name`, `publisher_display_name`, +`publisher` and `identity_name` attributes match the settings in your +Microsoft Partner Center application submission. + +## macOS Network and File entitlements + +To access the network, macOS requires applications enable the +[com.apple.security.network.client entitlement][macOS-client]. For this +sample, this entitlement is required to access the Unsplash API. + +Likewise, to save a Photo to the local file system using the `file_chooser` plugin requires the +[com.apple.security.files.user-selected.read-write entitlement][macOS-read-write]. + +Please see [macOS Signing and Security][macOS-security] for more detail. + + +[Unsplash]: lib/src/unsplash/unsplash.dart + +[built_collection]: https://pub.dev/packages/built_collection +[built_value]: https://pub.dev/packages/built_value +[file_chooser]: https://github.com/google/flutter-desktop-embedding/tree/master/plugins/file_chooser +[flutter_channels]: https://github.com/flutter/flutter/blob/master/docs/releases/Flutter-build-release-channels.md +[http]: https://pub.dev/packages/http +[macOS-client]: https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_network_client +[macOS-read-write]: https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_files_user-selected_read-write +[macOS-security]: https://github.com/google/flutter-desktop-embedding/blob/master/macOS-Security.md +[menubar]: https://github.com/google/flutter-desktop-embedding/tree/master/plugins/menubar +[setup documentation]: https://flutter.dev/desktop#set-up +[url_launcher]: https://pub.dev/packages/url_launcher + +[MSIX pub package]: https://pub.dev/packages/msix +[Microsoft Store]: https://www.microsoft.com/en-au/p/flutter-desktop-photo-search/9nh719dxcpj4 +[ms_store_publishing_doc]: https://docs.microsoft.com/en-us/windows/uwp/publish/ diff --git a/desktop_photo_search/material/analysis_options.yaml b/desktop_photo_search/material/analysis_options.yaml new file mode 100644 index 0000000..511bdba --- /dev/null +++ b/desktop_photo_search/material/analysis_options.yaml @@ -0,0 +1,5 @@ +include: ../../analysis_options.yaml + +linter: + rules: + sort_pub_dependencies: true diff --git a/desktop_photo_search/material/lib/main.dart b/desktop_photo_search/material/lib/main.dart new file mode 100644 index 0000000..d2fdad6 --- /dev/null +++ b/desktop_photo_search/material/lib/main.dart @@ -0,0 +1,127 @@ +// 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. + +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:logging/logging.dart'; +import 'package:menubar/menubar.dart' as menubar; +import 'package:provider/provider.dart'; + +import 'src/model/photo_search_model.dart'; +import 'src/unsplash/unsplash.dart'; +import 'src/widgets/photo_search_dialog.dart'; +import 'src/widgets/policy_dialog.dart'; +import 'src/widgets/unsplash_notice.dart'; +import 'src/widgets/unsplash_search_content.dart'; +import 'unsplash_access_key.dart'; + +void main() { + Logger.root.level = Level.ALL; + Logger.root.onRecord.listen((rec) { + // ignore: avoid_print + print( + '${rec.loggerName} ${rec.level.name}: ${rec.time}: ${rec.message}', + ); + }); + + if (unsplashAccessKey.isEmpty) { + Logger('main').severe( + 'Unsplash Access Key is required. ' + 'Please add to `lib/unsplash_access_key.dart`.', + ); + exit(1); + } + + runApp( + ChangeNotifierProvider( + create: (context) => + PhotoSearchModel(Unsplash(accessKey: unsplashAccessKey)), + child: const UnsplashSearchApp(), + ), + ); +} + +class UnsplashSearchApp extends StatelessWidget { + const UnsplashSearchApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Photo Search', + theme: ThemeData(colorSchemeSeed: Colors.orange), + home: const UnsplashHomePage(title: 'Photo Search'), + ); + } +} + +class UnsplashHomePage extends StatelessWidget { + const UnsplashHomePage({required this.title, super.key}); + final String title; + + @override + Widget build(BuildContext context) { + final photoSearchModel = Provider.of(context); + menubar.setApplicationMenu([ + menubar.NativeSubmenu( + label: 'Search', + children: [ + menubar.NativeMenuItem( + label: 'Search…', + onSelected: () { + showDialog( + context: context, + builder: (context) => PhotoSearchDialog( + callback: photoSearchModel.addSearch, + ), + ); + }, + ), + if (!Platform.isMacOS) + menubar.NativeMenuItem( + label: 'Quit', + onSelected: () { + SystemNavigator.pop(); + }, + ), + ], + ), + menubar.NativeSubmenu( + label: 'About', + children: [ + menubar.NativeMenuItem( + label: 'About', + onSelected: () { + showDialog( + context: context, + builder: (context) => const PolicyDialog(), + ); + }, + ), + ], + ), + ]); + + return UnsplashNotice( + child: Scaffold( + appBar: AppBar(title: Text(title)), + body: photoSearchModel.entries.isNotEmpty + ? const UnsplashSearchContent() + : const Center( + child: Text('Search for Photos using the Fab button'), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => showDialog( + context: context, + builder: (context) => + PhotoSearchDialog(callback: photoSearchModel.addSearch), + ), + tooltip: 'Search for a photo', + child: const Icon(Icons.search), + ), + ), + ); + } +} diff --git a/desktop_photo_search/material/lib/src/model/photo_search_model.dart b/desktop_photo_search/material/lib/src/model/photo_search_model.dart new file mode 100644 index 0000000..c3b1878 --- /dev/null +++ b/desktop_photo_search/material/lib/src/model/photo_search_model.dart @@ -0,0 +1,50 @@ +// 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. + +import 'package:flutter/foundation.dart'; + +import '../unsplash/photo.dart'; +import '../unsplash/unsplash.dart'; +import 'search.dart'; + +class SearchEntry { + const SearchEntry(this.query, this.photos, this.model); + final String query; + final List photos; + final PhotoSearchModel model; +} + +class PhotoSearchModel extends ChangeNotifier { + PhotoSearchModel(this._client); + final Unsplash _client; + + List get entries => List.unmodifiable(_entries); + final List _entries = []; + + Photo? get selectedPhoto => _selectedPhoto; + set selectedPhoto(Photo? photo) { + _selectedPhoto = photo; + notifyListeners(); + } + + Photo? _selectedPhoto; + + Future addSearch(String query) async { + final result = await _client.searchPhotos( + query: query, + orientation: SearchPhotosOrientation.portrait, + ); + final search = Search((s) { + s + ..query = query + ..results.addAll(result!.results); + }); + + _entries.add(SearchEntry(query, search.results.toList(), this)); + notifyListeners(); + } + + Future download({required Photo photo}) => + _client.download(photo); +} diff --git a/desktop_photo_search/material/lib/src/model/search.dart b/desktop_photo_search/material/lib/src/model/search.dart new file mode 100644 index 0000000..4b85be8 --- /dev/null +++ b/desktop_photo_search/material/lib/src/model/search.dart @@ -0,0 +1,38 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_collection/built_collection.dart'; +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; +import '../unsplash/photo.dart'; + +part 'search.g.dart'; + +abstract class Search implements Built { + factory Search([void Function(SearchBuilder)? updates]) = _$Search; + Search._(); + + @BuiltValueField(wireName: 'query') + String get query; + + @BuiltValueField(wireName: 'results') + BuiltList get results; + + String toJson() { + return json.encode(serializers.serializeWith(Search.serializer, this)); + } + + static Search? fromJson(String jsonString) { + return serializers.deserializeWith( + Search.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => _$searchSerializer; +} diff --git a/desktop_photo_search/material/lib/src/model/search.g.dart b/desktop_photo_search/material/lib/src/model/search.g.dart new file mode 100644 index 0000000..578c88f --- /dev/null +++ b/desktop_photo_search/material/lib/src/model/search.g.dart @@ -0,0 +1,200 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'search.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$searchSerializer = new _$SearchSerializer(); + +class _$SearchSerializer implements StructuredSerializer { + @override + final Iterable types = const [Search, _$Search]; + @override + final String wireName = 'Search'; + + @override + Iterable serialize( + Serializers serializers, + Search object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = [ + 'query', + serializers.serialize( + object.query, + specifiedType: const FullType(String), + ), + 'results', + serializers.serialize( + object.results, + specifiedType: const FullType(BuiltList, const [ + const FullType(Photo), + ]), + ), + ]; + + return result; + } + + @override + Search deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new SearchBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'query': + result.query = + serializers.deserialize( + value, + specifiedType: const FullType(String), + )! + as String; + break; + case 'results': + result.results.replace( + serializers.deserialize( + value, + specifiedType: const FullType(BuiltList, const [ + const FullType(Photo), + ]), + )! + as BuiltList, + ); + break; + } + } + + return result.build(); + } +} + +class _$Search extends Search { + @override + final String query; + @override + final BuiltList results; + + factory _$Search([void Function(SearchBuilder)? updates]) => + (new SearchBuilder()..update(updates))._build(); + + _$Search._({required this.query, required this.results}) : super._() { + BuiltValueNullFieldError.checkNotNull(query, r'Search', 'query'); + BuiltValueNullFieldError.checkNotNull(results, r'Search', 'results'); + } + + @override + Search rebuild(void Function(SearchBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + SearchBuilder toBuilder() => new SearchBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Search && query == other.query && results == other.results; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, query.hashCode); + _$hash = $jc(_$hash, results.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'Search') + ..add('query', query) + ..add('results', results)) + .toString(); + } +} + +class SearchBuilder implements Builder { + _$Search? _$v; + + String? _query; + String? get query => _$this._query; + set query(String? query) => _$this._query = query; + + ListBuilder? _results; + ListBuilder get results => + _$this._results ??= new ListBuilder(); + set results(ListBuilder? results) => _$this._results = results; + + SearchBuilder(); + + SearchBuilder get _$this { + final $v = _$v; + if ($v != null) { + _query = $v.query; + _results = $v.results.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(Search other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$Search; + } + + @override + void update(void Function(SearchBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + Search build() => _build(); + + _$Search _build() { + _$Search _$result; + try { + _$result = + _$v ?? + new _$Search._( + query: BuiltValueNullFieldError.checkNotNull( + query, + r'Search', + 'query', + ), + results: results.build(), + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'results'; + results.build(); + } catch (e) { + throw new BuiltValueNestedFieldError( + r'Search', + _$failedField, + e.toString(), + ); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/material/lib/src/serializers.dart b/desktop_photo_search/material/lib/src/serializers.dart new file mode 100644 index 0000000..6153a3d --- /dev/null +++ b/desktop_photo_search/material/lib/src/serializers.dart @@ -0,0 +1,27 @@ +// 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. + +import 'package:built_collection/built_collection.dart'; +import 'package:built_value/serializer.dart'; +import 'package:built_value/standard_json_plugin.dart'; + +import 'model/search.dart'; +import 'unsplash/api_error.dart'; +import 'unsplash/current_user_collections.dart'; +import 'unsplash/exif.dart'; +import 'unsplash/links.dart'; +import 'unsplash/location.dart'; +import 'unsplash/photo.dart'; +import 'unsplash/position.dart'; +import 'unsplash/search_photos_response.dart'; +import 'unsplash/tags.dart'; +import 'unsplash/urls.dart'; +import 'unsplash/user.dart'; + +part 'serializers.g.dart'; + +//add all of the built value types that require serialization +@SerializersFor([Search, ApiError, Photo, SearchPhotosResponse]) +final Serializers serializers = + (_$serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build(); diff --git a/desktop_photo_search/material/lib/src/serializers.g.dart b/desktop_photo_search/material/lib/src/serializers.g.dart new file mode 100644 index 0000000..f2d7986 --- /dev/null +++ b/desktop_photo_search/material/lib/src/serializers.g.dart @@ -0,0 +1,51 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'serializers.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializers _$serializers = + (new Serializers().toBuilder() + ..add(ApiError.serializer) + ..add(CurrentUserCollections.serializer) + ..add(Exif.serializer) + ..add(Links.serializer) + ..add(Location.serializer) + ..add(Photo.serializer) + ..add(Position.serializer) + ..add(Search.serializer) + ..add(SearchPhotosResponse.serializer) + ..add(Tags.serializer) + ..add(Urls.serializer) + ..add(User.serializer) + ..addBuilderFactory( + const FullType(BuiltList, const [const FullType(Photo)]), + () => new ListBuilder(), + ) + ..addBuilderFactory( + const FullType(BuiltList, const [const FullType(Photo)]), + () => new ListBuilder(), + ) + ..addBuilderFactory( + const FullType(BuiltList, const [const FullType(String)]), + () => new ListBuilder(), + ) + ..addBuilderFactory( + const FullType(BuiltList, const [const FullType(Tags)]), + () => new ListBuilder(), + ) + ..addBuilderFactory( + const FullType(BuiltList, const [ + const FullType(CurrentUserCollections), + ]), + () => new ListBuilder(), + )) + .build(); + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/material/lib/src/unsplash/api_error.dart b/desktop_photo_search/material/lib/src/unsplash/api_error.dart new file mode 100644 index 0000000..e7607c0 --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/api_error.dart @@ -0,0 +1,37 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_collection/built_collection.dart'; +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; + +part 'api_error.g.dart'; + +abstract class ApiError implements Built { + factory ApiError([void Function(ApiErrorBuilder)? updates]) = _$ApiError; + + ApiError._(); + + @BuiltValueField(wireName: 'errors') + BuiltList? get errors; + + String toJson() { + return json.encode( + serializers.serializeWith(ApiError.serializer, this), + ); + } + + static ApiError? fromJson(String jsonString) { + return serializers.deserializeWith( + ApiError.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => _$apiErrorSerializer; +} diff --git a/desktop_photo_search/material/lib/src/unsplash/api_error.g.dart b/desktop_photo_search/material/lib/src/unsplash/api_error.g.dart new file mode 100644 index 0000000..acc2aab --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/api_error.g.dart @@ -0,0 +1,171 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'api_error.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$apiErrorSerializer = new _$ApiErrorSerializer(); + +class _$ApiErrorSerializer implements StructuredSerializer { + @override + final Iterable types = const [ApiError, _$ApiError]; + @override + final String wireName = 'ApiError'; + + @override + Iterable serialize( + Serializers serializers, + ApiError object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = []; + Object? value; + value = object.errors; + if (value != null) { + result + ..add('errors') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(BuiltList, const [ + const FullType(String), + ]), + ), + ); + } + return result; + } + + @override + ApiError deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new ApiErrorBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'errors': + result.errors.replace( + serializers.deserialize( + value, + specifiedType: const FullType(BuiltList, const [ + const FullType(String), + ]), + )! + as BuiltList, + ); + break; + } + } + + return result.build(); + } +} + +class _$ApiError extends ApiError { + @override + final BuiltList? errors; + + factory _$ApiError([void Function(ApiErrorBuilder)? updates]) => + (new ApiErrorBuilder()..update(updates))._build(); + + _$ApiError._({this.errors}) : super._(); + + @override + ApiError rebuild(void Function(ApiErrorBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + ApiErrorBuilder toBuilder() => new ApiErrorBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is ApiError && errors == other.errors; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, errors.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper( + r'ApiError', + )..add('errors', errors)).toString(); + } +} + +class ApiErrorBuilder implements Builder { + _$ApiError? _$v; + + ListBuilder? _errors; + ListBuilder get errors => + _$this._errors ??= new ListBuilder(); + set errors(ListBuilder? errors) => _$this._errors = errors; + + ApiErrorBuilder(); + + ApiErrorBuilder get _$this { + final $v = _$v; + if ($v != null) { + _errors = $v.errors?.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(ApiError other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$ApiError; + } + + @override + void update(void Function(ApiErrorBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + ApiError build() => _build(); + + _$ApiError _build() { + _$ApiError _$result; + try { + _$result = _$v ?? new _$ApiError._(errors: _errors?.build()); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'errors'; + _errors?.build(); + } catch (e) { + throw new BuiltValueNestedFieldError( + r'ApiError', + _$failedField, + e.toString(), + ); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/material/lib/src/unsplash/current_user_collections.dart b/desktop_photo_search/material/lib/src/unsplash/current_user_collections.dart new file mode 100644 index 0000000..610ae21 --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/current_user_collections.dart @@ -0,0 +1,49 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; + +part 'current_user_collections.g.dart'; + +abstract class CurrentUserCollections + implements Built { + factory CurrentUserCollections([ + void Function(CurrentUserCollectionsBuilder)? updates, + ]) = _$CurrentUserCollections; + + CurrentUserCollections._(); + + @BuiltValueField(wireName: 'id') + int get id; + + @BuiltValueField(wireName: 'title') + String? get title; + + @BuiltValueField(wireName: 'published_at') + String? get publishedAt; + + @BuiltValueField(wireName: 'updated_at') + String? get updatedAt; + + String toJson() { + return json.encode( + serializers.serializeWith(CurrentUserCollections.serializer, this), + ); + } + + static CurrentUserCollections? fromJson(String jsonString) { + return serializers.deserializeWith( + CurrentUserCollections.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => + _$currentUserCollectionsSerializer; +} diff --git a/desktop_photo_search/material/lib/src/unsplash/current_user_collections.g.dart b/desktop_photo_search/material/lib/src/unsplash/current_user_collections.g.dart new file mode 100644 index 0000000..f37c6c9 --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/current_user_collections.g.dart @@ -0,0 +1,260 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'current_user_collections.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$currentUserCollectionsSerializer = + new _$CurrentUserCollectionsSerializer(); + +class _$CurrentUserCollectionsSerializer + implements StructuredSerializer { + @override + final Iterable types = const [ + CurrentUserCollections, + _$CurrentUserCollections, + ]; + @override + final String wireName = 'CurrentUserCollections'; + + @override + Iterable serialize( + Serializers serializers, + CurrentUserCollections object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = [ + 'id', + serializers.serialize(object.id, specifiedType: const FullType(int)), + ]; + Object? value; + value = object.title; + if (value != null) { + result + ..add('title') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.publishedAt; + if (value != null) { + result + ..add('published_at') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.updatedAt; + if (value != null) { + result + ..add('updated_at') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + return result; + } + + @override + CurrentUserCollections deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new CurrentUserCollectionsBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'id': + result.id = + serializers.deserialize( + value, + specifiedType: const FullType(int), + )! + as int; + break; + case 'title': + result.title = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'published_at': + result.publishedAt = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'updated_at': + result.updatedAt = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + } + } + + return result.build(); + } +} + +class _$CurrentUserCollections extends CurrentUserCollections { + @override + final int id; + @override + final String? title; + @override + final String? publishedAt; + @override + final String? updatedAt; + + factory _$CurrentUserCollections([ + void Function(CurrentUserCollectionsBuilder)? updates, + ]) => (new CurrentUserCollectionsBuilder()..update(updates))._build(); + + _$CurrentUserCollections._({ + required this.id, + this.title, + this.publishedAt, + this.updatedAt, + }) : super._() { + BuiltValueNullFieldError.checkNotNull( + id, + r'CurrentUserCollections', + 'id', + ); + } + + @override + CurrentUserCollections rebuild( + void Function(CurrentUserCollectionsBuilder) updates, + ) => (toBuilder()..update(updates)).build(); + + @override + CurrentUserCollectionsBuilder toBuilder() => + new CurrentUserCollectionsBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is CurrentUserCollections && + id == other.id && + title == other.title && + publishedAt == other.publishedAt && + updatedAt == other.updatedAt; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, id.hashCode); + _$hash = $jc(_$hash, title.hashCode); + _$hash = $jc(_$hash, publishedAt.hashCode); + _$hash = $jc(_$hash, updatedAt.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'CurrentUserCollections') + ..add('id', id) + ..add('title', title) + ..add('publishedAt', publishedAt) + ..add('updatedAt', updatedAt)) + .toString(); + } +} + +class CurrentUserCollectionsBuilder + implements Builder { + _$CurrentUserCollections? _$v; + + int? _id; + int? get id => _$this._id; + set id(int? id) => _$this._id = id; + + String? _title; + String? get title => _$this._title; + set title(String? title) => _$this._title = title; + + String? _publishedAt; + String? get publishedAt => _$this._publishedAt; + set publishedAt(String? publishedAt) => _$this._publishedAt = publishedAt; + + String? _updatedAt; + String? get updatedAt => _$this._updatedAt; + set updatedAt(String? updatedAt) => _$this._updatedAt = updatedAt; + + CurrentUserCollectionsBuilder(); + + CurrentUserCollectionsBuilder get _$this { + final $v = _$v; + if ($v != null) { + _id = $v.id; + _title = $v.title; + _publishedAt = $v.publishedAt; + _updatedAt = $v.updatedAt; + _$v = null; + } + return this; + } + + @override + void replace(CurrentUserCollections other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$CurrentUserCollections; + } + + @override + void update(void Function(CurrentUserCollectionsBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + CurrentUserCollections build() => _build(); + + _$CurrentUserCollections _build() { + final _$result = + _$v ?? + new _$CurrentUserCollections._( + id: BuiltValueNullFieldError.checkNotNull( + id, + r'CurrentUserCollections', + 'id', + ), + title: title, + publishedAt: publishedAt, + updatedAt: updatedAt, + ); + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/material/lib/src/unsplash/exif.dart b/desktop_photo_search/material/lib/src/unsplash/exif.dart new file mode 100644 index 0000000..9eb505d --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/exif.dart @@ -0,0 +1,49 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; + +part 'exif.g.dart'; + +abstract class Exif implements Built { + factory Exif([void Function(ExifBuilder)? updates]) = _$Exif; + + Exif._(); + + @BuiltValueField(wireName: 'make') + String? get make; + + @BuiltValueField(wireName: 'model') + String? get model; + + @BuiltValueField(wireName: 'exposure_time') + String? get exposureTime; + + @BuiltValueField(wireName: 'aperture') + String? get aperture; + + @BuiltValueField(wireName: 'focal_length') + String? get focalLength; + + @BuiltValueField(wireName: 'iso') + int? get iso; + + String toJson() { + return json.encode(serializers.serializeWith(Exif.serializer, this)); + } + + static Exif? fromJson(String jsonString) { + return serializers.deserializeWith( + Exif.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => _$exifSerializer; +} diff --git a/desktop_photo_search/material/lib/src/unsplash/exif.g.dart b/desktop_photo_search/material/lib/src/unsplash/exif.g.dart new file mode 100644 index 0000000..1896c86 --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/exif.g.dart @@ -0,0 +1,309 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'exif.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$exifSerializer = new _$ExifSerializer(); + +class _$ExifSerializer implements StructuredSerializer { + @override + final Iterable types = const [Exif, _$Exif]; + @override + final String wireName = 'Exif'; + + @override + Iterable serialize( + Serializers serializers, + Exif object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = []; + Object? value; + value = object.make; + if (value != null) { + result + ..add('make') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.model; + if (value != null) { + result + ..add('model') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.exposureTime; + if (value != null) { + result + ..add('exposure_time') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.aperture; + if (value != null) { + result + ..add('aperture') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.focalLength; + if (value != null) { + result + ..add('focal_length') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.iso; + if (value != null) { + result + ..add('iso') + ..add( + serializers.serialize(value, specifiedType: const FullType(int)), + ); + } + return result; + } + + @override + Exif deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new ExifBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'make': + result.make = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'model': + result.model = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'exposure_time': + result.exposureTime = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'aperture': + result.aperture = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'focal_length': + result.focalLength = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'iso': + result.iso = + serializers.deserialize( + value, + specifiedType: const FullType(int), + ) + as int?; + break; + } + } + + return result.build(); + } +} + +class _$Exif extends Exif { + @override + final String? make; + @override + final String? model; + @override + final String? exposureTime; + @override + final String? aperture; + @override + final String? focalLength; + @override + final int? iso; + + factory _$Exif([void Function(ExifBuilder)? updates]) => + (new ExifBuilder()..update(updates))._build(); + + _$Exif._({ + this.make, + this.model, + this.exposureTime, + this.aperture, + this.focalLength, + this.iso, + }) : super._(); + + @override + Exif rebuild(void Function(ExifBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + ExifBuilder toBuilder() => new ExifBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Exif && + make == other.make && + model == other.model && + exposureTime == other.exposureTime && + aperture == other.aperture && + focalLength == other.focalLength && + iso == other.iso; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, make.hashCode); + _$hash = $jc(_$hash, model.hashCode); + _$hash = $jc(_$hash, exposureTime.hashCode); + _$hash = $jc(_$hash, aperture.hashCode); + _$hash = $jc(_$hash, focalLength.hashCode); + _$hash = $jc(_$hash, iso.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'Exif') + ..add('make', make) + ..add('model', model) + ..add('exposureTime', exposureTime) + ..add('aperture', aperture) + ..add('focalLength', focalLength) + ..add('iso', iso)) + .toString(); + } +} + +class ExifBuilder implements Builder { + _$Exif? _$v; + + String? _make; + String? get make => _$this._make; + set make(String? make) => _$this._make = make; + + String? _model; + String? get model => _$this._model; + set model(String? model) => _$this._model = model; + + String? _exposureTime; + String? get exposureTime => _$this._exposureTime; + set exposureTime(String? exposureTime) => + _$this._exposureTime = exposureTime; + + String? _aperture; + String? get aperture => _$this._aperture; + set aperture(String? aperture) => _$this._aperture = aperture; + + String? _focalLength; + String? get focalLength => _$this._focalLength; + set focalLength(String? focalLength) => _$this._focalLength = focalLength; + + int? _iso; + int? get iso => _$this._iso; + set iso(int? iso) => _$this._iso = iso; + + ExifBuilder(); + + ExifBuilder get _$this { + final $v = _$v; + if ($v != null) { + _make = $v.make; + _model = $v.model; + _exposureTime = $v.exposureTime; + _aperture = $v.aperture; + _focalLength = $v.focalLength; + _iso = $v.iso; + _$v = null; + } + return this; + } + + @override + void replace(Exif other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$Exif; + } + + @override + void update(void Function(ExifBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + Exif build() => _build(); + + _$Exif _build() { + final _$result = + _$v ?? + new _$Exif._( + make: make, + model: model, + exposureTime: exposureTime, + aperture: aperture, + focalLength: focalLength, + iso: iso, + ); + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/material/lib/src/unsplash/links.dart b/desktop_photo_search/material/lib/src/unsplash/links.dart new file mode 100644 index 0000000..20b1018 --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/links.dart @@ -0,0 +1,43 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; + +part 'links.g.dart'; + +abstract class Links implements Built { + factory Links([void Function(LinksBuilder)? updates]) = _$Links; + + Links._(); + + @BuiltValueField(wireName: 'self') + String? get self; + + @BuiltValueField(wireName: 'html') + String? get html; + + @BuiltValueField(wireName: 'download') + String? get download; + + @BuiltValueField(wireName: 'download_location') + String? get downloadLocation; + + String toJson() { + return json.encode(serializers.serializeWith(Links.serializer, this)); + } + + static Links? fromJson(String jsonString) { + return serializers.deserializeWith( + Links.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => _$linksSerializer; +} diff --git a/desktop_photo_search/material/lib/src/unsplash/links.g.dart b/desktop_photo_search/material/lib/src/unsplash/links.g.dart new file mode 100644 index 0000000..5645e7e --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/links.g.dart @@ -0,0 +1,246 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'links.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$linksSerializer = new _$LinksSerializer(); + +class _$LinksSerializer implements StructuredSerializer { + @override + final Iterable types = const [Links, _$Links]; + @override + final String wireName = 'Links'; + + @override + Iterable serialize( + Serializers serializers, + Links object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = []; + Object? value; + value = object.self; + if (value != null) { + result + ..add('self') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.html; + if (value != null) { + result + ..add('html') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.download; + if (value != null) { + result + ..add('download') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.downloadLocation; + if (value != null) { + result + ..add('download_location') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + return result; + } + + @override + Links deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new LinksBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'self': + result.self = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'html': + result.html = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'download': + result.download = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'download_location': + result.downloadLocation = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + } + } + + return result.build(); + } +} + +class _$Links extends Links { + @override + final String? self; + @override + final String? html; + @override + final String? download; + @override + final String? downloadLocation; + + factory _$Links([void Function(LinksBuilder)? updates]) => + (new LinksBuilder()..update(updates))._build(); + + _$Links._({this.self, this.html, this.download, this.downloadLocation}) + : super._(); + + @override + Links rebuild(void Function(LinksBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + LinksBuilder toBuilder() => new LinksBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Links && + self == other.self && + html == other.html && + download == other.download && + downloadLocation == other.downloadLocation; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, self.hashCode); + _$hash = $jc(_$hash, html.hashCode); + _$hash = $jc(_$hash, download.hashCode); + _$hash = $jc(_$hash, downloadLocation.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'Links') + ..add('self', self) + ..add('html', html) + ..add('download', download) + ..add('downloadLocation', downloadLocation)) + .toString(); + } +} + +class LinksBuilder implements Builder { + _$Links? _$v; + + String? _self; + String? get self => _$this._self; + set self(String? self) => _$this._self = self; + + String? _html; + String? get html => _$this._html; + set html(String? html) => _$this._html = html; + + String? _download; + String? get download => _$this._download; + set download(String? download) => _$this._download = download; + + String? _downloadLocation; + String? get downloadLocation => _$this._downloadLocation; + set downloadLocation(String? downloadLocation) => + _$this._downloadLocation = downloadLocation; + + LinksBuilder(); + + LinksBuilder get _$this { + final $v = _$v; + if ($v != null) { + _self = $v.self; + _html = $v.html; + _download = $v.download; + _downloadLocation = $v.downloadLocation; + _$v = null; + } + return this; + } + + @override + void replace(Links other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$Links; + } + + @override + void update(void Function(LinksBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + Links build() => _build(); + + _$Links _build() { + final _$result = + _$v ?? + new _$Links._( + self: self, + html: html, + download: download, + downloadLocation: downloadLocation, + ); + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/material/lib/src/unsplash/location.dart b/desktop_photo_search/material/lib/src/unsplash/location.dart new file mode 100644 index 0000000..0a7ebac --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/location.dart @@ -0,0 +1,43 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; +import 'position.dart'; + +part 'location.g.dart'; + +abstract class Location implements Built { + factory Location([void Function(LocationBuilder)? updates]) = _$Location; + + Location._(); + + @BuiltValueField(wireName: 'city') + String? get city; + + @BuiltValueField(wireName: 'country') + String? get country; + + @BuiltValueField(wireName: 'position') + Position? get position; + + String toJson() { + return json.encode( + serializers.serializeWith(Location.serializer, this), + ); + } + + static Location? fromJson(String jsonString) { + return serializers.deserializeWith( + Location.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => _$locationSerializer; +} diff --git a/desktop_photo_search/material/lib/src/unsplash/location.g.dart b/desktop_photo_search/material/lib/src/unsplash/location.g.dart new file mode 100644 index 0000000..7fe7b1c --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/location.g.dart @@ -0,0 +1,231 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'location.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$locationSerializer = new _$LocationSerializer(); + +class _$LocationSerializer implements StructuredSerializer { + @override + final Iterable types = const [Location, _$Location]; + @override + final String wireName = 'Location'; + + @override + Iterable serialize( + Serializers serializers, + Location object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = []; + Object? value; + value = object.city; + if (value != null) { + result + ..add('city') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.country; + if (value != null) { + result + ..add('country') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.position; + if (value != null) { + result + ..add('position') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(Position), + ), + ); + } + return result; + } + + @override + Location deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new LocationBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'city': + result.city = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'country': + result.country = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'position': + result.position.replace( + serializers.deserialize( + value, + specifiedType: const FullType(Position), + )! + as Position, + ); + break; + } + } + + return result.build(); + } +} + +class _$Location extends Location { + @override + final String? city; + @override + final String? country; + @override + final Position? position; + + factory _$Location([void Function(LocationBuilder)? updates]) => + (new LocationBuilder()..update(updates))._build(); + + _$Location._({this.city, this.country, this.position}) : super._(); + + @override + Location rebuild(void Function(LocationBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + LocationBuilder toBuilder() => new LocationBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Location && + city == other.city && + country == other.country && + position == other.position; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, city.hashCode); + _$hash = $jc(_$hash, country.hashCode); + _$hash = $jc(_$hash, position.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'Location') + ..add('city', city) + ..add('country', country) + ..add('position', position)) + .toString(); + } +} + +class LocationBuilder implements Builder { + _$Location? _$v; + + String? _city; + String? get city => _$this._city; + set city(String? city) => _$this._city = city; + + String? _country; + String? get country => _$this._country; + set country(String? country) => _$this._country = country; + + PositionBuilder? _position; + PositionBuilder get position => _$this._position ??= new PositionBuilder(); + set position(PositionBuilder? position) => _$this._position = position; + + LocationBuilder(); + + LocationBuilder get _$this { + final $v = _$v; + if ($v != null) { + _city = $v.city; + _country = $v.country; + _position = $v.position?.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(Location other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$Location; + } + + @override + void update(void Function(LocationBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + Location build() => _build(); + + _$Location _build() { + _$Location _$result; + try { + _$result = + _$v ?? + new _$Location._( + city: city, + country: country, + position: _position?.build(), + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'position'; + _position?.build(); + } catch (e) { + throw new BuiltValueNestedFieldError( + r'Location', + _$failedField, + e.toString(), + ); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/material/lib/src/unsplash/photo.dart b/desktop_photo_search/material/lib/src/unsplash/photo.dart new file mode 100644 index 0000000..2e0de56 --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/photo.dart @@ -0,0 +1,90 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_collection/built_collection.dart'; +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; +import 'current_user_collections.dart'; +import 'exif.dart'; +import 'links.dart'; +import 'location.dart'; +import 'tags.dart'; +import 'urls.dart'; +import 'user.dart'; + +part 'photo.g.dart'; + +abstract class Photo implements Built { + factory Photo([void Function(PhotoBuilder)? updates]) = _$Photo; + + Photo._(); + + @BuiltValueField(wireName: 'id') + String get id; + + @BuiltValueField(wireName: 'created_at') + String? get createdAt; + + @BuiltValueField(wireName: 'updated_at') + String? get updatedAt; + + @BuiltValueField(wireName: 'width') + int? get width; + + @BuiltValueField(wireName: 'height') + int? get height; + + @BuiltValueField(wireName: 'color') + String? get color; + + @BuiltValueField(wireName: 'downloads') + int? get downloads; + + @BuiltValueField(wireName: 'likes') + int? get likes; + + @BuiltValueField(wireName: 'liked_by_user') + bool? get likedByUser; + + @BuiltValueField(wireName: 'description') + String? get description; + + @BuiltValueField(wireName: 'exif') + Exif? get exif; + + @BuiltValueField(wireName: 'location') + Location? get location; + + @BuiltValueField(wireName: 'tags') + BuiltList? get tags; + + @BuiltValueField(wireName: 'current_user_collections') + BuiltList? get currentUserCollections; + + @BuiltValueField(wireName: 'urls') + Urls? get urls; + + @BuiltValueField(wireName: 'links') + Links? get links; + + @BuiltValueField(wireName: 'user') + User? get user; + + String toJson() { + return json.encode(serializers.serializeWith(Photo.serializer, this)); + } + + static Photo? fromJson(String jsonString) { + return serializers.deserializeWith( + Photo.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => _$photoSerializer; +} diff --git a/desktop_photo_search/material/lib/src/unsplash/photo.g.dart b/desktop_photo_search/material/lib/src/unsplash/photo.g.dart new file mode 100644 index 0000000..c032a75 --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/photo.g.dart @@ -0,0 +1,684 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'photo.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$photoSerializer = new _$PhotoSerializer(); + +class _$PhotoSerializer implements StructuredSerializer { + @override + final Iterable types = const [Photo, _$Photo]; + @override + final String wireName = 'Photo'; + + @override + Iterable serialize( + Serializers serializers, + Photo object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = [ + 'id', + serializers.serialize( + object.id, + specifiedType: const FullType(String), + ), + ]; + Object? value; + value = object.createdAt; + if (value != null) { + result + ..add('created_at') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.updatedAt; + if (value != null) { + result + ..add('updated_at') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.width; + if (value != null) { + result + ..add('width') + ..add( + serializers.serialize(value, specifiedType: const FullType(int)), + ); + } + value = object.height; + if (value != null) { + result + ..add('height') + ..add( + serializers.serialize(value, specifiedType: const FullType(int)), + ); + } + value = object.color; + if (value != null) { + result + ..add('color') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.downloads; + if (value != null) { + result + ..add('downloads') + ..add( + serializers.serialize(value, specifiedType: const FullType(int)), + ); + } + value = object.likes; + if (value != null) { + result + ..add('likes') + ..add( + serializers.serialize(value, specifiedType: const FullType(int)), + ); + } + value = object.likedByUser; + if (value != null) { + result + ..add('liked_by_user') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(bool), + ), + ); + } + value = object.description; + if (value != null) { + result + ..add('description') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.exif; + if (value != null) { + result + ..add('exif') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(Exif), + ), + ); + } + value = object.location; + if (value != null) { + result + ..add('location') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(Location), + ), + ); + } + value = object.tags; + if (value != null) { + result + ..add('tags') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(BuiltList, const [ + const FullType(Tags), + ]), + ), + ); + } + value = object.currentUserCollections; + if (value != null) { + result + ..add('current_user_collections') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(BuiltList, const [ + const FullType(CurrentUserCollections), + ]), + ), + ); + } + value = object.urls; + if (value != null) { + result + ..add('urls') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(Urls), + ), + ); + } + value = object.links; + if (value != null) { + result + ..add('links') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(Links), + ), + ); + } + value = object.user; + if (value != null) { + result + ..add('user') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(User), + ), + ); + } + return result; + } + + @override + Photo deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new PhotoBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'id': + result.id = + serializers.deserialize( + value, + specifiedType: const FullType(String), + )! + as String; + break; + case 'created_at': + result.createdAt = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'updated_at': + result.updatedAt = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'width': + result.width = + serializers.deserialize( + value, + specifiedType: const FullType(int), + ) + as int?; + break; + case 'height': + result.height = + serializers.deserialize( + value, + specifiedType: const FullType(int), + ) + as int?; + break; + case 'color': + result.color = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'downloads': + result.downloads = + serializers.deserialize( + value, + specifiedType: const FullType(int), + ) + as int?; + break; + case 'likes': + result.likes = + serializers.deserialize( + value, + specifiedType: const FullType(int), + ) + as int?; + break; + case 'liked_by_user': + result.likedByUser = + serializers.deserialize( + value, + specifiedType: const FullType(bool), + ) + as bool?; + break; + case 'description': + result.description = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'exif': + result.exif.replace( + serializers.deserialize( + value, + specifiedType: const FullType(Exif), + )! + as Exif, + ); + break; + case 'location': + result.location.replace( + serializers.deserialize( + value, + specifiedType: const FullType(Location), + )! + as Location, + ); + break; + case 'tags': + result.tags.replace( + serializers.deserialize( + value, + specifiedType: const FullType(BuiltList, const [ + const FullType(Tags), + ]), + )! + as BuiltList, + ); + break; + case 'current_user_collections': + result.currentUserCollections.replace( + serializers.deserialize( + value, + specifiedType: const FullType(BuiltList, const [ + const FullType(CurrentUserCollections), + ]), + )! + as BuiltList, + ); + break; + case 'urls': + result.urls.replace( + serializers.deserialize( + value, + specifiedType: const FullType(Urls), + )! + as Urls, + ); + break; + case 'links': + result.links.replace( + serializers.deserialize( + value, + specifiedType: const FullType(Links), + )! + as Links, + ); + break; + case 'user': + result.user.replace( + serializers.deserialize( + value, + specifiedType: const FullType(User), + )! + as User, + ); + break; + } + } + + return result.build(); + } +} + +class _$Photo extends Photo { + @override + final String id; + @override + final String? createdAt; + @override + final String? updatedAt; + @override + final int? width; + @override + final int? height; + @override + final String? color; + @override + final int? downloads; + @override + final int? likes; + @override + final bool? likedByUser; + @override + final String? description; + @override + final Exif? exif; + @override + final Location? location; + @override + final BuiltList? tags; + @override + final BuiltList? currentUserCollections; + @override + final Urls? urls; + @override + final Links? links; + @override + final User? user; + + factory _$Photo([void Function(PhotoBuilder)? updates]) => + (new PhotoBuilder()..update(updates))._build(); + + _$Photo._({ + required this.id, + this.createdAt, + this.updatedAt, + this.width, + this.height, + this.color, + this.downloads, + this.likes, + this.likedByUser, + this.description, + this.exif, + this.location, + this.tags, + this.currentUserCollections, + this.urls, + this.links, + this.user, + }) : super._() { + BuiltValueNullFieldError.checkNotNull(id, r'Photo', 'id'); + } + + @override + Photo rebuild(void Function(PhotoBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + PhotoBuilder toBuilder() => new PhotoBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Photo && + id == other.id && + createdAt == other.createdAt && + updatedAt == other.updatedAt && + width == other.width && + height == other.height && + color == other.color && + downloads == other.downloads && + likes == other.likes && + likedByUser == other.likedByUser && + description == other.description && + exif == other.exif && + location == other.location && + tags == other.tags && + currentUserCollections == other.currentUserCollections && + urls == other.urls && + links == other.links && + user == other.user; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, id.hashCode); + _$hash = $jc(_$hash, createdAt.hashCode); + _$hash = $jc(_$hash, updatedAt.hashCode); + _$hash = $jc(_$hash, width.hashCode); + _$hash = $jc(_$hash, height.hashCode); + _$hash = $jc(_$hash, color.hashCode); + _$hash = $jc(_$hash, downloads.hashCode); + _$hash = $jc(_$hash, likes.hashCode); + _$hash = $jc(_$hash, likedByUser.hashCode); + _$hash = $jc(_$hash, description.hashCode); + _$hash = $jc(_$hash, exif.hashCode); + _$hash = $jc(_$hash, location.hashCode); + _$hash = $jc(_$hash, tags.hashCode); + _$hash = $jc(_$hash, currentUserCollections.hashCode); + _$hash = $jc(_$hash, urls.hashCode); + _$hash = $jc(_$hash, links.hashCode); + _$hash = $jc(_$hash, user.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'Photo') + ..add('id', id) + ..add('createdAt', createdAt) + ..add('updatedAt', updatedAt) + ..add('width', width) + ..add('height', height) + ..add('color', color) + ..add('downloads', downloads) + ..add('likes', likes) + ..add('likedByUser', likedByUser) + ..add('description', description) + ..add('exif', exif) + ..add('location', location) + ..add('tags', tags) + ..add('currentUserCollections', currentUserCollections) + ..add('urls', urls) + ..add('links', links) + ..add('user', user)) + .toString(); + } +} + +class PhotoBuilder implements Builder { + _$Photo? _$v; + + String? _id; + String? get id => _$this._id; + set id(String? id) => _$this._id = id; + + String? _createdAt; + String? get createdAt => _$this._createdAt; + set createdAt(String? createdAt) => _$this._createdAt = createdAt; + + String? _updatedAt; + String? get updatedAt => _$this._updatedAt; + set updatedAt(String? updatedAt) => _$this._updatedAt = updatedAt; + + int? _width; + int? get width => _$this._width; + set width(int? width) => _$this._width = width; + + int? _height; + int? get height => _$this._height; + set height(int? height) => _$this._height = height; + + String? _color; + String? get color => _$this._color; + set color(String? color) => _$this._color = color; + + int? _downloads; + int? get downloads => _$this._downloads; + set downloads(int? downloads) => _$this._downloads = downloads; + + int? _likes; + int? get likes => _$this._likes; + set likes(int? likes) => _$this._likes = likes; + + bool? _likedByUser; + bool? get likedByUser => _$this._likedByUser; + set likedByUser(bool? likedByUser) => _$this._likedByUser = likedByUser; + + String? _description; + String? get description => _$this._description; + set description(String? description) => _$this._description = description; + + ExifBuilder? _exif; + ExifBuilder get exif => _$this._exif ??= new ExifBuilder(); + set exif(ExifBuilder? exif) => _$this._exif = exif; + + LocationBuilder? _location; + LocationBuilder get location => _$this._location ??= new LocationBuilder(); + set location(LocationBuilder? location) => _$this._location = location; + + ListBuilder? _tags; + ListBuilder get tags => _$this._tags ??= new ListBuilder(); + set tags(ListBuilder? tags) => _$this._tags = tags; + + ListBuilder? _currentUserCollections; + ListBuilder get currentUserCollections => + _$this._currentUserCollections ??= + new ListBuilder(); + set currentUserCollections( + ListBuilder? currentUserCollections, + ) => _$this._currentUserCollections = currentUserCollections; + + UrlsBuilder? _urls; + UrlsBuilder get urls => _$this._urls ??= new UrlsBuilder(); + set urls(UrlsBuilder? urls) => _$this._urls = urls; + + LinksBuilder? _links; + LinksBuilder get links => _$this._links ??= new LinksBuilder(); + set links(LinksBuilder? links) => _$this._links = links; + + UserBuilder? _user; + UserBuilder get user => _$this._user ??= new UserBuilder(); + set user(UserBuilder? user) => _$this._user = user; + + PhotoBuilder(); + + PhotoBuilder get _$this { + final $v = _$v; + if ($v != null) { + _id = $v.id; + _createdAt = $v.createdAt; + _updatedAt = $v.updatedAt; + _width = $v.width; + _height = $v.height; + _color = $v.color; + _downloads = $v.downloads; + _likes = $v.likes; + _likedByUser = $v.likedByUser; + _description = $v.description; + _exif = $v.exif?.toBuilder(); + _location = $v.location?.toBuilder(); + _tags = $v.tags?.toBuilder(); + _currentUserCollections = $v.currentUserCollections?.toBuilder(); + _urls = $v.urls?.toBuilder(); + _links = $v.links?.toBuilder(); + _user = $v.user?.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(Photo other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$Photo; + } + + @override + void update(void Function(PhotoBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + Photo build() => _build(); + + _$Photo _build() { + _$Photo _$result; + try { + _$result = + _$v ?? + new _$Photo._( + id: BuiltValueNullFieldError.checkNotNull(id, r'Photo', 'id'), + createdAt: createdAt, + updatedAt: updatedAt, + width: width, + height: height, + color: color, + downloads: downloads, + likes: likes, + likedByUser: likedByUser, + description: description, + exif: _exif?.build(), + location: _location?.build(), + tags: _tags?.build(), + currentUserCollections: _currentUserCollections?.build(), + urls: _urls?.build(), + links: _links?.build(), + user: _user?.build(), + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'exif'; + _exif?.build(); + _$failedField = 'location'; + _location?.build(); + _$failedField = 'tags'; + _tags?.build(); + _$failedField = 'currentUserCollections'; + _currentUserCollections?.build(); + _$failedField = 'urls'; + _urls?.build(); + _$failedField = 'links'; + _links?.build(); + _$failedField = 'user'; + _user?.build(); + } catch (e) { + throw new BuiltValueNestedFieldError( + r'Photo', + _$failedField, + e.toString(), + ); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/material/lib/src/unsplash/position.dart b/desktop_photo_search/material/lib/src/unsplash/position.dart new file mode 100644 index 0000000..b57bb93 --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/position.dart @@ -0,0 +1,39 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; + +part 'position.g.dart'; + +abstract class Position implements Built { + factory Position([void Function(PositionBuilder)? updates]) = _$Position; + + Position._(); + + @BuiltValueField(wireName: 'latitude') + double get latitude; + + @BuiltValueField(wireName: 'longitude') + double get longitude; + + String toJson() { + return json.encode( + serializers.serializeWith(Position.serializer, this), + ); + } + + static Position? fromJson(String jsonString) { + return serializers.deserializeWith( + Position.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => _$positionSerializer; +} diff --git a/desktop_photo_search/material/lib/src/unsplash/position.g.dart b/desktop_photo_search/material/lib/src/unsplash/position.g.dart new file mode 100644 index 0000000..c8619d5 --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/position.g.dart @@ -0,0 +1,192 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'position.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$positionSerializer = new _$PositionSerializer(); + +class _$PositionSerializer implements StructuredSerializer { + @override + final Iterable types = const [Position, _$Position]; + @override + final String wireName = 'Position'; + + @override + Iterable serialize( + Serializers serializers, + Position object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = [ + 'latitude', + serializers.serialize( + object.latitude, + specifiedType: const FullType(double), + ), + 'longitude', + serializers.serialize( + object.longitude, + specifiedType: const FullType(double), + ), + ]; + + return result; + } + + @override + Position deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new PositionBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'latitude': + result.latitude = + serializers.deserialize( + value, + specifiedType: const FullType(double), + )! + as double; + break; + case 'longitude': + result.longitude = + serializers.deserialize( + value, + specifiedType: const FullType(double), + )! + as double; + break; + } + } + + return result.build(); + } +} + +class _$Position extends Position { + @override + final double latitude; + @override + final double longitude; + + factory _$Position([void Function(PositionBuilder)? updates]) => + (new PositionBuilder()..update(updates))._build(); + + _$Position._({required this.latitude, required this.longitude}) : super._() { + BuiltValueNullFieldError.checkNotNull( + latitude, + r'Position', + 'latitude', + ); + BuiltValueNullFieldError.checkNotNull( + longitude, + r'Position', + 'longitude', + ); + } + + @override + Position rebuild(void Function(PositionBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + PositionBuilder toBuilder() => new PositionBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Position && + latitude == other.latitude && + longitude == other.longitude; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, latitude.hashCode); + _$hash = $jc(_$hash, longitude.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'Position') + ..add('latitude', latitude) + ..add('longitude', longitude)) + .toString(); + } +} + +class PositionBuilder implements Builder { + _$Position? _$v; + + double? _latitude; + double? get latitude => _$this._latitude; + set latitude(double? latitude) => _$this._latitude = latitude; + + double? _longitude; + double? get longitude => _$this._longitude; + set longitude(double? longitude) => _$this._longitude = longitude; + + PositionBuilder(); + + PositionBuilder get _$this { + final $v = _$v; + if ($v != null) { + _latitude = $v.latitude; + _longitude = $v.longitude; + _$v = null; + } + return this; + } + + @override + void replace(Position other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$Position; + } + + @override + void update(void Function(PositionBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + Position build() => _build(); + + _$Position _build() { + final _$result = + _$v ?? + new _$Position._( + latitude: BuiltValueNullFieldError.checkNotNull( + latitude, + r'Position', + 'latitude', + ), + longitude: BuiltValueNullFieldError.checkNotNull( + longitude, + r'Position', + 'longitude', + ), + ); + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/material/lib/src/unsplash/search_photos_response.dart b/desktop_photo_search/material/lib/src/unsplash/search_photos_response.dart new file mode 100644 index 0000000..7ccc4ea --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/search_photos_response.dart @@ -0,0 +1,48 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_collection/built_collection.dart'; +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; +import 'photo.dart'; + +part 'search_photos_response.g.dart'; + +abstract class SearchPhotosResponse + implements Built { + factory SearchPhotosResponse([ + void Function(SearchPhotosResponseBuilder)? updates, + ]) = _$SearchPhotosResponse; + + SearchPhotosResponse._(); + + @BuiltValueField(wireName: 'total') + int? get total; + + @BuiltValueField(wireName: 'total_pages') + int? get totalPages; + + @BuiltValueField(wireName: 'results') + BuiltList get results; + + String toJson() { + return json.encode( + serializers.serializeWith(SearchPhotosResponse.serializer, this), + ); + } + + static SearchPhotosResponse? fromJson(String jsonString) { + return serializers.deserializeWith( + SearchPhotosResponse.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => + _$searchPhotosResponseSerializer; +} diff --git a/desktop_photo_search/material/lib/src/unsplash/search_photos_response.g.dart b/desktop_photo_search/material/lib/src/unsplash/search_photos_response.g.dart new file mode 100644 index 0000000..65cbb64 --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/search_photos_response.g.dart @@ -0,0 +1,244 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'search_photos_response.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$searchPhotosResponseSerializer = + new _$SearchPhotosResponseSerializer(); + +class _$SearchPhotosResponseSerializer + implements StructuredSerializer { + @override + final Iterable types = const [ + SearchPhotosResponse, + _$SearchPhotosResponse, + ]; + @override + final String wireName = 'SearchPhotosResponse'; + + @override + Iterable serialize( + Serializers serializers, + SearchPhotosResponse object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = [ + 'results', + serializers.serialize( + object.results, + specifiedType: const FullType(BuiltList, const [ + const FullType(Photo), + ]), + ), + ]; + Object? value; + value = object.total; + if (value != null) { + result + ..add('total') + ..add( + serializers.serialize(value, specifiedType: const FullType(int)), + ); + } + value = object.totalPages; + if (value != null) { + result + ..add('total_pages') + ..add( + serializers.serialize(value, specifiedType: const FullType(int)), + ); + } + return result; + } + + @override + SearchPhotosResponse deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new SearchPhotosResponseBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'total': + result.total = + serializers.deserialize( + value, + specifiedType: const FullType(int), + ) + as int?; + break; + case 'total_pages': + result.totalPages = + serializers.deserialize( + value, + specifiedType: const FullType(int), + ) + as int?; + break; + case 'results': + result.results.replace( + serializers.deserialize( + value, + specifiedType: const FullType(BuiltList, const [ + const FullType(Photo), + ]), + )! + as BuiltList, + ); + break; + } + } + + return result.build(); + } +} + +class _$SearchPhotosResponse extends SearchPhotosResponse { + @override + final int? total; + @override + final int? totalPages; + @override + final BuiltList results; + + factory _$SearchPhotosResponse([ + void Function(SearchPhotosResponseBuilder)? updates, + ]) => (new SearchPhotosResponseBuilder()..update(updates))._build(); + + _$SearchPhotosResponse._({ + this.total, + this.totalPages, + required this.results, + }) : super._() { + BuiltValueNullFieldError.checkNotNull( + results, + r'SearchPhotosResponse', + 'results', + ); + } + + @override + SearchPhotosResponse rebuild( + void Function(SearchPhotosResponseBuilder) updates, + ) => (toBuilder()..update(updates)).build(); + + @override + SearchPhotosResponseBuilder toBuilder() => + new SearchPhotosResponseBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is SearchPhotosResponse && + total == other.total && + totalPages == other.totalPages && + results == other.results; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, total.hashCode); + _$hash = $jc(_$hash, totalPages.hashCode); + _$hash = $jc(_$hash, results.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'SearchPhotosResponse') + ..add('total', total) + ..add('totalPages', totalPages) + ..add('results', results)) + .toString(); + } +} + +class SearchPhotosResponseBuilder + implements Builder { + _$SearchPhotosResponse? _$v; + + int? _total; + int? get total => _$this._total; + set total(int? total) => _$this._total = total; + + int? _totalPages; + int? get totalPages => _$this._totalPages; + set totalPages(int? totalPages) => _$this._totalPages = totalPages; + + ListBuilder? _results; + ListBuilder get results => + _$this._results ??= new ListBuilder(); + set results(ListBuilder? results) => _$this._results = results; + + SearchPhotosResponseBuilder(); + + SearchPhotosResponseBuilder get _$this { + final $v = _$v; + if ($v != null) { + _total = $v.total; + _totalPages = $v.totalPages; + _results = $v.results.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(SearchPhotosResponse other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$SearchPhotosResponse; + } + + @override + void update(void Function(SearchPhotosResponseBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + SearchPhotosResponse build() => _build(); + + _$SearchPhotosResponse _build() { + _$SearchPhotosResponse _$result; + try { + _$result = + _$v ?? + new _$SearchPhotosResponse._( + total: total, + totalPages: totalPages, + results: results.build(), + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'results'; + results.build(); + } catch (e) { + throw new BuiltValueNestedFieldError( + r'SearchPhotosResponse', + _$failedField, + e.toString(), + ); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/material/lib/src/unsplash/tags.dart b/desktop_photo_search/material/lib/src/unsplash/tags.dart new file mode 100644 index 0000000..b366e52 --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/tags.dart @@ -0,0 +1,34 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; + +part 'tags.g.dart'; + +abstract class Tags implements Built { + factory Tags([void Function(TagsBuilder)? updates]) = _$Tags; + + Tags._(); + + @BuiltValueField(wireName: 'title') + String get title; + + String toJson() { + return json.encode(serializers.serializeWith(Tags.serializer, this)); + } + + static Tags? fromJson(String jsonString) { + return serializers.deserializeWith( + Tags.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => _$tagsSerializer; +} diff --git a/desktop_photo_search/material/lib/src/unsplash/tags.g.dart b/desktop_photo_search/material/lib/src/unsplash/tags.g.dart new file mode 100644 index 0000000..4a9ac21 --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/tags.g.dart @@ -0,0 +1,154 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'tags.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$tagsSerializer = new _$TagsSerializer(); + +class _$TagsSerializer implements StructuredSerializer { + @override + final Iterable types = const [Tags, _$Tags]; + @override + final String wireName = 'Tags'; + + @override + Iterable serialize( + Serializers serializers, + Tags object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = [ + 'title', + serializers.serialize( + object.title, + specifiedType: const FullType(String), + ), + ]; + + return result; + } + + @override + Tags deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new TagsBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'title': + result.title = + serializers.deserialize( + value, + specifiedType: const FullType(String), + )! + as String; + break; + } + } + + return result.build(); + } +} + +class _$Tags extends Tags { + @override + final String title; + + factory _$Tags([void Function(TagsBuilder)? updates]) => + (new TagsBuilder()..update(updates))._build(); + + _$Tags._({required this.title}) : super._() { + BuiltValueNullFieldError.checkNotNull(title, r'Tags', 'title'); + } + + @override + Tags rebuild(void Function(TagsBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + TagsBuilder toBuilder() => new TagsBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Tags && title == other.title; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, title.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper( + r'Tags', + )..add('title', title)).toString(); + } +} + +class TagsBuilder implements Builder { + _$Tags? _$v; + + String? _title; + String? get title => _$this._title; + set title(String? title) => _$this._title = title; + + TagsBuilder(); + + TagsBuilder get _$this { + final $v = _$v; + if ($v != null) { + _title = $v.title; + _$v = null; + } + return this; + } + + @override + void replace(Tags other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$Tags; + } + + @override + void update(void Function(TagsBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + Tags build() => _build(); + + _$Tags _build() { + final _$result = + _$v ?? + new _$Tags._( + title: BuiltValueNullFieldError.checkNotNull( + title, + r'Tags', + 'title', + ), + ); + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/material/lib/src/unsplash/unsplash.dart b/desktop_photo_search/material/lib/src/unsplash/unsplash.dart new file mode 100644 index 0000000..413f2a2 --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/unsplash.dart @@ -0,0 +1,121 @@ +// 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. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:http/http.dart' as http; +import 'package:logging/logging.dart'; + +import 'api_error.dart'; +import 'photo.dart'; +import 'search_photos_response.dart'; + +final _unsplashBaseUrl = Uri.parse('https://api.unsplash.com/'); + +/// Unsplash API Client. Requires an +/// [Unsplash API](https://unsplash.com/developers) `accessKey` to make +/// requests to the Unsplash API. +class Unsplash { + Unsplash({required String accessKey, http.BaseClient? httpClient}) + : _accessKey = accessKey, + _client = httpClient ?? http.Client(); + + final String _accessKey; + final http.Client _client; + final _log = Logger('Unsplash'); + + Future searchPhotos({ + required String query, + num page = 1, + num perPage = 10, + List collections = const [], + SearchPhotosOrientation? orientation, + }) async { + final searchPhotosUrl = _unsplashBaseUrl.replace( + path: '/search/photos', + queryParameters: { + 'query': query, + if (page != 1) 'page': '$page', + if (perPage != 10) 'per_page': '$perPage', + if (collections.isNotEmpty) 'collections': collections.join(','), + if (orientation == SearchPhotosOrientation.landscape) + 'orientation': 'landscape', + if (orientation == SearchPhotosOrientation.portrait) + 'orientation': 'portrait', + if (orientation == SearchPhotosOrientation.squarish) + 'orientation': 'squarish', + }, + ); + _log.info('GET $searchPhotosUrl'); + + final response = await _client.get( + searchPhotosUrl, + headers: { + 'Accept-Version': 'v1', + 'Authorization': 'Client-ID $_accessKey', + }, + ); + + dynamic body; + try { + body = json.fuse(utf8).decode(response.bodyBytes); + } catch (e) { + throw UnsplashException('Invalid JSON received'); + } + + if (body is Map && + body['errors'] is List && + body['errors'].isNotEmpty as bool) { + final apiError = ApiError.fromJson(response.body)!; + throw UnsplashException(apiError.errors!.join(', ')); + } + + return SearchPhotosResponse.fromJson(json.encode(body)); + } + + Future download(Photo photo) async { + // For detail on how downloading photos from Unsplash, please see + // https://help.unsplash.com/en/articles/2511258-guideline-triggering-a-download + + _log.info('GET ${photo.urls!.full}'); + final futureBytes = http.readBytes( + Uri.parse(photo.urls!.full!), + headers: { + 'Accept-Version': 'v1', + 'Authorization': 'Client-ID $_accessKey', + }, + ); + + _log.info('GET ${photo.links!.downloadLocation}'); + unawaited( + http.get( + Uri.parse(photo.links!.downloadLocation!), + headers: { + 'Accept-Version': 'v1', + 'Authorization': 'Client-ID $_accessKey', + }, + ), + ); + + return futureBytes; + } +} + +enum SearchPhotosOrientation { landscape, portrait, squarish } + +class UnsplashException implements Exception { + UnsplashException([this.message]); + + final String? message; + + @override + String toString() { + if (message == null) { + return 'UnsplashException'; + } + return 'UnsplashException: $message'; + } +} diff --git a/desktop_photo_search/material/lib/src/unsplash/urls.dart b/desktop_photo_search/material/lib/src/unsplash/urls.dart new file mode 100644 index 0000000..dc514c9 --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/urls.dart @@ -0,0 +1,46 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; + +part 'urls.g.dart'; + +abstract class Urls implements Built { + factory Urls([void Function(UrlsBuilder b)? updates]) = _$Urls; + + Urls._(); + + @BuiltValueField(wireName: 'raw') + String? get raw; + + @BuiltValueField(wireName: 'full') + String? get full; + + @BuiltValueField(wireName: 'regular') + String? get regular; + + @BuiltValueField(wireName: 'small') + String? get small; + + @BuiltValueField(wireName: 'thumb') + String? get thumb; + + String toJson() { + return json.encode(serializers.serializeWith(Urls.serializer, this)); + } + + static Urls? fromJson(String jsonString) { + return serializers.deserializeWith( + Urls.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => _$urlsSerializer; +} diff --git a/desktop_photo_search/material/lib/src/unsplash/urls.g.dart b/desktop_photo_search/material/lib/src/unsplash/urls.g.dart new file mode 100644 index 0000000..f8f5862 --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/urls.g.dart @@ -0,0 +1,275 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'urls.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$urlsSerializer = new _$UrlsSerializer(); + +class _$UrlsSerializer implements StructuredSerializer { + @override + final Iterable types = const [Urls, _$Urls]; + @override + final String wireName = 'Urls'; + + @override + Iterable serialize( + Serializers serializers, + Urls object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = []; + Object? value; + value = object.raw; + if (value != null) { + result + ..add('raw') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.full; + if (value != null) { + result + ..add('full') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.regular; + if (value != null) { + result + ..add('regular') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.small; + if (value != null) { + result + ..add('small') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.thumb; + if (value != null) { + result + ..add('thumb') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + return result; + } + + @override + Urls deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new UrlsBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'raw': + result.raw = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'full': + result.full = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'regular': + result.regular = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'small': + result.small = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'thumb': + result.thumb = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + } + } + + return result.build(); + } +} + +class _$Urls extends Urls { + @override + final String? raw; + @override + final String? full; + @override + final String? regular; + @override + final String? small; + @override + final String? thumb; + + factory _$Urls([void Function(UrlsBuilder)? updates]) => + (new UrlsBuilder()..update(updates))._build(); + + _$Urls._({this.raw, this.full, this.regular, this.small, this.thumb}) + : super._(); + + @override + Urls rebuild(void Function(UrlsBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + UrlsBuilder toBuilder() => new UrlsBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Urls && + raw == other.raw && + full == other.full && + regular == other.regular && + small == other.small && + thumb == other.thumb; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, raw.hashCode); + _$hash = $jc(_$hash, full.hashCode); + _$hash = $jc(_$hash, regular.hashCode); + _$hash = $jc(_$hash, small.hashCode); + _$hash = $jc(_$hash, thumb.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'Urls') + ..add('raw', raw) + ..add('full', full) + ..add('regular', regular) + ..add('small', small) + ..add('thumb', thumb)) + .toString(); + } +} + +class UrlsBuilder implements Builder { + _$Urls? _$v; + + String? _raw; + String? get raw => _$this._raw; + set raw(String? raw) => _$this._raw = raw; + + String? _full; + String? get full => _$this._full; + set full(String? full) => _$this._full = full; + + String? _regular; + String? get regular => _$this._regular; + set regular(String? regular) => _$this._regular = regular; + + String? _small; + String? get small => _$this._small; + set small(String? small) => _$this._small = small; + + String? _thumb; + String? get thumb => _$this._thumb; + set thumb(String? thumb) => _$this._thumb = thumb; + + UrlsBuilder(); + + UrlsBuilder get _$this { + final $v = _$v; + if ($v != null) { + _raw = $v.raw; + _full = $v.full; + _regular = $v.regular; + _small = $v.small; + _thumb = $v.thumb; + _$v = null; + } + return this; + } + + @override + void replace(Urls other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$Urls; + } + + @override + void update(void Function(UrlsBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + Urls build() => _build(); + + _$Urls _build() { + final _$result = + _$v ?? + new _$Urls._( + raw: raw, + full: full, + regular: regular, + small: small, + thumb: thumb, + ); + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/material/lib/src/unsplash/user.dart b/desktop_photo_search/material/lib/src/unsplash/user.dart new file mode 100644 index 0000000..98f0178 --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/user.dart @@ -0,0 +1,65 @@ +// 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. + +import 'dart:convert'; + +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +import '../serializers.dart'; +import 'links.dart'; + +part 'user.g.dart'; + +abstract class User implements Built { + factory User([void Function(UserBuilder)? updates]) = _$User; + + User._(); + + @BuiltValueField(wireName: 'id') + String get id; + + @BuiltValueField(wireName: 'updated_at') + String? get updatedAt; + + @BuiltValueField(wireName: 'username') + String get username; + + @BuiltValueField(wireName: 'name') + String get name; + + @BuiltValueField(wireName: 'portfolio_url') + String? get portfolioUrl; + + @BuiltValueField(wireName: 'bio') + String? get bio; + + @BuiltValueField(wireName: 'location') + String? get location; + + @BuiltValueField(wireName: 'total_likes') + int? get totalLikes; + + @BuiltValueField(wireName: 'total_photos') + int? get totalPhotos; + + @BuiltValueField(wireName: 'total_collections') + int? get totalCollections; + + @BuiltValueField(wireName: 'links') + Links? get links; + + String toJson() { + return json.encode(serializers.serializeWith(User.serializer, this)); + } + + static User? fromJson(String jsonString) { + return serializers.deserializeWith( + User.serializer, + json.decode(jsonString), + ); + } + + static Serializer get serializer => _$userSerializer; +} diff --git a/desktop_photo_search/material/lib/src/unsplash/user.g.dart b/desktop_photo_search/material/lib/src/unsplash/user.g.dart new file mode 100644 index 0000000..cf00d10 --- /dev/null +++ b/desktop_photo_search/material/lib/src/unsplash/user.g.dart @@ -0,0 +1,471 @@ +// 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. + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$userSerializer = new _$UserSerializer(); + +class _$UserSerializer implements StructuredSerializer { + @override + final Iterable types = const [User, _$User]; + @override + final String wireName = 'User'; + + @override + Iterable serialize( + Serializers serializers, + User object, { + FullType specifiedType = FullType.unspecified, + }) { + final result = [ + 'id', + serializers.serialize( + object.id, + specifiedType: const FullType(String), + ), + 'username', + serializers.serialize( + object.username, + specifiedType: const FullType(String), + ), + 'name', + serializers.serialize( + object.name, + specifiedType: const FullType(String), + ), + ]; + Object? value; + value = object.updatedAt; + if (value != null) { + result + ..add('updated_at') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.portfolioUrl; + if (value != null) { + result + ..add('portfolio_url') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.bio; + if (value != null) { + result + ..add('bio') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.location; + if (value != null) { + result + ..add('location') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(String), + ), + ); + } + value = object.totalLikes; + if (value != null) { + result + ..add('total_likes') + ..add( + serializers.serialize(value, specifiedType: const FullType(int)), + ); + } + value = object.totalPhotos; + if (value != null) { + result + ..add('total_photos') + ..add( + serializers.serialize(value, specifiedType: const FullType(int)), + ); + } + value = object.totalCollections; + if (value != null) { + result + ..add('total_collections') + ..add( + serializers.serialize(value, specifiedType: const FullType(int)), + ); + } + value = object.links; + if (value != null) { + result + ..add('links') + ..add( + serializers.serialize( + value, + specifiedType: const FullType(Links), + ), + ); + } + return result; + } + + @override + User deserialize( + Serializers serializers, + Iterable serialized, { + FullType specifiedType = FullType.unspecified, + }) { + final result = new UserBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'id': + result.id = + serializers.deserialize( + value, + specifiedType: const FullType(String), + )! + as String; + break; + case 'updated_at': + result.updatedAt = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'username': + result.username = + serializers.deserialize( + value, + specifiedType: const FullType(String), + )! + as String; + break; + case 'name': + result.name = + serializers.deserialize( + value, + specifiedType: const FullType(String), + )! + as String; + break; + case 'portfolio_url': + result.portfolioUrl = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'bio': + result.bio = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'location': + result.location = + serializers.deserialize( + value, + specifiedType: const FullType(String), + ) + as String?; + break; + case 'total_likes': + result.totalLikes = + serializers.deserialize( + value, + specifiedType: const FullType(int), + ) + as int?; + break; + case 'total_photos': + result.totalPhotos = + serializers.deserialize( + value, + specifiedType: const FullType(int), + ) + as int?; + break; + case 'total_collections': + result.totalCollections = + serializers.deserialize( + value, + specifiedType: const FullType(int), + ) + as int?; + break; + case 'links': + result.links.replace( + serializers.deserialize( + value, + specifiedType: const FullType(Links), + )! + as Links, + ); + break; + } + } + + return result.build(); + } +} + +class _$User extends User { + @override + final String id; + @override + final String? updatedAt; + @override + final String username; + @override + final String name; + @override + final String? portfolioUrl; + @override + final String? bio; + @override + final String? location; + @override + final int? totalLikes; + @override + final int? totalPhotos; + @override + final int? totalCollections; + @override + final Links? links; + + factory _$User([void Function(UserBuilder)? updates]) => + (new UserBuilder()..update(updates))._build(); + + _$User._({ + required this.id, + this.updatedAt, + required this.username, + required this.name, + this.portfolioUrl, + this.bio, + this.location, + this.totalLikes, + this.totalPhotos, + this.totalCollections, + this.links, + }) : super._() { + BuiltValueNullFieldError.checkNotNull(id, r'User', 'id'); + BuiltValueNullFieldError.checkNotNull(username, r'User', 'username'); + BuiltValueNullFieldError.checkNotNull(name, r'User', 'name'); + } + + @override + User rebuild(void Function(UserBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + UserBuilder toBuilder() => new UserBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is User && + id == other.id && + updatedAt == other.updatedAt && + username == other.username && + name == other.name && + portfolioUrl == other.portfolioUrl && + bio == other.bio && + location == other.location && + totalLikes == other.totalLikes && + totalPhotos == other.totalPhotos && + totalCollections == other.totalCollections && + links == other.links; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, id.hashCode); + _$hash = $jc(_$hash, updatedAt.hashCode); + _$hash = $jc(_$hash, username.hashCode); + _$hash = $jc(_$hash, name.hashCode); + _$hash = $jc(_$hash, portfolioUrl.hashCode); + _$hash = $jc(_$hash, bio.hashCode); + _$hash = $jc(_$hash, location.hashCode); + _$hash = $jc(_$hash, totalLikes.hashCode); + _$hash = $jc(_$hash, totalPhotos.hashCode); + _$hash = $jc(_$hash, totalCollections.hashCode); + _$hash = $jc(_$hash, links.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'User') + ..add('id', id) + ..add('updatedAt', updatedAt) + ..add('username', username) + ..add('name', name) + ..add('portfolioUrl', portfolioUrl) + ..add('bio', bio) + ..add('location', location) + ..add('totalLikes', totalLikes) + ..add('totalPhotos', totalPhotos) + ..add('totalCollections', totalCollections) + ..add('links', links)) + .toString(); + } +} + +class UserBuilder implements Builder { + _$User? _$v; + + String? _id; + String? get id => _$this._id; + set id(String? id) => _$this._id = id; + + String? _updatedAt; + String? get updatedAt => _$this._updatedAt; + set updatedAt(String? updatedAt) => _$this._updatedAt = updatedAt; + + String? _username; + String? get username => _$this._username; + set username(String? username) => _$this._username = username; + + String? _name; + String? get name => _$this._name; + set name(String? name) => _$this._name = name; + + String? _portfolioUrl; + String? get portfolioUrl => _$this._portfolioUrl; + set portfolioUrl(String? portfolioUrl) => + _$this._portfolioUrl = portfolioUrl; + + String? _bio; + String? get bio => _$this._bio; + set bio(String? bio) => _$this._bio = bio; + + String? _location; + String? get location => _$this._location; + set location(String? location) => _$this._location = location; + + int? _totalLikes; + int? get totalLikes => _$this._totalLikes; + set totalLikes(int? totalLikes) => _$this._totalLikes = totalLikes; + + int? _totalPhotos; + int? get totalPhotos => _$this._totalPhotos; + set totalPhotos(int? totalPhotos) => _$this._totalPhotos = totalPhotos; + + int? _totalCollections; + int? get totalCollections => _$this._totalCollections; + set totalCollections(int? totalCollections) => + _$this._totalCollections = totalCollections; + + LinksBuilder? _links; + LinksBuilder get links => _$this._links ??= new LinksBuilder(); + set links(LinksBuilder? links) => _$this._links = links; + + UserBuilder(); + + UserBuilder get _$this { + final $v = _$v; + if ($v != null) { + _id = $v.id; + _updatedAt = $v.updatedAt; + _username = $v.username; + _name = $v.name; + _portfolioUrl = $v.portfolioUrl; + _bio = $v.bio; + _location = $v.location; + _totalLikes = $v.totalLikes; + _totalPhotos = $v.totalPhotos; + _totalCollections = $v.totalCollections; + _links = $v.links?.toBuilder(); + _$v = null; + } + return this; + } + + @override + void replace(User other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$User; + } + + @override + void update(void Function(UserBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + User build() => _build(); + + _$User _build() { + _$User _$result; + try { + _$result = + _$v ?? + new _$User._( + id: BuiltValueNullFieldError.checkNotNull(id, r'User', 'id'), + updatedAt: updatedAt, + username: BuiltValueNullFieldError.checkNotNull( + username, + r'User', + 'username', + ), + name: BuiltValueNullFieldError.checkNotNull( + name, + r'User', + 'name', + ), + portfolioUrl: portfolioUrl, + bio: bio, + location: location, + totalLikes: totalLikes, + totalPhotos: totalPhotos, + totalCollections: totalCollections, + links: _links?.build(), + ); + } catch (_) { + late String _$failedField; + try { + _$failedField = 'links'; + _links?.build(); + } catch (e) { + throw new BuiltValueNestedFieldError( + r'User', + _$failedField, + e.toString(), + ); + } + rethrow; + } + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/desktop_photo_search/material/lib/src/widgets/photo_details.dart b/desktop_photo_search/material/lib/src/widgets/photo_details.dart new file mode 100644 index 0000000..96b1906 --- /dev/null +++ b/desktop_photo_search/material/lib/src/widgets/photo_details.dart @@ -0,0 +1,116 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:transparent_image/transparent_image.dart'; +import 'package:url_launcher/link.dart'; + +import '../../unsplash_access_key.dart'; +import '../unsplash/photo.dart'; + +final _unsplashHomepage = Uri.parse( + 'https://unsplash.com/?utm_source=$unsplashAppName&utm_medium=referral', +); + +typedef PhotoDetailsPhotoSaveCallback = void Function(Photo); + +class PhotoDetails extends StatefulWidget { + const PhotoDetails({ + required this.photo, + required this.onPhotoSave, + super.key, + }); + final Photo photo; + final PhotoDetailsPhotoSaveCallback onPhotoSave; + + @override + State createState() => _PhotoDetailsState(); +} + +class _PhotoDetailsState extends State { + Widget _buildPhotoAttribution(BuildContext context) { + return Row( + children: [ + const Text('Photo by '), + Link( + uri: Uri.parse( + 'https://unsplash.com/@${widget.photo.user!.username}?utm_source=$unsplashAppName&utm_medium=referral', + ), + builder: (context, followLink) => TextButton( + onPressed: followLink, + child: Text(widget.photo.user!.name), + ), + ), + const Text(' on '), + Link( + uri: _unsplashHomepage, + builder: (context, followLink) => TextButton( + onPressed: followLink, + child: const Text('Unsplash'), + ), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + return Scrollbar( + child: SingleChildScrollView( + primary: true, + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 16), + Card( + shape: ContinuousRectangleBorder( + side: const BorderSide(color: Colors.black12), + borderRadius: BorderRadius.circular(4), + ), + child: AnimatedSize( + duration: const Duration(milliseconds: 750), + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 16, 16, 40), + child: ConstrainedBox( + constraints: const BoxConstraints( + minWidth: 400, + minHeight: 400, + ), + child: FadeInImage.memoryNetwork( + placeholder: kTransparentImage, + imageSemanticLabel: widget.photo.description, + image: widget.photo.urls!.small!, + ), + ), + ), + ), + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.only(left: 4), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + _buildPhotoAttribution(context), + const SizedBox(width: 8), + IconButton( + tooltip: 'Download', + visualDensity: VisualDensity.compact, + icon: const Icon(Icons.cloud_download), + onPressed: () => widget.onPhotoSave(widget.photo), + ), + ], + ), + ), + const SizedBox(height: 48), + ], + ), + ), + ), + ); + } +} diff --git a/desktop_photo_search/material/lib/src/widgets/photo_search_dialog.dart b/desktop_photo_search/material/lib/src/widgets/photo_search_dialog.dart new file mode 100644 index 0000000..28cd62e --- /dev/null +++ b/desktop_photo_search/material/lib/src/widgets/photo_search_dialog.dart @@ -0,0 +1,68 @@ +// 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. + +import 'package:flutter/material.dart'; + +typedef PhotoSearchDialogCallback = void Function(String searchQuery); + +class PhotoSearchDialog extends StatefulWidget { + const PhotoSearchDialog({required this.callback, super.key}); + final PhotoSearchDialogCallback callback; + @override + State createState() => _PhotoSearchDialogState(); +} + +class _PhotoSearchDialogState extends State { + final _controller = TextEditingController(); + bool _searchEnabled = false; + + @override + void initState() { + super.initState(); + _controller.addListener(() { + setState(() { + _searchEnabled = _controller.text.isNotEmpty; + }); + }); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => AlertDialog( + title: const Text('Photo Search'), + content: TextField( + autofocus: true, + controller: _controller, + decoration: const InputDecoration(hintText: 'Search query'), + onSubmitted: (content) { + if (content.isNotEmpty) { + widget.callback(content); + Navigator.of(context).pop(); + } + }, + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('CANCEL'), + ), + TextButton( + onPressed: _searchEnabled + ? () { + widget.callback(_controller.text); + Navigator.of(context).pop(); + } + : null, + child: const Text('SEARCH'), + ), + ], + ); +} diff --git a/desktop_photo_search/material/lib/src/widgets/policy_dialog.dart b/desktop_photo_search/material/lib/src/widgets/policy_dialog.dart new file mode 100644 index 0000000..792b752 --- /dev/null +++ b/desktop_photo_search/material/lib/src/widgets/policy_dialog.dart @@ -0,0 +1,80 @@ +// 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. + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart' as url_launcher; + +class PolicyDialog extends StatelessWidget { + const PolicyDialog({super.key}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Terms & Conditions'), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RichText( + textAlign: TextAlign.left, + text: TextSpan( + text: 'β€’ ', + style: const TextStyle(color: Colors.black, fontSize: 18), + children: [ + TextSpan( + text: 'https://policies.google.com/terms', + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Colors.lightBlue, + ), + recognizer: TapGestureRecognizer() + ..onTap = () async { + final url = Uri.parse( + 'https://policies.google.com/terms', + ); + if (await url_launcher.canLaunchUrl(url)) { + await url_launcher.launchUrl(url); + } + }, + ), + ], + ), + ), + RichText( + textAlign: TextAlign.left, + text: TextSpan( + text: 'β€’ ', + style: const TextStyle(color: Colors.black, fontSize: 18), + children: [ + TextSpan( + text: 'https://unsplash.com/terms', + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Colors.lightBlue, + ), + recognizer: TapGestureRecognizer() + ..onTap = () async { + final url = Uri.parse('https://unsplash.com/terms'); + if (await url_launcher.canLaunchUrl(url)) { + await url_launcher.launchUrl(url); + } + }, + ), + ], + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('CLOSE'), + ), + ], + ); + } +} diff --git a/desktop_photo_search/material/lib/src/widgets/split.dart b/desktop_photo_search/material/lib/src/widgets/split.dart new file mode 100644 index 0000000..2296019 --- /dev/null +++ b/desktop_photo_search/material/lib/src/widgets/split.dart @@ -0,0 +1,181 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:math'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +/// A widget that takes two children, lays them out along [axis], and allows +/// the user to resize them. +/// +/// The user can customize the amount of space allocated to each child by +/// dragging a divider between them. +/// +/// [initialFirstFraction] defines how much space to give the [firstChild] +/// when first building this widget. [secondChild] will take the remaining +/// space. +/// +/// The user can drag the widget with key [dividerKey] to change +/// the space allocated between [firstChild] and [secondChild]. +class Split extends StatefulWidget { + /// Builds a split oriented along [axis]. + const Split({ + super.key, + required this.axis, + required this.firstChild, + required this.secondChild, + double? initialFirstFraction, + }) : initialFirstFraction = initialFirstFraction ?? 0.5; + + /// The main axis the children will lay out on. + /// + /// If [Axis.horizontal], the children will be placed in a [Row] + /// and they will be horizontally resizable. + /// + /// If [Axis.vertical], the children will be placed in a [Column] + /// and they will be vertically resizable. + /// + /// Cannot be null. + final Axis axis; + + /// The child that will be laid out first along [axis]. + final Widget firstChild; + + /// The child that will be laid out last along [axis]. + final Widget secondChild; + + /// The fraction of the layout to allocate to [firstChild]. + /// + /// [secondChild] will receive a fraction of `1 - initialFirstFraction`. + final double initialFirstFraction; + + /// The key passed to the divider between [firstChild] and [secondChild]. + /// + /// Visible to grab it in tests. + @visibleForTesting + Key get dividerKey => Key('$this dividerKey'); + + /// The size of the divider between [firstChild] and [secondChild] in + /// logical pixels (dp, not px). + static const double dividerMainAxisSize = 10; + + static Axis axisFor(BuildContext context, double horizontalAspectRatio) { + final screenSize = MediaQuery.of(context).size; + final aspectRatio = screenSize.width / screenSize.height; + if (aspectRatio >= horizontalAspectRatio) { + return Axis.horizontal; + } + return Axis.vertical; + } + + @override + State createState() => _SplitState(); +} + +class _SplitState extends State { + late double firstFraction; + + double get secondFraction => 1 - firstFraction; + + bool get isHorizontal => widget.axis == Axis.horizontal; + + @override + void initState() { + super.initState(); + firstFraction = widget.initialFirstFraction; + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder(builder: _buildLayout); + } + + Widget _buildLayout(BuildContext context, BoxConstraints constraints) { + final width = constraints.maxWidth; + final height = constraints.maxHeight; + final axisSize = isHorizontal ? width : height; + final crossAxisSize = isHorizontal ? height : width; + const halfDivider = Split.dividerMainAxisSize / 2.0; + + // Determine what fraction to give each child, including enough space to + // display the divider. + var firstSize = axisSize * firstFraction; + var secondSize = axisSize * secondFraction; + + // Clamp the sizes to be sure there is enough space for the dividers. + firstSize = firstSize.clamp(halfDivider, axisSize - halfDivider); + secondSize = secondSize.clamp(halfDivider, axisSize - halfDivider); + + // Remove space from each child to place the divider in the middle. + firstSize = firstSize - halfDivider; + secondSize = secondSize - halfDivider; + + void updateSpacing(DragUpdateDetails dragDetails) { + final delta = isHorizontal ? dragDetails.delta.dx : dragDetails.delta.dy; + final fractionalDelta = delta / axisSize; + setState(() { + // Update the fraction of space consumed by the children, + // being sure not to allocate any negative space. + firstFraction += fractionalDelta; + firstFraction = firstFraction.clamp(0.0, 1.0); + }); + } + + final dragIndicator = Flex( + direction: isHorizontal ? Axis.vertical : Axis.horizontal, + mainAxisSize: MainAxisSize.min, + children: [ + for (var i = 0; i < min(crossAxisSize / 6.0, 3).floor(); i++) + Padding( + padding: EdgeInsets.symmetric( + vertical: isHorizontal ? 2.0 : 0.0, + horizontal: isHorizontal ? 0.0 : 2.0, + ), + child: DecoratedBox( + decoration: BoxDecoration( + color: Theme.of(context).dividerColor, + borderRadius: BorderRadius.circular( + Split.dividerMainAxisSize, + ), + ), + child: SizedBox( + height: isHorizontal ? 2.0 : Split.dividerMainAxisSize - 2.0, + width: isHorizontal ? Split.dividerMainAxisSize - 2.0 : 2.0, + ), + ), + ), + ], + ); + + final children = [ + SizedBox( + width: isHorizontal ? firstSize : width, + height: isHorizontal ? height : firstSize, + child: widget.firstChild, + ), + GestureDetector( + key: widget.dividerKey, + behavior: HitTestBehavior.translucent, + onHorizontalDragUpdate: isHorizontal ? updateSpacing : null, + onVerticalDragUpdate: isHorizontal ? null : updateSpacing, + // DartStartBehavior.down is needed to keep the mouse pointer stuck to + // the drag bar. There still appears to be a few frame lag before the + // drag action triggers which is't ideal but isn't a launch blocker. + dragStartBehavior: DragStartBehavior.down, + child: SizedBox( + width: isHorizontal ? Split.dividerMainAxisSize : width, + height: isHorizontal ? height : Split.dividerMainAxisSize, + child: Center(child: dragIndicator), + ), + ), + SizedBox( + width: isHorizontal ? secondSize : width, + height: isHorizontal ? height : secondSize, + child: widget.secondChild, + ), + ]; + return Flex(direction: widget.axis, children: children); + } +} diff --git a/desktop_photo_search/material/lib/src/widgets/unsplash_notice.dart b/desktop_photo_search/material/lib/src/widgets/unsplash_notice.dart new file mode 100644 index 0000000..22e2c46 --- /dev/null +++ b/desktop_photo_search/material/lib/src/widgets/unsplash_notice.dart @@ -0,0 +1,116 @@ +// Copyright 2022 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. + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../../unsplash_access_key.dart'; + +final _unsplashHomepage = Uri.parse( + 'https://unsplash.com/?utm_source=${Uri.encodeFull(unsplashAppName)}&utm_medium=referral', +); +final _unsplashPrivacyPolicy = Uri.parse( + 'https://unsplash.com/privacy?utm_source=${Uri.encodeFull(unsplashAppName)}&utm_medium=referral', +); + +class UnsplashNotice extends StatefulWidget { + const UnsplashNotice({super.key, required this.child}); + final Widget child; + + @override + State createState() => _UnsplashNoticeState(); +} + +class _UnsplashNoticeState extends State { + bool noticeAccepted = false; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + showDialog( + barrierDismissible: false, + context: context, + builder: (context) { + return _UnsplashDialog( + accepted: () { + setState(() { + noticeAccepted = true; + }); + }, + ); + }, + ); + }); + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +} + +class _UnsplashDialog extends StatelessWidget { + const _UnsplashDialog({required this.accepted}); + final Function accepted; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Unsplash Notice'), + content: RichText( + text: TextSpan( + text: + 'This is a sample desktop application provided by Google' + ' that enables you to search ', + style: const TextStyle(color: Colors.grey), + children: [ + TextSpan( + text: 'Unsplash', + recognizer: TapGestureRecognizer() + ..onTap = () async { + if (!await launchUrl(_unsplashHomepage)) { + throw 'Could not launch $_unsplashHomepage'; + } + }, + style: const TextStyle(color: Colors.blue), + ), + const TextSpan( + text: + ' for photographs that interest you. When you search' + ' for and interact with photos, Unsplash will collect' + ' information about you and your use of the Unsplash' + ' services. Learn more about ', + style: TextStyle(color: Colors.grey), + ), + TextSpan( + text: 'how Unsplash collects and uses data', + recognizer: TapGestureRecognizer() + ..onTap = () async { + if (!await launchUrl(_unsplashPrivacyPolicy)) { + throw 'Could not launch $_unsplashPrivacyPolicy'; + } + }, + style: const TextStyle(color: Colors.blue), + ), + const TextSpan( + text: '.', + style: TextStyle(color: Colors.grey), + ), + ], + ), + ), + actions: [ + TextButton( + child: const Text('GOT IT'), + onPressed: () { + accepted(); + Navigator.pop(context); + }, + ), + ], + ); + } +} diff --git a/desktop_photo_search/material/lib/src/widgets/unsplash_search_content.dart b/desktop_photo_search/material/lib/src/widgets/unsplash_search_content.dart new file mode 100644 index 0000000..93e1814 --- /dev/null +++ b/desktop_photo_search/material/lib/src/widgets/unsplash_search_content.dart @@ -0,0 +1,112 @@ +// Copyright 2022 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. + +import 'package:file_selector/file_selector.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_simple_treeview/flutter_simple_treeview.dart'; +import 'package:provider/provider.dart'; + +import '../model/photo_search_model.dart'; +import '../unsplash/photo.dart'; +import '../widgets/photo_details.dart'; +import '../widgets/split.dart' as split; + +class UnsplashSearchContent extends StatefulWidget { + const UnsplashSearchContent({super.key}); + + @override + State createState() => _UnsplashSearchContentState(); +} + +class _UnsplashSearchContentState extends State { + final _treeViewScrollController = ScrollController(); + + @override + dispose() { + _treeViewScrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final photoSearchModel = Provider.of(context); + + return split.Split( + axis: Axis.horizontal, + initialFirstFraction: 0.4, + firstChild: Scrollbar( + controller: _treeViewScrollController, + child: SingleChildScrollView( + controller: _treeViewScrollController, + child: TreeView( + nodes: photoSearchModel.entries.map(_buildSearchEntry).toList(), + indent: 0, + ), + ), + ), + secondChild: Center( + child: photoSearchModel.selectedPhoto != null + ? PhotoDetails( + photo: photoSearchModel.selectedPhoto!, + onPhotoSave: (photo) async { + final saveLocation = await getSaveLocation( + suggestedName: '${photo.id}.jpg', + acceptedTypeGroups: [ + const XTypeGroup( + label: 'JPG', + extensions: ['jpg'], + mimeTypes: ['image/jpeg'], + ), + ], + ); + if (saveLocation != null) { + final fileData = await photoSearchModel.download( + photo: photo, + ); + final photoFile = XFile.fromData( + fileData, + mimeType: 'image/jpeg', + ); + await photoFile.saveTo(saveLocation.path); + } + }, + ) + : Container(), + ), + ); + } + + TreeNode _buildSearchEntry(SearchEntry searchEntry) { + void selectPhoto(Photo photo) { + searchEntry.model.selectedPhoto = photo; + } + + String labelForPhoto(Photo photo) => 'Photo by ${photo.user!.name}'; + + return TreeNode( + content: Expanded(child: Text(searchEntry.query)), + children: searchEntry.photos + .map( + (photo) => TreeNode( + content: Expanded( + child: Semantics( + button: true, + onTap: () => selectPhoto(photo), + label: labelForPhoto(photo), + excludeSemantics: true, + child: InkWell( + onTap: () => selectPhoto(photo), + child: Padding( + padding: const EdgeInsets.all(12), + child: Text(labelForPhoto(photo)), + ), + ), + ), + ), + ), + ) + .toList(), + ); + } +} diff --git a/desktop_photo_search/material/lib/unsplash_access_key.dart b/desktop_photo_search/material/lib/unsplash_access_key.dart new file mode 100644 index 0000000..9f29f46 --- /dev/null +++ b/desktop_photo_search/material/lib/unsplash_access_key.dart @@ -0,0 +1,9 @@ +// 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. + +// To use this sample: Retrieve an API Access Key from https://unsplash.com/developers +const String unsplashAccessKey = ''; + +// The application name for the above API Access Key. +const unsplashAppName = ''; diff --git a/desktop_photo_search/material/linux/.gitignore b/desktop_photo_search/material/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/desktop_photo_search/material/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/desktop_photo_search/material/linux/CMakeLists.txt b/desktop_photo_search/material/linux/CMakeLists.txt new file mode 100644 index 0000000..d0728bd --- /dev/null +++ b/desktop_photo_search/material/linux/CMakeLists.txt @@ -0,0 +1,145 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "desktop_photo_search") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.desktop_photo_search") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/desktop_photo_search/material/linux/flutter/CMakeLists.txt b/desktop_photo_search/material/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/desktop_photo_search/material/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/desktop_photo_search/material/linux/flutter/generated_plugin_registrant.cc b/desktop_photo_search/material/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..cab1d27 --- /dev/null +++ b/desktop_photo_search/material/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,23 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); + g_autoptr(FlPluginRegistrar) menubar_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "MenubarPlugin"); + menubar_plugin_register_with_registrar(menubar_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/desktop_photo_search/material/linux/flutter/generated_plugin_registrant.h b/desktop_photo_search/material/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/desktop_photo_search/material/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/desktop_photo_search/material/linux/flutter/generated_plugins.cmake b/desktop_photo_search/material/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..a862141 --- /dev/null +++ b/desktop_photo_search/material/linux/flutter/generated_plugins.cmake @@ -0,0 +1,26 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux + menubar + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/desktop_photo_search/material/linux/main.cc b/desktop_photo_search/material/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/desktop_photo_search/material/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/desktop_photo_search/material/linux/my_application.cc b/desktop_photo_search/material/linux/my_application.cc new file mode 100644 index 0000000..1bc867f --- /dev/null +++ b/desktop_photo_search/material/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "desktop_photo_search"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "desktop_photo_search"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/desktop_photo_search/material/linux/my_application.h b/desktop_photo_search/material/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/desktop_photo_search/material/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/desktop_photo_search/material/macos/.gitignore b/desktop_photo_search/material/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/desktop_photo_search/material/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/desktop_photo_search/material/macos/Flutter/Flutter-Debug.xcconfig b/desktop_photo_search/material/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/desktop_photo_search/material/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/desktop_photo_search/material/macos/Flutter/Flutter-Release.xcconfig b/desktop_photo_search/material/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/desktop_photo_search/material/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/desktop_photo_search/material/macos/Flutter/GeneratedPluginRegistrant.swift b/desktop_photo_search/material/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..a6ebf3c --- /dev/null +++ b/desktop_photo_search/material/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,16 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import file_selector_macos +import menubar +import url_launcher_macos + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) + MenubarPlugin.register(with: registry.registrar(forPlugin: "MenubarPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) +} diff --git a/desktop_photo_search/material/macos/Podfile b/desktop_photo_search/material/macos/Podfile new file mode 100644 index 0000000..c795730 --- /dev/null +++ b/desktop_photo_search/material/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/desktop_photo_search/material/macos/Runner.xcodeproj/project.pbxproj b/desktop_photo_search/material/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..60cb908 --- /dev/null +++ b/desktop_photo_search/material/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,791 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 349709AA2363A125DB8F796B /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DE57AB81656FBBC865A54C0 /* Pods_RunnerTests.framework */; }; + 8C1757FD540FB5C48A0F5010 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97A28FC43BE38BD17EE3BEF4 /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* desktop_photo_search.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = desktop_photo_search.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 3BC6BD9CC02182BE360921F9 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 4CD23FAECAC5C74242FF3D88 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 5B9CEF07F8FE0383D113A5F2 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 8DE57AB81656FBBC865A54C0 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 90DDB9E62FE9B080E500C32C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + 97A28FC43BE38BD17EE3BEF4 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C1B05DB5B9FE3A79AB29816E /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + DE4C0D240D13D104B002FC8D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 349709AA2363A125DB8F796B /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8C1757FD540FB5C48A0F5010 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 3ED3842985EC204D5EF642E7 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* desktop_photo_search.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + 3ED3842985EC204D5EF642E7 /* Pods */ = { + isa = PBXGroup; + children = ( + 4CD23FAECAC5C74242FF3D88 /* Pods-Runner.debug.xcconfig */, + DE4C0D240D13D104B002FC8D /* Pods-Runner.release.xcconfig */, + 90DDB9E62FE9B080E500C32C /* Pods-Runner.profile.xcconfig */, + 5B9CEF07F8FE0383D113A5F2 /* Pods-RunnerTests.debug.xcconfig */, + 3BC6BD9CC02182BE360921F9 /* Pods-RunnerTests.release.xcconfig */, + C1B05DB5B9FE3A79AB29816E /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 97A28FC43BE38BD17EE3BEF4 /* Pods_Runner.framework */, + 8DE57AB81656FBBC865A54C0 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + B832EA5E3FD72100B3343EE3 /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 1EDB131C273CF6C004C76B91 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + C5A2BEEAAECD880F23AC144F /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* desktop_photo_search.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 1EDB131C273CF6C004C76B91 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + B832EA5E3FD72100B3343EE3 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + C5A2BEEAAECD880F23AC144F /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5B9CEF07F8FE0383D113A5F2 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.desktopPhotoSearch.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/desktop_photo_search.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/desktop_photo_search"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3BC6BD9CC02182BE360921F9 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.desktopPhotoSearch.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/desktop_photo_search.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/desktop_photo_search"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C1B05DB5B9FE3A79AB29816E /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.desktopPhotoSearch.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/desktop_photo_search.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/desktop_photo_search"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/desktop_photo_search/material/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/desktop_photo_search/material/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/desktop_photo_search/material/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/desktop_photo_search/material/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/desktop_photo_search/material/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..1bc8277 --- /dev/null +++ b/desktop_photo_search/material/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/desktop_photo_search/material/macos/Runner.xcworkspace/contents.xcworkspacedata b/desktop_photo_search/material/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/desktop_photo_search/material/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/desktop_photo_search/material/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/desktop_photo_search/material/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/desktop_photo_search/material/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/desktop_photo_search/material/macos/Runner/AppDelegate.swift b/desktop_photo_search/material/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..d53ef64 --- /dev/null +++ b/desktop_photo_search/material/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/desktop_photo_search/material/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/desktop_photo_search/material/macos/Runner/Base.lproj/MainMenu.xib b/desktop_photo_search/material/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..0a0a505 --- /dev/null +++ b/desktop_photo_search/material/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,344 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/desktop_photo_search/material/macos/Runner/Configs/AppInfo.xcconfig b/desktop_photo_search/material/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..59146e6 --- /dev/null +++ b/desktop_photo_search/material/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = desktop_photo_search + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.desktopPhotoSearch + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright Β© 2023 com.example. All rights reserved. diff --git a/desktop_photo_search/material/macos/Runner/Configs/Debug.xcconfig b/desktop_photo_search/material/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/desktop_photo_search/material/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/desktop_photo_search/material/macos/Runner/Configs/Release.xcconfig b/desktop_photo_search/material/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/desktop_photo_search/material/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/desktop_photo_search/material/macos/Runner/Configs/Warnings.xcconfig b/desktop_photo_search/material/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/desktop_photo_search/material/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/desktop_photo_search/material/macos/Runner/DebugProfile.entitlements b/desktop_photo_search/material/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..0eaccf1 --- /dev/null +++ b/desktop_photo_search/material/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,16 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.files.user-selected.read-write + + com.apple.security.network.client + + com.apple.security.network.server + + + diff --git a/desktop_photo_search/material/macos/Runner/Info.plist b/desktop_photo_search/material/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/desktop_photo_search/material/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/desktop_photo_search/material/macos/Runner/MainFlutterWindow.swift b/desktop_photo_search/material/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/desktop_photo_search/material/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/desktop_photo_search/material/macos/Runner/Release.entitlements b/desktop_photo_search/material/macos/Runner/Release.entitlements new file mode 100644 index 0000000..a046386 --- /dev/null +++ b/desktop_photo_search/material/macos/Runner/Release.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-write + + com.apple.security.network.client + + + diff --git a/desktop_photo_search/material/macos/RunnerTests/RunnerTests.swift b/desktop_photo_search/material/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..5418c9f --- /dev/null +++ b/desktop_photo_search/material/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import FlutterMacOS +import Cocoa +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/desktop_photo_search/material/pubspec.yaml b/desktop_photo_search/material/pubspec.yaml new file mode 100644 index 0000000..29ef3b1 --- /dev/null +++ b/desktop_photo_search/material/pubspec.yaml @@ -0,0 +1,54 @@ +name: desktop_photo_search_material +description: Search for Photos, using the Unsplash API. +publish_to: 'none' +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + built_collection: ^5.1.1 + built_value: ^8.6.1 + cupertino_icons: ^1.0.5 + file_selector: ^1.0.0 + flutter: + sdk: flutter + flutter_simple_treeview: ^3.0.2 + http: ^1.2.1 + logging: ^1.2.0 + menubar: + git: + url: https://github.com/google/flutter-desktop-embedding.git + path: plugins/menubar + provider: ^6.0.5 + transparent_image: ^2.0.1 + url_launcher: ^6.1.12 + uuid: ^4.0.0 + +dev_dependencies: + analysis_defaults: + path: ../../analysis_defaults + async: ^2.11.0 + build: ^3.0.0 + build_runner: ^2.4.6 + built_value_generator: ^8.6.1 + flutter_test: + sdk: flutter + grinder: ^0.9.4 + msix: ^3.16.1 + +flutter: + uses-material-design: true + +msix_config: + display_name: Flutter Desktop Photo Search + publisher_display_name: flutter.dev + store: false # Set to true to deploy to Microsoft Store + publisher: CN=01A6D5C0-D51A-4EEE-8DD0-F134DDD378F7 + identity_name: 16354flutter.dev.FlutterDesktopPhotoSearch + msix_version: 1.0.0.1 + icons_background_color: "#ffffff" + architecture: x64 + # See https://docs.microsoft.com/en-us/windows/uwp/packaging/app-capability-declarations + capabilities: "internetClient" diff --git a/desktop_photo_search/material/test/unsplash_test.dart b/desktop_photo_search/material/test/unsplash_test.dart new file mode 100644 index 0000000..c276c92 --- /dev/null +++ b/desktop_photo_search/material/test/unsplash_test.dart @@ -0,0 +1,538 @@ +// 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. + +import 'package:desktop_photo_search_material/src/unsplash/photo.dart'; +import 'package:desktop_photo_search_material/src/unsplash/search_photos_response.dart'; +import 'package:desktop_photo_search_material/src/unsplash/unsplash.dart'; +import 'package:desktop_photo_search_material/src/unsplash/user.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart'; +import 'package:http/testing.dart'; + +void main() { + test('Photo.fromJson', () { + const input = ''' +{ + "id": "Dwu85P9SOIk", + "created_at": "2016-05-03T11:00:28-04:00", + "updated_at": "2016-07-10T11:00:01-05:00", + "width": 2448, + "height": 3264, + "color": "#6E633A", + "downloads": 1345, + "likes": 24, + "liked_by_user": false, + "description": "A man drinking a coffee.", + "exif": { + "make": "Canon", + "model": "Canon EOS 40D", + "exposure_time": "0.011111111111111112", + "aperture": "4.970854", + "focal_length": "37", + "iso": 100 + }, + "location": { + "city": "Montreal", + "country": "Canada", + "position": { + "latitude": 45.4732984, + "longitude": -73.6384879 + } + }, + "tags": [ + { "title": "man" }, + { "title": "drinking" }, + { "title": "coffee" } + ], + "current_user_collections": [ + { + "id": 206, + "title": "Makers: Cat and Ben", + "published_at": "2016-01-12T18:16:09-05:00", + "updated_at": "2016-07-10T11:00:01-05:00", + "cover_photo": null, + "user": null + } + ], + "urls": { + "raw": "https://images.unsplash.com/photo-1417325384643-aac51acc9e5d", + "full": "https://images.unsplash.com/photo-1417325384643-aac51acc9e5d?q=75&fm=jpg", + "regular": "https://images.unsplash.com/photo-1417325384643-aac51acc9e5d?q=75&fm=jpg&w=1080&fit=max", + "small": "https://images.unsplash.com/photo-1417325384643-aac51acc9e5d?q=75&fm=jpg&w=400&fit=max", + "thumb": "https://images.unsplash.com/photo-1417325384643-aac51acc9e5d?q=75&fm=jpg&w=200&fit=max" + }, + "links": { + "self": "https://api.unsplash.com/photos/Dwu85P9SOIk", + "html": "https://unsplash.com/photos/Dwu85P9SOIk", + "download": "https://unsplash.com/photos/Dwu85P9SOIk/download", + "download_location": "https://api.unsplash.com/photos/Dwu85P9SOIk/download" + }, + "user": { + "id": "QPxL2MGqfrw", + "updated_at": "2016-07-10T11:00:01-05:00", + "username": "exampleuser", + "name": "Joe Example", + "portfolio_url": "https://example.com/", + "bio": "Just an everyday Joe", + "location": "Montreal", + "total_likes": 5, + "total_photos": 10, + "total_collections": 13, + "links": { + "self": "https://api.unsplash.com/users/exampleuser", + "html": "https://unsplash.com/exampleuser", + "photos": "https://api.unsplash.com/users/exampleuser/photos", + "likes": "https://api.unsplash.com/users/exampleuser/likes", + "portfolio": "https://api.unsplash.com/users/exampleuser/portfolio" + } + } +} + '''; + + final photo = Photo.fromJson(input)!; + expect(photo.id, 'Dwu85P9SOIk'); + expect(photo.createdAt, '2016-05-03T11:00:28-04:00'); + expect(photo.updatedAt, '2016-07-10T11:00:01-05:00'); + expect(photo.width, 2448); + expect(photo.height, 3264); + expect(photo.color, '#6E633A'); + expect(photo.downloads, 1345); + expect(photo.likedByUser, false); + expect(photo.exif!.make, 'Canon'); + expect(photo.exif!.iso, 100); + expect(photo.location!.city, 'Montreal'); + expect(photo.location!.country, 'Canada'); + expect(photo.location!.position!.latitude, 45.4732984); + expect(photo.location!.position!.longitude, -73.6384879); + }); + + test('User.fromJson', () { + const input = ''' +{ + "id": "pXhwzz1JtQU", + "updated_at": "2016-07-10T11:00:01-05:00", + "username": "jimmyexample", + "name": "James Example", + "first_name": "James", + "last_name": "Example", + "instagram_username": "instantgrammer", + "twitter_username": "jimmy", + "portfolio_url": null, + "bio": "The user's bio", + "location": "Montreal, Qc", + "total_likes": 20, + "total_photos": 10, + "total_collections": 5, + "followed_by_user": false, + "followers_count": 300, + "following_count": 25, + "downloads": 225974, + "profile_image": { + "small": "https://images.unsplash.com/face-springmorning.jpg?q=80&fm=jpg&crop=faces&fit=crop&h=32&w=32", + "medium": "https://images.unsplash.com/face-springmorning.jpg?q=80&fm=jpg&crop=faces&fit=crop&h=64&w=64", + "large": "https://images.unsplash.com/face-springmorning.jpg?q=80&fm=jpg&crop=faces&fit=crop&h=128&w=128" + }, + "badge": { + "title": "Book contributor", + "primary": true, + "slug": "book-contributor", + "link": "https://book.unsplash.com" + }, + "links": { + "self": "https://api.unsplash.com/users/jimmyexample", + "html": "https://unsplash.com/jimmyexample", + "photos": "https://api.unsplash.com/users/jimmyexample/photos", + "likes": "https://api.unsplash.com/users/jimmyexample/likes", + "portfolio": "https://api.unsplash.com/users/jimmyexample/portfolio" + } +} + '''; + + final user = User.fromJson(input)!; + expect(user.id, 'pXhwzz1JtQU'); + }); + + test('SearchPhotosResponse.fromJson', () { + const input = ''' +{ + "total": 133, + "total_pages": 7, + "results": [ + { + "id": "eOLpJytrbsQ", + "created_at": "2014-11-18T14:35:36-05:00", + "width": 4000, + "height": 3000, + "color": "#A7A2A1", + "likes": 286, + "liked_by_user": false, + "description": "A man drinking a coffee.", + "user": { + "id": "Ul0QVz12Goo", + "username": "ugmonk", + "name": "Jeff Sheldon", + "first_name": "Jeff", + "last_name": "Sheldon", + "instagram_username": "instantgrammer", + "twitter_username": "ugmonk", + "portfolio_url": "http://ugmonk.com/", + "profile_image": { + "small": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=32&w=32&s=7cfe3b93750cb0c93e2f7caec08b5a41", + "medium": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=64&w=64&s=5a9dc749c43ce5bd60870b129a40902f", + "large": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=128&w=128&s=32085a077889586df88bfbe406692202" + }, + "links": { + "self": "https://api.unsplash.com/users/ugmonk", + "html": "http://unsplash.com/@ugmonk", + "photos": "https://api.unsplash.com/users/ugmonk/photos", + "likes": "https://api.unsplash.com/users/ugmonk/likes" + } + }, + "current_user_collections": [], + "urls": { + "raw": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f", + "full": "https://hd.unsplash.com/photo-1416339306562-f3d12fefd36f", + "regular": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=92f3e02f63678acc8416d044e189f515", + "small": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&s=263af33585f9d32af39d165b000845eb", + "thumb": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&s=8aae34cf35df31a592f0bef16e6342ef" + }, + "links": { + "self": "https://api.unsplash.com/photos/eOLpJytrbsQ", + "html": "http://unsplash.com/photos/eOLpJytrbsQ", + "download": "http://unsplash.com/photos/eOLpJytrbsQ/download" + } + } + ] +} + '''; + + final response = SearchPhotosResponse.fromJson(input)!; + expect(response.total, 133); + expect(response.totalPages, 7); + expect(response.results[0].id, 'eOLpJytrbsQ'); + expect(response.results[0].user!.id, 'Ul0QVz12Goo'); + }); + + group('Unsplash API client', () { + test('handles success', () async { + const searchPhotosResponseBody = ''' +{ + "total": 133, + "total_pages": 7, + "results": [ + { + "id": "eOLpJytrbsQ", + "created_at": "2014-11-18T14:35:36-05:00", + "width": 4000, + "height": 3000, + "color": "#A7A2A1", + "likes": 286, + "liked_by_user": false, + "description": "A man drinking a coffee.", + "user": { + "id": "Ul0QVz12Goo", + "username": "ugmonk", + "name": "Jeff Sheldon", + "first_name": "Jeff", + "last_name": "Sheldon", + "instagram_username": "instantgrammer", + "twitter_username": "ugmonk", + "portfolio_url": "http://ugmonk.com/", + "profile_image": { + "small": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=32&w=32&s=7cfe3b93750cb0c93e2f7caec08b5a41", + "medium": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=64&w=64&s=5a9dc749c43ce5bd60870b129a40902f", + "large": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=128&w=128&s=32085a077889586df88bfbe406692202" + }, + "links": { + "self": "https://api.unsplash.com/users/ugmonk", + "html": "http://unsplash.com/@ugmonk", + "photos": "https://api.unsplash.com/users/ugmonk/photos", + "likes": "https://api.unsplash.com/users/ugmonk/likes" + } + }, + "current_user_collections": [], + "urls": { + "raw": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f", + "full": "https://hd.unsplash.com/photo-1416339306562-f3d12fefd36f", + "regular": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=92f3e02f63678acc8416d044e189f515", + "small": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&s=263af33585f9d32af39d165b000845eb", + "thumb": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&s=8aae34cf35df31a592f0bef16e6342ef" + }, + "links": { + "self": "https://api.unsplash.com/photos/eOLpJytrbsQ", + "html": "http://unsplash.com/photos/eOLpJytrbsQ", + "download": "http://unsplash.com/photos/eOLpJytrbsQ/download" + } + } + ] +} + '''; + + final httpClient = MockClient((req) async { + return Response( + searchPhotosResponseBody, + 200, + request: req, + headers: {'content-type': 'application/json'}, + ); + }); + + final unsplashClient = Unsplash( + accessKey: 'not-an-access-key', + httpClient: httpClient, + ); + + final response = (await unsplashClient.searchPhotos(query: 'red'))!; + + expect(response.total, 133); + expect(response.totalPages, 7); + expect(response.results[0].id, 'eOLpJytrbsQ'); + expect(response.results[0].user!.id, 'Ul0QVz12Goo'); + }); + + test('handles failure', () async { + const apiError = + '{"errors":["OAuth error: The access token is invalid"]}'; + + final httpClient = MockClient((req) async { + return Response( + apiError, + 401, + request: req, + headers: {'content-type': 'application/json'}, + ); + }); + + final unsplashClient = Unsplash( + accessKey: 'not-an-access-key', + httpClient: httpClient, + ); + + try { + await unsplashClient.searchPhotos(query: 'red'); + fail('UnsplashException should have been thrown'); + } on UnsplashException catch (e) { + expect( + e.toString(), + 'UnsplashException: OAuth error: The access token is invalid', + ); + } + }); + + test('handles broken JSON', () async { + const apiError = '{"tot'; // truncated JSON. + + final httpClient = MockClient((req) async { + return Response( + apiError, + 401, + request: req, + headers: {'content-type': 'application/json'}, + ); + }); + + final unsplashClient = Unsplash( + accessKey: 'not-an-access-key', + httpClient: httpClient, + ); + + try { + await unsplashClient.searchPhotos(query: 'red'); + fail('UnsplashException should have been thrown'); + } on UnsplashException catch (e) { + expect(e.toString(), 'UnsplashException: Invalid JSON received'); + } + }); + }); + + test('handles utf8 content in JSON', () async { + const searchPhotosResponseBody = ''' +{ + "total": 22395, + "total_pages": 2240, + "results": [ + { + "id": "E4u_Zo9PET8", + "created_at": "2019-12-29T13:45:28-05:00", + "updated_at": "2020-11-05T17:12:18-05:00", + "promoted_at": null, + "width": 2598, + "height": 4618, + "color": "#A53E40", + "blur_hash": "LlO{8lL#XSbu*Jtla0jZOrb^ozjF", + "description": null, + "alt_description": "red apparel", + "urls": { + "raw": "https://images.unsplash.com/photo-1577645113639-32537a4a938b?ixlib=rb-1.2.1\u0026ixid=eyJhcHBfaWQiOjM5NTU5fQ", + "full": "https://images.unsplash.com/photo-1577645113639-32537a4a938b?ixlib=rb-1.2.1\u0026q=85\u0026fm=jpg\u0026crop=entropy\u0026cs=srgb\u0026ixid=eyJhcHBfaWQiOjM5NTU5fQ", + "regular": "https://images.unsplash.com/photo-1577645113639-32537a4a938b?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=entropy\u0026cs=tinysrgb\u0026w=1080\u0026fit=max\u0026ixid=eyJhcHBfaWQiOjM5NTU5fQ", + "small": "https://images.unsplash.com/photo-1577645113639-32537a4a938b?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=entropy\u0026cs=tinysrgb\u0026w=400\u0026fit=max\u0026ixid=eyJhcHBfaWQiOjM5NTU5fQ", + "thumb": "https://images.unsplash.com/photo-1577645113639-32537a4a938b?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=entropy\u0026cs=tinysrgb\u0026w=200\u0026fit=max\u0026ixid=eyJhcHBfaWQiOjM5NTU5fQ" + }, + "links": { + "self": "https://api.unsplash.com/photos/E4u_Zo9PET8", + "html": "https://unsplash.com/photos/E4u_Zo9PET8", + "download": "https://unsplash.com/photos/E4u_Zo9PET8/download", + "download_location": "https://api.unsplash.com/photos/E4u_Zo9PET8/download" + }, + "categories": [], + "likes": 132, + "liked_by_user": false, + "current_user_collections": [], + "sponsorship": null, + "user": { + "id": "_2nQcPrbyuE", + "updated_at": "2020-11-06T01:37:51-05:00", + "username": "svalenas", + "name": "Sergiu VΔƒlenaΘ™", + "first_name": "Sergiu", + "last_name": "VΔƒlenaΘ™", + "twitter_username": null, + "portfolio_url": null, + "bio": "Gifted psychologist and enthusiast photographer", + "location": "Cluj-Napoca, Romania", + "links": { + "self": "https://api.unsplash.com/users/svalenas", + "html": "https://unsplash.com/@svalenas", + "photos": "https://api.unsplash.com/users/svalenas/photos", + "likes": "https://api.unsplash.com/users/svalenas/likes", + "portfolio": "https://api.unsplash.com/users/svalenas/portfolio", + "following": "https://api.unsplash.com/users/svalenas/following", + "followers": "https://api.unsplash.com/users/svalenas/followers" + }, + "profile_image": { + "small": "https://images.unsplash.com/profile-1597067601066-d43176d68553image?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=faces\u0026cs=tinysrgb\u0026fit=crop\u0026h=32\u0026w=32", + "medium": "https://images.unsplash.com/profile-1597067601066-d43176d68553image?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=faces\u0026cs=tinysrgb\u0026fit=crop\u0026h=64\u0026w=64", + "large": "https://images.unsplash.com/profile-1597067601066-d43176d68553image?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=faces\u0026cs=tinysrgb\u0026fit=crop\u0026h=128\u0026w=128" + }, + "instagram_username": "svalenas", + "total_collections": 0, + "total_likes": 413, + "total_photos": 129, + "accepted_tos": true + }, + "tags": [ + { + "type": "landing_page", + "title": "red", + "source": { + "ancestry": { + "type": { + "slug": "wallpapers", + "pretty_slug": "HD Wallpapers" + }, + "category": { + "slug": "colors", + "pretty_slug": "Color" + }, + "subcategory": { + "slug": "red", + "pretty_slug": "Red" + } + }, + "title": "HD Red Wallpapers", + "subtitle": "Download Free Red Wallpapers", + "description": "Choose from a curated selection of red wallpapers for your mobile and desktop screens. Always free on Unsplash.", + "meta_title": "Red Wallpapers: Free HD Download [500+ HQ] | Unsplash", + "meta_description": "Choose from hundreds of free red wallpapers. Download HD wallpapers for free on Unsplash.", + "cover_photo": { + "id": "HyBXy5PHQR8", + "created_at": "2018-02-17T13:44:58-05:00", + "updated_at": "2020-10-21T01:07:42-04:00", + "promoted_at": null, + "width": 3024, + "height": 4032, + "color": "#C51918", + "blur_hash": "L9Bmx_o1o1Jl|cwxWpWpN]\$5N]sU", + "description": null, + "alt_description": "red textile", + "urls": { + "raw": "https://images.unsplash.com/photo-1518893063132-36e46dbe2428?ixlib=rb-1.2.1", + "full": "https://images.unsplash.com/photo-1518893063132-36e46dbe2428?ixlib=rb-1.2.1\u0026q=85\u0026fm=jpg\u0026crop=entropy\u0026cs=srgb", + "regular": "https://images.unsplash.com/photo-1518893063132-36e46dbe2428?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=entropy\u0026cs=tinysrgb\u0026w=1080\u0026fit=max", + "small": "https://images.unsplash.com/photo-1518893063132-36e46dbe2428?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=entropy\u0026cs=tinysrgb\u0026w=400\u0026fit=max", + "thumb": "https://images.unsplash.com/photo-1518893063132-36e46dbe2428?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=entropy\u0026cs=tinysrgb\u0026w=200\u0026fit=max" + }, + "links": { + "self": "https://api.unsplash.com/photos/HyBXy5PHQR8", + "html": "https://unsplash.com/photos/HyBXy5PHQR8", + "download": "https://unsplash.com/photos/HyBXy5PHQR8/download", + "download_location": "https://api.unsplash.com/photos/HyBXy5PHQR8/download" + }, + "categories": [], + "likes": 1243, + "liked_by_user": false, + "current_user_collections": [], + "sponsorship": null, + "user": { + "id": "6nkkrwW9M-s", + "updated_at": "2020-10-22T20:44:54-04:00", + "username": "montylov", + "name": "MontyLov", + "first_name": "MontyLov", + "last_name": null, + "twitter_username": "MontyLov", + "portfolio_url": "http://montylov.com", + "bio": "Stay humble and innovate.", + "location": null, + "links": { + "self": "https://api.unsplash.com/users/montylov", + "html": "https://unsplash.com/@montylov", + "photos": "https://api.unsplash.com/users/montylov/photos", + "likes": "https://api.unsplash.com/users/montylov/likes", + "portfolio": "https://api.unsplash.com/users/montylov/portfolio", + "following": "https://api.unsplash.com/users/montylov/following", + "followers": "https://api.unsplash.com/users/montylov/followers" + }, + "profile_image": { + "small": "https://images.unsplash.com/profile-1588478301600-b5e5203a574aimage?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=faces\u0026cs=tinysrgb\u0026fit=crop\u0026h=32\u0026w=32", + "medium": "https://images.unsplash.com/profile-1588478301600-b5e5203a574aimage?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=faces\u0026cs=tinysrgb\u0026fit=crop\u0026h=64\u0026w=64", + "large": "https://images.unsplash.com/profile-1588478301600-b5e5203a574aimage?ixlib=rb-1.2.1\u0026q=80\u0026fm=jpg\u0026crop=faces\u0026cs=tinysrgb\u0026fit=crop\u0026h=128\u0026w=128" + }, + "instagram_username": "montylov", + "total_collections": 1, + "total_likes": 0, + "total_photos": 79, + "accepted_tos": false + } + } + } + }, + { + "type": "search", + "title": "rug" + }, + { + "type": "search", + "title": "plant" + } + ] + } + ] +} + '''; + + final httpClient = MockClient((req) async { + return Response( + searchPhotosResponseBody, + 200, + request: req, + headers: {'content-type': 'application/json; charset=utf-8'}, + ); + }); + + final unsplashClient = Unsplash( + accessKey: 'not-an-access-key', + httpClient: httpClient, + ); + + final response = (await unsplashClient.searchPhotos(query: 'red'))!; + + expect(response.total, 22395); + expect(response.totalPages, 2240); + expect(response.results[0].id, 'E4u_Zo9PET8'); + expect(response.results[0].user!.id, '_2nQcPrbyuE'); + expect(response.results[0].user!.name, 'Sergiu VΔƒlenaΘ™'); + }); +} diff --git a/desktop_photo_search/material/test/widget_test.dart b/desktop_photo_search/material/test/widget_test.dart new file mode 100644 index 0000000..26f7d60 --- /dev/null +++ b/desktop_photo_search/material/test/widget_test.dart @@ -0,0 +1,134 @@ +// 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. + +import 'dart:typed_data'; + +import 'package:desktop_photo_search_material/src/model/photo_search_model.dart'; +import 'package:desktop_photo_search_material/src/unsplash/photo.dart'; +import 'package:desktop_photo_search_material/src/unsplash/search_photos_response.dart'; +import 'package:desktop_photo_search_material/src/unsplash/unsplash.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; + +class FakeUnsplash implements Unsplash { + static const searchPhotosResponse = ''' +{ + "total": 133, + "total_pages": 7, + "results": [ + { + "id": "eOLpJytrbsQ", + "created_at": "2014-11-18T14:35:36-05:00", + "width": 4000, + "height": 3000, + "color": "#A7A2A1", + "likes": 286, + "liked_by_user": false, + "description": "A man drinking a coffee.", + "user": { + "id": "Ul0QVz12Goo", + "username": "ugmonk", + "name": "Jeff Sheldon", + "first_name": "Jeff", + "last_name": "Sheldon", + "instagram_username": "instantgrammer", + "twitter_username": "ugmonk", + "portfolio_url": "http://ugmonk.com/", + "profile_image": { + "small": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=32&w=32&s=7cfe3b93750cb0c93e2f7caec08b5a41", + "medium": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=64&w=64&s=5a9dc749c43ce5bd60870b129a40902f", + "large": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=128&w=128&s=32085a077889586df88bfbe406692202" + }, + "links": { + "self": "https://api.unsplash.com/users/ugmonk", + "html": "http://unsplash.com/@ugmonk", + "photos": "https://api.unsplash.com/users/ugmonk/photos", + "likes": "https://api.unsplash.com/users/ugmonk/likes" + } + }, + "current_user_collections": [], + "urls": { + "raw": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f", + "full": "https://hd.unsplash.com/photo-1416339306562-f3d12fefd36f", + "regular": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=92f3e02f63678acc8416d044e189f515", + "small": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&s=263af33585f9d32af39d165b000845eb", + "thumb": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&s=8aae34cf35df31a592f0bef16e6342ef" + }, + "links": { + "self": "https://api.unsplash.com/photos/eOLpJytrbsQ", + "html": "http://unsplash.com/photos/eOLpJytrbsQ", + "download": "http://unsplash.com/photos/eOLpJytrbsQ/download" + } + } + ] +} + '''; + + @override + Future searchPhotos({ + String? query, + num page = 1, + num perPage = 10, + List collections = const [], + SearchPhotosOrientation? orientation, + }) async { + return SearchPhotosResponse.fromJson(searchPhotosResponse); + } + + @override + Future download(Photo photo) { + throw UnimplementedError(); + } +} + +const fabKey = Key('fab'); + +class PhotoSearchModelTester extends StatelessWidget { + const PhotoSearchModelTester({required this.query, super.key}); + final String query; + @override + Widget build(BuildContext context) { + return MaterialApp( + home: TextButton( + key: fabKey, + onPressed: () async { + await Provider.of( + context, + listen: false, + ).addSearch(query); + }, + child: const Text('Search for a Photo'), + ), + ); + } +} + +void main() { + group('search_list', () { + testWidgets('starts empty', (tester) async { + final unsplashSearches = PhotoSearchModel(FakeUnsplash()); + final testWidget = ChangeNotifierProvider( + create: (context) => unsplashSearches, + child: const PhotoSearchModelTester(query: 'clouds'), + ); + await tester.pumpWidget(testWidget); + expect(unsplashSearches.entries.length, 0); + }); + + testWidgets('addSearch adds searches', (tester) async { + final unsplashSearches = PhotoSearchModel(FakeUnsplash()); + final testWidget = ChangeNotifierProvider( + create: (context) => unsplashSearches, + child: const PhotoSearchModelTester(query: 'clouds'), + ); + await tester.pumpWidget(testWidget); + await tester.tap(find.byKey(fabKey)); + await tester.tap(find.byKey(fabKey)); + await tester.pumpAndSettle(); + + expect(unsplashSearches.entries.length, 2); + }); + }); +} diff --git a/desktop_photo_search/material/tool/grind.dart b/desktop_photo_search/material/tool/grind.dart new file mode 100644 index 0000000..a672fec --- /dev/null +++ b/desktop_photo_search/material/tool/grind.dart @@ -0,0 +1,55 @@ +// 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. + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:async/async.dart' show StreamGroup; +import 'package:grinder/grinder.dart'; + +void main(List args) => grind(args); + +@DefaultTask() +@Depends(pubGet, generateJsonBindings, analyzeSource, test) +void build() {} + +@Task() +Future pubGet() async => + _logProcessOutput(Process.start('flutter', ['pub', 'get'])); + +@Task() +Future generateJsonBindings() async => _logProcessOutput( + Process.start('flutter', [ + 'pub', + 'run', + 'build_runner', + 'build', + '--delete-conflicting-outputs', + ]), +); + +@Task() +Future watch() async => _logProcessOutput( + Process.start('dart', ['run', 'build_runner', 'watch']), +); + +@Task() +Future analyzeSource() async => + _logProcessOutput(Process.start('flutter', ['analyze'])); + +@Task() +Future test() async => + _logProcessOutput(Process.start('flutter', ['test'])); + +@Task() +Future clean() => _logProcessOutput(Process.start('flutter', ['clean'])); + +Future _logProcessOutput(Future proc) async { + final process = await proc; + final output = StreamGroup.merge([process.stdout, process.stderr]); + await for (final message in output) { + log(utf8.decode(message)); + } +} diff --git a/desktop_photo_search/material/windows/.gitignore b/desktop_photo_search/material/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/desktop_photo_search/material/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/desktop_photo_search/material/windows/CMakeLists.txt b/desktop_photo_search/material/windows/CMakeLists.txt new file mode 100644 index 0000000..f3415cf --- /dev/null +++ b/desktop_photo_search/material/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(desktop_photo_search LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "desktop_photo_search") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/desktop_photo_search/material/windows/flutter/CMakeLists.txt b/desktop_photo_search/material/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/desktop_photo_search/material/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/desktop_photo_search/material/windows/flutter/generated_plugin_registrant.cc b/desktop_photo_search/material/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..b16c80c --- /dev/null +++ b/desktop_photo_search/material/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,20 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); + MenubarPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("MenubarPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/desktop_photo_search/material/windows/flutter/generated_plugin_registrant.h b/desktop_photo_search/material/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/desktop_photo_search/material/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/desktop_photo_search/material/windows/flutter/generated_plugins.cmake b/desktop_photo_search/material/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..31159ac --- /dev/null +++ b/desktop_photo_search/material/windows/flutter/generated_plugins.cmake @@ -0,0 +1,26 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + file_selector_windows + menubar + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/desktop_photo_search/material/windows/runner/CMakeLists.txt b/desktop_photo_search/material/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/desktop_photo_search/material/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/desktop_photo_search/material/windows/runner/Runner.rc b/desktop_photo_search/material/windows/runner/Runner.rc new file mode 100644 index 0000000..fc74684 --- /dev/null +++ b/desktop_photo_search/material/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "desktop_photo_search" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "desktop_photo_search" "\0" + VALUE "LegalCopyright", "Copyright (C) 2023 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "desktop_photo_search.exe" "\0" + VALUE "ProductName", "desktop_photo_search" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/desktop_photo_search/material/windows/runner/flutter_window.cpp b/desktop_photo_search/material/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/desktop_photo_search/material/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/desktop_photo_search/material/windows/runner/flutter_window.h b/desktop_photo_search/material/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/desktop_photo_search/material/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/desktop_photo_search/material/windows/runner/main.cpp b/desktop_photo_search/material/windows/runner/main.cpp new file mode 100644 index 0000000..23aadea --- /dev/null +++ b/desktop_photo_search/material/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"desktop_photo_search", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/desktop_photo_search/material/windows/runner/resource.h b/desktop_photo_search/material/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/desktop_photo_search/material/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/desktop_photo_search/material/windows/runner/resources/app_icon.ico b/desktop_photo_search/material/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/desktop_photo_search/material/windows/runner/resources/app_icon.ico differ diff --git a/desktop_photo_search/material/windows/runner/runner.exe.manifest b/desktop_photo_search/material/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..a42ea76 --- /dev/null +++ b/desktop_photo_search/material/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/desktop_photo_search/material/windows/runner/utils.cpp b/desktop_photo_search/material/windows/runner/utils.cpp new file mode 100644 index 0000000..b2b0873 --- /dev/null +++ b/desktop_photo_search/material/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/desktop_photo_search/material/windows/runner/utils.h b/desktop_photo_search/material/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/desktop_photo_search/material/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/desktop_photo_search/material/windows/runner/win32_window.cpp b/desktop_photo_search/material/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/desktop_photo_search/material/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/desktop_photo_search/material/windows/runner/win32_window.h b/desktop_photo_search/material/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/desktop_photo_search/material/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..c0223c7 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,13 @@ + + + + + Redirecting... + + + + +

Redirecting...

+

If you are not redirected automatically, follow this link to the Flutter learning resources.

+ + diff --git a/dynamic_theme/.gitignore b/dynamic_theme/.gitignore new file mode 100644 index 0000000..29a3a50 --- /dev/null +++ b/dynamic_theme/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/dynamic_theme/.idx/dev.nix b/dynamic_theme/.idx/dev.nix new file mode 100644 index 0000000..0354484 --- /dev/null +++ b/dynamic_theme/.idx/dev.nix @@ -0,0 +1,62 @@ +# To learn more about how to use Nix to configure your environment +# see: https://developers.google.com/idx/guides/customize-idx-env +{ pkgs, ... }: { + # Which nixpkgs channel to use. + channel = "stable-23.11"; # or "unstable" + # Use https://search.nixos.org/packages to find packages + packages = [ + pkgs.nodePackages.firebase-tools + pkgs.jdk17 + pkgs.unzip + ]; + # Sets environment variables in the workspace + env = {}; + idx = { + # Search for the extensions you want on https://open-vsx.org/ and use "publisher.id" + extensions = [ + "Dart-Code.flutter" + "Dart-Code.dart-code" + ]; + workspace = { + # Runs when a workspace is first created with this `dev.nix` file + onCreate = { + build-flutter = '' + cd /home/user/myapp/android + ./gradlew \ + --parallel \ + -Pverbose=true \ + -Ptarget-platform=android-x86 \ + -Ptarget=/home/user/myapp/lib/main.dart \ + -Pbase-application-name=android.app.Application \ + -Pdart-defines=RkxVVFRFUl9XRUJfQ0FOVkFTS0lUX1VSTD1odHRwczovL3d3dy5nc3RhdGljLmNvbS9mbHV0dGVyLWNhbnZhc2tpdC85NzU1MDkwN2I3MGY0ZjNiMzI4YjZjMTYwMGRmMjFmYWMxYTE4ODlhLw== \ + -Pdart-obfuscation=false \ + -Ptrack-widget-creation=true \ + -Ptree-shake-icons=false \ + -Pfilesystem-scheme=org-dartlang-root \ + assembleDebug + # TODO: Execute web build in debug mode. + # flutter run does this transparently either way + # https://github.com/flutter/flutter/issues/96283#issuecomment-1144750411 + # flutter build web --profile --dart-define=Dart2jsOptimization=O0 + adb -s localhost:5555 wait-for-device + ''; + }; + + # To run something each time the workspace is (re)started, use the `onStart` hook + }; + # Enable previews and customize configuration + previews = { + enable = true; + previews = { + web = { + command = ["flutter" "run" "--machine" "-d" "web-server" "--web-hostname" "0.0.0.0" "--web-port" "$PORT"]; + manager = "flutter"; + }; + android = { + command = ["flutter" "run" "--machine" "-d" "android" "-d" "localhost:5555"]; + manager = "flutter"; + }; + }; + }; + }; +} \ No newline at end of file diff --git a/dynamic_theme/.metadata b/dynamic_theme/.metadata new file mode 100644 index 0000000..8629a4e --- /dev/null +++ b/dynamic_theme/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "abb292a07e20d696c4568099f918f6c5f330e6b0" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 + base_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 + - platform: android + create_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 + base_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 + - platform: ios + create_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 + base_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 + - platform: linux + create_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 + base_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 + - platform: macos + create_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 + base_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 + - platform: web + create_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 + base_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 + - platform: windows + create_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 + base_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/dynamic_theme/README.md b/dynamic_theme/README.md new file mode 100644 index 0000000..3d3b57c --- /dev/null +++ b/dynamic_theme/README.md @@ -0,0 +1,21 @@ +# Flutter Dynamic Theme Sample + +A developer sample demonstrating how to call on-device Flutter APIs based on +output from the Gemini API. Allows the user to dynamically change font scale, +colors, and other theme properties inside a running app using natural language. + +## Goals + +* Show how to use the Gemini API for a chat session. +* Demonstrate how to use functions calls with the Gemini API. +* Show how to tie responses from Gemini to specific local API calls. + +## Questions/issues + +If you have a general question about any of the techniques you see in +the sample, Flutter's open source community is a great place to find answers. +You can find links to online and local groups at +[https://flutter.dev/community]! + +If you run into an issue with the sample itself, please file an issue +in the [main Flutter repo](https://github.com/flutter/flutter/issues). diff --git a/dynamic_theme/analysis_options.yaml b/dynamic_theme/analysis_options.yaml new file mode 100644 index 0000000..5e2133e --- /dev/null +++ b/dynamic_theme/analysis_options.yaml @@ -0,0 +1 @@ +include: ../analysis_options.yaml diff --git a/dynamic_theme/android/.gitignore b/dynamic_theme/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/dynamic_theme/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/dynamic_theme/android/app/build.gradle b/dynamic_theme/android/app/build.gradle new file mode 100644 index 0000000..9ef1ada --- /dev/null +++ b/dynamic_theme/android/app/build.gradle @@ -0,0 +1,67 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +android { + namespace "com.example.dynamic_theme" + compileSdk flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.dynamic_theme" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies {} diff --git a/dynamic_theme/android/app/src/debug/AndroidManifest.xml b/dynamic_theme/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/dynamic_theme/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/dynamic_theme/android/app/src/main/AndroidManifest.xml b/dynamic_theme/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b8df4d6 --- /dev/null +++ b/dynamic_theme/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/dynamic_theme/android/app/src/main/kotlin/com/example/dynamic_theme/MainActivity.kt b/dynamic_theme/android/app/src/main/kotlin/com/example/dynamic_theme/MainActivity.kt new file mode 100644 index 0000000..5940b5f --- /dev/null +++ b/dynamic_theme/android/app/src/main/kotlin/com/example/dynamic_theme/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.dynamic_theme + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/dynamic_theme/android/app/src/main/res/drawable-v21/launch_background.xml b/dynamic_theme/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/dynamic_theme/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/dynamic_theme/android/app/src/main/res/drawable/launch_background.xml b/dynamic_theme/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/dynamic_theme/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/dynamic_theme/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/dynamic_theme/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/dynamic_theme/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/dynamic_theme/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/dynamic_theme/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/dynamic_theme/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/dynamic_theme/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/dynamic_theme/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/dynamic_theme/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/dynamic_theme/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/dynamic_theme/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/dynamic_theme/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/dynamic_theme/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/dynamic_theme/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/dynamic_theme/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/dynamic_theme/android/app/src/main/res/values-night/styles.xml b/dynamic_theme/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/dynamic_theme/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/dynamic_theme/android/app/src/main/res/values/styles.xml b/dynamic_theme/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/dynamic_theme/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/dynamic_theme/android/app/src/profile/AndroidManifest.xml b/dynamic_theme/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/dynamic_theme/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/dynamic_theme/android/build.gradle b/dynamic_theme/android/build.gradle new file mode 100644 index 0000000..bc157bd --- /dev/null +++ b/dynamic_theme/android/build.gradle @@ -0,0 +1,18 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/dynamic_theme/android/gradle.properties b/dynamic_theme/android/gradle.properties new file mode 100644 index 0000000..598d13f --- /dev/null +++ b/dynamic_theme/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G +android.useAndroidX=true +android.enableJetifier=true diff --git a/dynamic_theme/android/gradle/wrapper/gradle-wrapper.properties b/dynamic_theme/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..efdcc4a --- /dev/null +++ b/dynamic_theme/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip diff --git a/dynamic_theme/android/settings.gradle b/dynamic_theme/android/settings.gradle new file mode 100644 index 0000000..1bb3284 --- /dev/null +++ b/dynamic_theme/android/settings.gradle @@ -0,0 +1,26 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.9.1" apply false + id "org.jetbrains.kotlin.android" version "2.1.0" apply false +} + +include ":app" diff --git a/dynamic_theme/idx-template.json b/dynamic_theme/idx-template.json new file mode 100644 index 0000000..8fca44b --- /dev/null +++ b/dynamic_theme/idx-template.json @@ -0,0 +1,9 @@ +{ + "name": "Flutter dynamic theme", + "description": "A template for a Flutter app that uses Gemini to dynamically create a theme.", + "icon": "https://www.gstatic.com/images/branding/productlogos/idx/v1/192px.svg", + "params": [], + "host": { + "virtualization": true + } +} \ No newline at end of file diff --git a/dynamic_theme/idx-template.nix b/dynamic_theme/idx-template.nix new file mode 100644 index 0000000..5ef65ad --- /dev/null +++ b/dynamic_theme/idx-template.nix @@ -0,0 +1,15 @@ +# No user-configurable parameters +{ pkgs, ... }: { + packages = [ + pkgs.flutter + ]; + # Shell script that produces the final environment + bootstrap = '' + export HOME=/home/user + export PATH="$PATH":"$HOME/flutter/bin" + + cp -rf ${./.} "$out" + chmod -R +w "$out" + rm -rf "$out/.git" "$out/idx-template".{nix,json} + ''; +} \ No newline at end of file diff --git a/dynamic_theme/ios/.gitignore b/dynamic_theme/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/dynamic_theme/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/dynamic_theme/ios/Flutter/AppFrameworkInfo.plist b/dynamic_theme/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/dynamic_theme/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/dynamic_theme/ios/Flutter/Debug.xcconfig b/dynamic_theme/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/dynamic_theme/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/dynamic_theme/ios/Flutter/Release.xcconfig b/dynamic_theme/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/dynamic_theme/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/dynamic_theme/ios/Podfile b/dynamic_theme/ios/Podfile new file mode 100644 index 0000000..d97f17e --- /dev/null +++ b/dynamic_theme/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/dynamic_theme/ios/Runner.xcodeproj/project.pbxproj b/dynamic_theme/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..c044b87 --- /dev/null +++ b/dynamic_theme/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,616 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.dynamicTheme; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.dynamicTheme.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.dynamicTheme.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.dynamicTheme.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.dynamicTheme; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.dynamicTheme; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/dynamic_theme/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/dynamic_theme/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/dynamic_theme/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/dynamic_theme/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/dynamic_theme/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/dynamic_theme/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/dynamic_theme/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/dynamic_theme/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/dynamic_theme/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/dynamic_theme/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/dynamic_theme/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..8e3ca5d --- /dev/null +++ b/dynamic_theme/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dynamic_theme/ios/Runner.xcworkspace/contents.xcworkspacedata b/dynamic_theme/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/dynamic_theme/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/dynamic_theme/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/dynamic_theme/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/dynamic_theme/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/dynamic_theme/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/dynamic_theme/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/dynamic_theme/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/dynamic_theme/ios/Runner/AppDelegate.swift b/dynamic_theme/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/dynamic_theme/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/dynamic_theme/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/dynamic_theme/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/dynamic_theme/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/dynamic_theme/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/dynamic_theme/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/dynamic_theme/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/dynamic_theme/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/dynamic_theme/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/dynamic_theme/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/dynamic_theme/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/dynamic_theme/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/dynamic_theme/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/dynamic_theme/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/dynamic_theme/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/dynamic_theme/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/dynamic_theme/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/dynamic_theme/ios/Runner/Base.lproj/LaunchScreen.storyboard b/dynamic_theme/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/dynamic_theme/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dynamic_theme/ios/Runner/Base.lproj/Main.storyboard b/dynamic_theme/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/dynamic_theme/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dynamic_theme/ios/Runner/Info.plist b/dynamic_theme/ios/Runner/Info.plist new file mode 100644 index 0000000..5c12ce8 --- /dev/null +++ b/dynamic_theme/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Dynamic Theme + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + dynamic_theme + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/dynamic_theme/ios/Runner/Runner-Bridging-Header.h b/dynamic_theme/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/dynamic_theme/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/dynamic_theme/ios/RunnerTests/RunnerTests.swift b/dynamic_theme/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/dynamic_theme/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/dynamic_theme/lib/main.dart b/dynamic_theme/lib/main.dart new file mode 100644 index 0000000..2281b09 --- /dev/null +++ b/dynamic_theme/lib/main.dart @@ -0,0 +1,329 @@ +// Copyright 2024 Google LLC +// +// 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. + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:google_generative_ai/google_generative_ai.dart'; + +import 'widgets/api_key_widget.dart'; +import 'widgets/message_widget.dart'; +import 'widgets/text_field_decoration.dart'; + +final themeColor = ValueNotifier(Colors.orangeAccent); +final themeMode = ValueNotifier(ThemeMode.light); +final textScaleFactor = ValueNotifier(1); + +void main() { + runApp(const GenerativeAISample(title: "Dynamic Theme")); +} + +class GenerativeAISample extends StatefulWidget { + const GenerativeAISample({super.key, required this.title}); + final String title; + + @override + State createState() => _GenerativeAISampleState(); +} + +class _GenerativeAISampleState extends State { + String? apiKey; + + ThemeData theme(Brightness brightness) { + final colors = ColorScheme.fromSeed( + brightness: brightness, + seedColor: themeColor.value, + ); + return ThemeData( + brightness: brightness, + colorScheme: colors, + scaffoldBackgroundColor: colors.surface, + ); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: Listenable.merge([themeColor, themeMode, textScaleFactor]), + builder: (context, child) { + return MaterialApp( + debugShowCheckedModeBanner: false, + title: 'Flutter + GenAI', + theme: theme(Brightness.light), + darkTheme: theme(Brightness.dark), + themeMode: themeMode.value, + builder: (context, child) { + return MediaQuery( + data: MediaQuery.of( + context, + ).copyWith(textScaler: TextScaler.linear(textScaleFactor.value)), + child: child!, + ); + }, + home: switch (apiKey) { + final providedKey? => Example( + title: widget.title, + apiKey: providedKey, + ), + _ => ApiKeyWidget( + title: widget.title, + onSubmitted: (key) { + setState(() => apiKey = key); + }, + ), + }, + ); + }, + ); + } +} + +class Example extends StatefulWidget { + const Example({super.key, required this.apiKey, required this.title}); + + final String apiKey, title; + + @override + State createState() => _ExampleState(); +} + +class _ExampleState extends State { + final loading = ValueNotifier(false); + final menu = ValueNotifier(''); + final messages = ValueNotifier>([]); + final controller = TextEditingController(); + late final _history = []; + + late final model = GenerativeModel( + model: 'gemini-pro', + apiKey: widget.apiKey, + requestOptions: const RequestOptions(apiVersion: 'v1beta'), + tools: [ + Tool( + functionDeclarations: [ + FunctionDeclaration( + 'change_theme_color', + 'Change the current theme color', + Schema( + SchemaType.object, + properties: { + 'hex': Schema( + SchemaType.string, + description: 'Must be 6 in length. FF00EE,000000,FFFFFF', + ), + }, + ), + ), + FunctionDeclaration( + 'change_theme_mode', + 'Change the current theme mode', + Schema( + SchemaType.object, + properties: { + 'mode': Schema( + SchemaType.string, + description: + 'Must be one of the following: light,dark,system', + ), + }, + ), + ), + FunctionDeclaration( + 'change_text_scale_factor', + 'Change the current font scale, where 1 represents 14px and 2.0 = 48px', + Schema( + SchemaType.object, + properties: { + 'scale': Schema( + SchemaType.number, + description: 'Valid font scale, defaults to 1.0', + ), + }, + ), + ), + ], + ), + ], + ); + + Future sendMessage() async { + final message = controller.text.trim(); + if (message.isEmpty) return; + controller.clear(); + addMessage(Sender.user, message); + loading.value = true; + try { + final prompt = StringBuffer(); + prompt.writeln(message); + final response = await callWithActions([ + Content.text(prompt.toString()), + ]); + if (response.text != null) { + addMessage(Sender.system, response.text!); + } else { + addMessage(Sender.system, 'Something went wrong, please try again.'); + } + } catch (e) { + addMessage(Sender.system, 'Error sending message: $e'); + } finally { + loading.value = false; + } + } + + Future callWithActions( + Iterable prompt, + ) async { + final response = await model.generateContent(_history.followedBy(prompt)); + if (response.candidates.isNotEmpty) { + _history.addAll(prompt); + _history.add(response.candidates.first.content); + } + final actions = []; + for (final fn in response.functionCalls) { + final args = fn.args; + switch (fn.name) { + case 'change_theme_color': + final hex = args['hex'] as String; + if (hex.length != 6) { + actions.add( + FunctionResponse(fn.name, { + 'type': 'Error', + 'message': 'hex must be exactly 6 characters', + }), + ); + } else { + themeColor.value = Color(int.parse('0xFF$hex')); + actions.add( + FunctionResponse(fn.name, { + 'type': 'Success', + 'message': 'theme color updated', + }), + ); + } + case 'change_theme_mode': + final mode = args['mode'] as String; + themeMode.value = switch (mode) { + 'system' => ThemeMode.system, + 'light' => ThemeMode.light, + 'dark' => ThemeMode.dark, + (_) => ThemeMode.system, + }; + actions.add( + FunctionResponse(fn.name, { + 'type': 'Success', + 'message': 'theme mode updated', + }), + ); + case 'change_text_scale_factor': + final value = args['scale'] as num; + textScaleFactor.value = value.toDouble(); + actions.add( + FunctionResponse(fn.name, { + 'type': 'Success', + 'message': 'font scale updated', + }), + ); + default: + } + } + if (actions.isNotEmpty) { + return await callWithActions([ + ...prompt, + if (response.functionCalls.isNotEmpty) + Content.model(response.functionCalls), + for (final res in actions) + Content.functionResponse(res.name, res.response), + ]); + } + return response; + } + + void addMessage(Sender sender, String value, {bool clear = false}) { + if (clear) { + _history.clear(); + messages.value = []; + } + messages.value = messages.value.toList()..add((sender, value)); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: messages, + builder: (context, child) { + final reversed = messages.value.reversed; + return Scaffold( + appBar: AppBar(title: Text(widget.title)), + body: messages.value.isEmpty + ? const Center( + child: Padding( + padding: EdgeInsets.all(32.0), + child: Text( + 'Start changing the theme! Try typing ' + 'in requests like "Make the colors darker" or "Make the ' + 'font larger" and see what happens.', + ), + ), + ) + : ListView.builder( + padding: const EdgeInsets.all(8), + reverse: true, + itemCount: reversed.length, + itemBuilder: (context, index) { + final (sender, message) = reversed.elementAt(index); + return MessageWidget( + isFromUser: sender == Sender.user, + text: message, + ); + }, + ), + bottomNavigationBar: BottomAppBar( + padding: const EdgeInsets.all(8), + child: Row( + children: [ + Expanded( + child: TextField( + controller: controller, + decoration: textFieldDecoration( + context, + 'Change the theme color, font scale factor or brightness', + ), + onEditingComplete: sendMessage, + onSubmitted: (value) => sendMessage(), + ), + ), + const SizedBox(width: 8), + AnimatedBuilder( + animation: loading, + builder: (context, _) { + if (loading.value) { + return const CircularProgressIndicator(); + } + return IconButton( + onPressed: sendMessage, + icon: const Icon(Icons.send), + tooltip: 'Send a message', + ); + }, + ), + ], + ), + ), + ); + }, + ); + } +} + +enum Sender { user, system } diff --git a/dynamic_theme/lib/widgets/api_key_widget.dart b/dynamic_theme/lib/widgets/api_key_widget.dart new file mode 100644 index 0000000..b5d61c7 --- /dev/null +++ b/dynamic_theme/lib/widgets/api_key_widget.dart @@ -0,0 +1,82 @@ +// Copyright 2024 Google LLC +// +// 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. + +import 'package:flutter/material.dart'; +import 'package:url_launcher/link.dart'; + +import 'text_field_decoration.dart'; + +class ApiKeyWidget extends StatelessWidget { + ApiKeyWidget({super.key, required this.onSubmitted, required this.title}); + + final String title; + final ValueChanged onSubmitted; + final _textController = TextEditingController(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(title)), + body: Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'To use the Gemini API, you\'ll need an API key. ' + 'If you don\'t already have one, ' + 'create a key in Google AI Studio.', + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Link( + uri: Uri.https('aistudio.google.com', '/app/apikey'), + target: LinkTarget.blank, + builder: (context, followLink) => TextButton( + onPressed: followLink, + child: const Text('Get an API Key'), + ), + ), + ], + ), + ), + ), + bottomNavigationBar: BottomAppBar( + padding: const EdgeInsets.all(8), + child: Row( + children: [ + Expanded( + child: TextField( + decoration: textFieldDecoration(context, 'Enter your API key'), + controller: _textController, + obscureText: true, + onSubmitted: (value) { + onSubmitted(value); + }, + ), + ), + const SizedBox(height: 8), + TextButton( + onPressed: () { + onSubmitted(_textController.value.text); + }, + child: const Text('Submit'), + ), + ], + ), + ), + ); + } +} diff --git a/dynamic_theme/lib/widgets/message_widget.dart b/dynamic_theme/lib/widgets/message_widget.dart new file mode 100644 index 0000000..59603ca --- /dev/null +++ b/dynamic_theme/lib/widgets/message_widget.dart @@ -0,0 +1,59 @@ +// Copyright 2024 Google LLC +// +// 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. + +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; + +class MessageWidget extends StatelessWidget { + const MessageWidget({ + super.key, + this.text, + this.image, + required this.isFromUser, + }); + + final Image? image; + final String? text; + final bool isFromUser; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: isFromUser + ? MainAxisAlignment.end + : MainAxisAlignment.start, + children: [ + Flexible( + child: Container( + constraints: const BoxConstraints(maxWidth: 520), + decoration: BoxDecoration( + color: isFromUser + ? Theme.of(context).colorScheme.primaryContainer + : Theme.of(context).colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.circular(18), + ), + padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 20), + margin: const EdgeInsets.only(bottom: 8), + child: Column( + children: [ + if (text case final text?) MarkdownBody(data: text), + ?image, + ], + ), + ), + ), + ], + ); + } +} diff --git a/dynamic_theme/lib/widgets/text_field_decoration.dart b/dynamic_theme/lib/widgets/text_field_decoration.dart new file mode 100644 index 0000000..04cfce2 --- /dev/null +++ b/dynamic_theme/lib/widgets/text_field_decoration.dart @@ -0,0 +1,30 @@ +// Copyright 2024 Google LLC +// +// 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. + +import 'package:flutter/material.dart'; + +InputDecoration textFieldDecoration(BuildContext context, String hintText) { + return InputDecoration( + contentPadding: const EdgeInsets.all(15), + hintText: hintText, + border: OutlineInputBorder( + borderRadius: const BorderRadius.all(Radius.circular(14)), + borderSide: BorderSide(color: Theme.of(context).colorScheme.secondary), + ), + focusedBorder: OutlineInputBorder( + borderRadius: const BorderRadius.all(Radius.circular(14)), + borderSide: BorderSide(color: Theme.of(context).colorScheme.secondary), + ), + ); +} diff --git a/dynamic_theme/linux/.gitignore b/dynamic_theme/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/dynamic_theme/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/dynamic_theme/linux/CMakeLists.txt b/dynamic_theme/linux/CMakeLists.txt new file mode 100644 index 0000000..9513c1f --- /dev/null +++ b/dynamic_theme/linux/CMakeLists.txt @@ -0,0 +1,145 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "dynamic_theme") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.dynamic_theme") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/dynamic_theme/linux/flutter/CMakeLists.txt b/dynamic_theme/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/dynamic_theme/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/dynamic_theme/linux/flutter/generated_plugin_registrant.cc b/dynamic_theme/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..f6f23bf --- /dev/null +++ b/dynamic_theme/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/dynamic_theme/linux/flutter/generated_plugin_registrant.h b/dynamic_theme/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/dynamic_theme/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/dynamic_theme/linux/flutter/generated_plugins.cmake b/dynamic_theme/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..f16b4c3 --- /dev/null +++ b/dynamic_theme/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/dynamic_theme/linux/main.cc b/dynamic_theme/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/dynamic_theme/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/dynamic_theme/linux/my_application.cc b/dynamic_theme/linux/my_application.cc new file mode 100644 index 0000000..42581a3 --- /dev/null +++ b/dynamic_theme/linux/my_application.cc @@ -0,0 +1,124 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "dynamic_theme"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "dynamic_theme"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/dynamic_theme/linux/my_application.h b/dynamic_theme/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/dynamic_theme/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/dynamic_theme/macos/.gitignore b/dynamic_theme/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/dynamic_theme/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/dynamic_theme/macos/Flutter/Flutter-Debug.xcconfig b/dynamic_theme/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/dynamic_theme/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/dynamic_theme/macos/Flutter/Flutter-Release.xcconfig b/dynamic_theme/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/dynamic_theme/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/dynamic_theme/macos/Flutter/GeneratedPluginRegistrant.swift b/dynamic_theme/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..8236f57 --- /dev/null +++ b/dynamic_theme/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import url_launcher_macos + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) +} diff --git a/dynamic_theme/macos/Podfile b/dynamic_theme/macos/Podfile new file mode 100644 index 0000000..c795730 --- /dev/null +++ b/dynamic_theme/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/dynamic_theme/macos/Runner.xcodeproj/project.pbxproj b/dynamic_theme/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..b721a4d --- /dev/null +++ b/dynamic_theme/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,801 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 287400A9EC44776F28F31C8B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F611F1DAB08D867D504D2407 /* Pods_Runner.framework */; }; + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 38A802307A0201EDBB2C58C9 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4170BAFD4359164F659F06F1 /* Pods_RunnerTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 074D69953D38FB6B6E70DC7F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* dynamic_theme.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = dynamic_theme.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 349503D317120280E2182111 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 39A5D88C395E17065C59851E /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 4170BAFD4359164F659F06F1 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 55939BEA737967F00783DD37 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 953CC1AD9E1D46640495B652 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + BE61068ACA732E778B5B2ABD /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + F611F1DAB08D867D504D2407 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 38A802307A0201EDBB2C58C9 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 287400A9EC44776F28F31C8B /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 8B2D8D0F2F909F62010B5D76 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* dynamic_theme.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + 8B2D8D0F2F909F62010B5D76 /* Pods */ = { + isa = PBXGroup; + children = ( + 55939BEA737967F00783DD37 /* Pods-Runner.debug.xcconfig */, + 074D69953D38FB6B6E70DC7F /* Pods-Runner.release.xcconfig */, + 349503D317120280E2182111 /* Pods-Runner.profile.xcconfig */, + 39A5D88C395E17065C59851E /* Pods-RunnerTests.debug.xcconfig */, + 953CC1AD9E1D46640495B652 /* Pods-RunnerTests.release.xcconfig */, + BE61068ACA732E778B5B2ABD /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + F611F1DAB08D867D504D2407 /* Pods_Runner.framework */, + 4170BAFD4359164F659F06F1 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 607A24ADA552BBD79106FAC7 /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 1B535C1F2FFC6320A18ED712 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + 097842B7F7E558D94D9B134A /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* dynamic_theme.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 097842B7F7E558D94D9B134A /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 1B535C1F2FFC6320A18ED712 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 607A24ADA552BBD79106FAC7 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 39A5D88C395E17065C59851E /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.dynamicTheme.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/dynamic_theme.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/dynamic_theme"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 953CC1AD9E1D46640495B652 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.dynamicTheme.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/dynamic_theme.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/dynamic_theme"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BE61068ACA732E778B5B2ABD /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.dynamicTheme.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/dynamic_theme.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/dynamic_theme"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/dynamic_theme/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/dynamic_theme/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/dynamic_theme/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/dynamic_theme/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/dynamic_theme/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..a8d7ef3 --- /dev/null +++ b/dynamic_theme/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dynamic_theme/macos/Runner.xcworkspace/contents.xcworkspacedata b/dynamic_theme/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/dynamic_theme/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/dynamic_theme/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/dynamic_theme/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/dynamic_theme/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/dynamic_theme/macos/Runner/AppDelegate.swift b/dynamic_theme/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..d53ef64 --- /dev/null +++ b/dynamic_theme/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/dynamic_theme/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/dynamic_theme/macos/Runner/Base.lproj/MainMenu.xib b/dynamic_theme/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/dynamic_theme/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dynamic_theme/macos/Runner/Configs/AppInfo.xcconfig b/dynamic_theme/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..c2d79fa --- /dev/null +++ b/dynamic_theme/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = dynamic_theme + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.dynamicTheme + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright Β© 2024 com.example. All rights reserved. diff --git a/dynamic_theme/macos/Runner/Configs/Debug.xcconfig b/dynamic_theme/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/dynamic_theme/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/dynamic_theme/macos/Runner/Configs/Release.xcconfig b/dynamic_theme/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/dynamic_theme/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/dynamic_theme/macos/Runner/Configs/Warnings.xcconfig b/dynamic_theme/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/dynamic_theme/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/dynamic_theme/macos/Runner/DebugProfile.entitlements b/dynamic_theme/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..08c3ab1 --- /dev/null +++ b/dynamic_theme/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + com.apple.security.network.client + + + diff --git a/dynamic_theme/macos/Runner/Info.plist b/dynamic_theme/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/dynamic_theme/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/dynamic_theme/macos/Runner/MainFlutterWindow.swift b/dynamic_theme/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/dynamic_theme/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/dynamic_theme/macos/Runner/Release.entitlements b/dynamic_theme/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/dynamic_theme/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/dynamic_theme/macos/RunnerTests/RunnerTests.swift b/dynamic_theme/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..5418c9f --- /dev/null +++ b/dynamic_theme/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import FlutterMacOS +import Cocoa +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/dynamic_theme/pubspec.yaml b/dynamic_theme/pubspec.yaml new file mode 100644 index 0000000..97d4a95 --- /dev/null +++ b/dynamic_theme/pubspec.yaml @@ -0,0 +1,25 @@ +name: dynamic_theme +description: "Sample app for the google_generative_ai package" +publish_to: 'none' +version: 1.0.0+1 +resolution: workspace +environment: + sdk: ^3.9.0-0 + +dependencies: + flutter: + sdk: flutter + flutter_markdown: ^0.7.3 + google_generative_ai: ^0.4.0 + url_launcher: ^6.2.6 + +dev_dependencies: + analysis_defaults: + path: ../analysis_defaults + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 + +flutter: + uses-material-design: true + diff --git a/dynamic_theme/web/favicon.png b/dynamic_theme/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/dynamic_theme/web/favicon.png differ diff --git a/dynamic_theme/web/icons/Icon-192.png b/dynamic_theme/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/dynamic_theme/web/icons/Icon-192.png differ diff --git a/dynamic_theme/web/icons/Icon-512.png b/dynamic_theme/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/dynamic_theme/web/icons/Icon-512.png differ diff --git a/dynamic_theme/web/icons/Icon-maskable-192.png b/dynamic_theme/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/dynamic_theme/web/icons/Icon-maskable-192.png differ diff --git a/dynamic_theme/web/icons/Icon-maskable-512.png b/dynamic_theme/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/dynamic_theme/web/icons/Icon-maskable-512.png differ diff --git a/dynamic_theme/web/index.html b/dynamic_theme/web/index.html new file mode 100644 index 0000000..1c69283 --- /dev/null +++ b/dynamic_theme/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + dynamic_theme + + + + + + diff --git a/dynamic_theme/web/manifest.json b/dynamic_theme/web/manifest.json new file mode 100644 index 0000000..712cd4d --- /dev/null +++ b/dynamic_theme/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "dynamic_theme", + "short_name": "dynamic_theme", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/dynamic_theme/windows/.gitignore b/dynamic_theme/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/dynamic_theme/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/dynamic_theme/windows/CMakeLists.txt b/dynamic_theme/windows/CMakeLists.txt new file mode 100644 index 0000000..640f58e --- /dev/null +++ b/dynamic_theme/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(dynamic_theme LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "dynamic_theme") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/dynamic_theme/windows/flutter/CMakeLists.txt b/dynamic_theme/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/dynamic_theme/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/dynamic_theme/windows/flutter/generated_plugin_registrant.cc b/dynamic_theme/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..4f78848 --- /dev/null +++ b/dynamic_theme/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/dynamic_theme/windows/flutter/generated_plugin_registrant.h b/dynamic_theme/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/dynamic_theme/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/dynamic_theme/windows/flutter/generated_plugins.cmake b/dynamic_theme/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..88b22e5 --- /dev/null +++ b/dynamic_theme/windows/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/dynamic_theme/windows/runner/CMakeLists.txt b/dynamic_theme/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/dynamic_theme/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/dynamic_theme/windows/runner/Runner.rc b/dynamic_theme/windows/runner/Runner.rc new file mode 100644 index 0000000..b71f4df --- /dev/null +++ b/dynamic_theme/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "dynamic_theme" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "dynamic_theme" "\0" + VALUE "LegalCopyright", "Copyright (C) 2024 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "dynamic_theme.exe" "\0" + VALUE "ProductName", "dynamic_theme" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/dynamic_theme/windows/runner/flutter_window.cpp b/dynamic_theme/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/dynamic_theme/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/dynamic_theme/windows/runner/flutter_window.h b/dynamic_theme/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/dynamic_theme/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/dynamic_theme/windows/runner/main.cpp b/dynamic_theme/windows/runner/main.cpp new file mode 100644 index 0000000..99ca144 --- /dev/null +++ b/dynamic_theme/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"dynamic_theme", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/dynamic_theme/windows/runner/resource.h b/dynamic_theme/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/dynamic_theme/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/dynamic_theme/windows/runner/resources/app_icon.ico b/dynamic_theme/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/dynamic_theme/windows/runner/resources/app_icon.ico differ diff --git a/dynamic_theme/windows/runner/runner.exe.manifest b/dynamic_theme/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..a42ea76 --- /dev/null +++ b/dynamic_theme/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/dynamic_theme/windows/runner/utils.cpp b/dynamic_theme/windows/runner/utils.cpp new file mode 100644 index 0000000..b2b0873 --- /dev/null +++ b/dynamic_theme/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/dynamic_theme/windows/runner/utils.h b/dynamic_theme/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/dynamic_theme/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/dynamic_theme/windows/runner/win32_window.cpp b/dynamic_theme/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/dynamic_theme/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/dynamic_theme/windows/runner/win32_window.h b/dynamic_theme/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/dynamic_theme/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/form_app/.gitignore b/form_app/.gitignore new file mode 100644 index 0000000..955a348 --- /dev/null +++ b/form_app/.gitignore @@ -0,0 +1,41 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Exceptions to above rules. +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/form_app/.metadata b/form_app/.metadata new file mode 100644 index 0000000..d22992e --- /dev/null +++ b/form_app/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "db7ef5bf9f59442b0e200a90587e8fa5e0c6336a" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: android + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: ios + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: linux + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: macos + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: web + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: windows + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/form_app/README.md b/form_app/README.md new file mode 100644 index 0000000..c63a49c --- /dev/null +++ b/form_app/README.md @@ -0,0 +1,33 @@ +# form_app + +A sample demonstrating different types of forms and best practices. + +## Sign In with HTTP +[*lib/src/sign_in_http.dart*](lib/src/sign_in_http.dart) + +A sign in form using `package:http` to send a request. + +## Form widgets +[*lib/src/form_widgets.dart*](lib/src/form_widgets.dart) + +A stylized form that uses widgets like TextField, DatePicker, Slider, Checkbox, +and Switch. + +## Autofill +[*lib/src/autofill.dart*](lib/src/autofill.dart) + +A form that uses AutofillGroup to auto-fill the users name, email, and address. + +In order to use Autofill in a browser, your app needs to be hosted with HTTPS. +If you would like to test locally, you can build the app in release mode +(`flutter run -d chrome --release --web-port=5000`). + +Then use [tunnelmole](https://tunnelmole.com/docs), an open source tunneling +tool or [ngrok](https://ngrok.com/), a popular closed source tunneling tool +to create an HTTPS url for your local app (`tmole 5000` or `ngrok http 5000`). + +## Validation +[*lib/src/validation.dart*](lib/src/validation.dart) + +A form that alerts the user if the data entered is invalid. + diff --git a/form_app/analysis_options.yaml b/form_app/analysis_options.yaml new file mode 100644 index 0000000..5e2133e --- /dev/null +++ b/form_app/analysis_options.yaml @@ -0,0 +1 @@ +include: ../analysis_options.yaml diff --git a/form_app/android/.gitignore b/form_app/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/form_app/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/form_app/android/app/build.gradle b/form_app/android/app/build.gradle new file mode 100644 index 0000000..c36c9f6 --- /dev/null +++ b/form_app/android/app/build.gradle @@ -0,0 +1,67 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +android { + namespace "dev.flutter.formApp.form_app" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "dev.flutter.formApp.form_app" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies {} diff --git a/form_app/android/app/src/debug/AndroidManifest.xml b/form_app/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/form_app/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/form_app/android/app/src/main/AndroidManifest.xml b/form_app/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..3e8d2c2 --- /dev/null +++ b/form_app/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/form_app/android/app/src/main/kotlin/dev/flutter/formApp/form_app/MainActivity.kt b/form_app/android/app/src/main/kotlin/dev/flutter/formApp/form_app/MainActivity.kt new file mode 100644 index 0000000..ea7d4f2 --- /dev/null +++ b/form_app/android/app/src/main/kotlin/dev/flutter/formApp/form_app/MainActivity.kt @@ -0,0 +1,6 @@ +package dev.flutter.formApp.form_app + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/form_app/android/app/src/main/res/drawable-v21/launch_background.xml b/form_app/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/form_app/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/form_app/android/app/src/main/res/drawable/launch_background.xml b/form_app/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/form_app/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/form_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/form_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/form_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/form_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/form_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/form_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/form_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/form_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/form_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/form_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/form_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/form_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/form_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/form_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/form_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/form_app/android/app/src/main/res/values-night/styles.xml b/form_app/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/form_app/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/form_app/android/app/src/main/res/values/styles.xml b/form_app/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/form_app/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/form_app/android/app/src/profile/AndroidManifest.xml b/form_app/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/form_app/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/form_app/android/build.gradle b/form_app/android/build.gradle new file mode 100644 index 0000000..e83fb5d --- /dev/null +++ b/form_app/android/build.gradle @@ -0,0 +1,30 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/form_app/android/gradle.properties b/form_app/android/gradle.properties new file mode 100644 index 0000000..598d13f --- /dev/null +++ b/form_app/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G +android.useAndroidX=true +android.enableJetifier=true diff --git a/form_app/android/gradle/wrapper/gradle-wrapper.properties b/form_app/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/form_app/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/form_app/android/settings.gradle b/form_app/android/settings.gradle new file mode 100644 index 0000000..7cd7128 --- /dev/null +++ b/form_app/android/settings.gradle @@ -0,0 +1,29 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + + plugins { + id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false +} + +include ":app" diff --git a/form_app/codelab_rebuild.yaml b/form_app/codelab_rebuild.yaml new file mode 100644 index 0000000..314e673 --- /dev/null +++ b/form_app/codelab_rebuild.yaml @@ -0,0 +1,35 @@ +# Run with tooling from https://github.com/flutter/codelabs/tree/main/tooling/codelab_rebuild +name: Form App rebuild script +steps: + - name: Remove runners + rmdirs: + - android + - ios + - macos + - linux + - windows + - web + - name: Flutter recreate + flutter: create --platforms android,ios,windows,linux,macos,web --org dev.flutter.formApp . + - name: Drop widget_test.dart + rm: test/widget_test.dart + - name: Flutter upgrade + flutter: pub upgrade --major-versions + - name: Patch web/manifest.json + path: web/manifest.json + patch-u: | + --- b/form_app/web/manifest.json + +++ a/form_app/web/manifest.json + @@ -5,7 +5,7 @@ + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + - "description": "A new Flutter project.", + + "description": "A sample demonstrating different types of forms and best practices", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + - name: Flutter build macOS + flutter: build macos + - name: Flutter build ios + flutter: build ios --simulator diff --git a/form_app/ios/.gitignore b/form_app/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/form_app/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/form_app/ios/Flutter/AppFrameworkInfo.plist b/form_app/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/form_app/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/form_app/ios/Flutter/Debug.xcconfig b/form_app/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/form_app/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/form_app/ios/Flutter/Release.xcconfig b/form_app/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/form_app/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/form_app/ios/Podfile b/form_app/ios/Podfile new file mode 100644 index 0000000..fdcc671 --- /dev/null +++ b/form_app/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '11.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/form_app/ios/Runner.xcodeproj/project.pbxproj b/form_app/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..738a9ef --- /dev/null +++ b/form_app/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,704 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 1D7E430EC5589BBC8E504C02 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 690C4FF34F2154750B6483C3 /* Pods_RunnerTests.framework */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3729981BDD7FE2FDAD36827F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1E412F28943DE9C7046A969 /* Pods_Runner.framework */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 31BDD741C4DD9CB888C8AFA2 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3DF94DB31BD7F07D19F9D60A /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 5A3C527385087755DD450A3E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 644AF542CCFE06995AEB3189 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 690C4FF34F2154750B6483C3 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C1E412F28943DE9C7046A969 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DDC9F22649FE0D92D9C8EEA2 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + E683018EC80F2DB74021065F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 3C33551528C999AD483131E4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D7E430EC5589BBC8E504C02 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3729981BDD7FE2FDAD36827F /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 7FAF82F7C4667B5239468D3E /* Pods */ = { + isa = PBXGroup; + children = ( + 31BDD741C4DD9CB888C8AFA2 /* Pods-Runner.debug.xcconfig */, + E683018EC80F2DB74021065F /* Pods-Runner.release.xcconfig */, + 5A3C527385087755DD450A3E /* Pods-Runner.profile.xcconfig */, + DDC9F22649FE0D92D9C8EEA2 /* Pods-RunnerTests.debug.xcconfig */, + 644AF542CCFE06995AEB3189 /* Pods-RunnerTests.release.xcconfig */, + 3DF94DB31BD7F07D19F9D60A /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 7FAF82F7C4667B5239468D3E /* Pods */, + D7F6BACD89531C88B1CB3091 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + D7F6BACD89531C88B1CB3091 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C1E412F28943DE9C7046A969 /* Pods_Runner.framework */, + 690C4FF34F2154750B6483C3 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 57DF108057C891787117C0FD /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 3C33551528C999AD483131E4 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + C8CABB2B5E722F202A1F97E3 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 57DF108057C891787117C0FD /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + C8CABB2B5E722F202A1F97E3 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.formApp.formApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DDC9F22649FE0D92D9C8EEA2 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.formApp.formApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 644AF542CCFE06995AEB3189 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.formApp.formApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3DF94DB31BD7F07D19F9D60A /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.formApp.formApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.formApp.formApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.formApp.formApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/form_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/form_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/form_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/form_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/form_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/form_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/form_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/form_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/form_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/form_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/form_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..87131a0 --- /dev/null +++ b/form_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/form_app/ios/Runner.xcworkspace/contents.xcworkspacedata b/form_app/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/form_app/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/form_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/form_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/form_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/form_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/form_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/form_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/form_app/ios/Runner/AppDelegate.swift b/form_app/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/form_app/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/form_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/form_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/form_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/form_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/form_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/form_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/form_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/form_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/form_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/form_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/form_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/form_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/form_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/form_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/form_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/form_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/form_app/ios/Runner/Base.lproj/LaunchScreen.storyboard b/form_app/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/form_app/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/form_app/ios/Runner/Base.lproj/Main.storyboard b/form_app/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/form_app/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/form_app/ios/Runner/Info.plist b/form_app/ios/Runner/Info.plist new file mode 100644 index 0000000..e374d3b --- /dev/null +++ b/form_app/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Form App + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + form_app + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/form_app/ios/Runner/Runner-Bridging-Header.h b/form_app/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/form_app/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/form_app/ios/RunnerTests/RunnerTests.swift b/form_app/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/form_app/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/form_app/lib/main.dart b/form_app/lib/main.dart new file mode 100644 index 0000000..5b1ab62 --- /dev/null +++ b/form_app/lib/main.dart @@ -0,0 +1,112 @@ +// Copyright 2020, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import 'src/autofill.dart'; +import 'src/form_widgets.dart'; +import 'src/http/mock_client.dart'; +import 'src/sign_in_http.dart'; +import 'src/validation.dart'; + +void main() { + // Removed setupWindow() call + runApp(const FormApp()); +} + +final demos = [ + Demo( + name: 'Sign in with HTTP', + route: 'signin_http', + builder: (context) => SignInHttpDemo( + // This sample uses a mock HTTP client. + httpClient: mockClient, + ), + ), + Demo( + name: 'Autofill', + route: 'autofill', + builder: (context) => const AutofillDemo(), + ), + Demo( + name: 'Form widgets', + route: 'form_widgets', + builder: (context) => const FormWidgetsDemo(), + ), + Demo( + name: 'Validation', + route: 'validation', + builder: (context) => const FormValidationDemo(), + ), +]; + +final router = GoRouter( + routes: [ + GoRoute( + path: '/', + builder: (context, state) => const HomePage(), + routes: [ + for (final demo in demos) + GoRoute( + path: demo.route, + builder: (context, state) => demo.builder(context), + ), + ], + ), + ], +); + +class FormApp extends StatelessWidget { + const FormApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp.router( + title: 'Form Samples', + theme: ThemeData(colorSchemeSeed: Colors.teal), + routerConfig: router, + ); + } +} + +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Form Samples')), + body: ListView(children: [...demos.map((d) => DemoTile(demo: d))]), + ); + } +} + +class DemoTile extends StatelessWidget { + final Demo? demo; + + const DemoTile({this.demo, super.key}); + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text(demo!.name), + onTap: () { + context.go('/${demo!.route}'); + }, + ); + } +} + +class Demo { + final String name; + final String route; + final WidgetBuilder builder; + + const Demo({ + required this.name, + required this.route, + required this.builder, + }); +} diff --git a/form_app/lib/src/autofill.dart b/form_app/lib/src/autofill.dart new file mode 100644 index 0000000..7e2a9b0 --- /dev/null +++ b/form_app/lib/src/autofill.dart @@ -0,0 +1,113 @@ +// Copyright 2020, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter/material.dart'; + +// Demonstrates how to use autofill hints. The full list of hints is here: +// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/src/engine/text_editing/autofill_hint.dart +class AutofillDemo extends StatefulWidget { + const AutofillDemo({super.key}); + + @override + State createState() => _AutofillDemoState(); +} + +class _AutofillDemoState extends State { + final _formKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Autofill')), + body: Form( + key: _formKey, + child: Scrollbar( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: AutofillGroup( + child: Column( + children: [ + ...[ + const Text('This sample demonstrates autofill. '), + TextFormField( + autofocus: true, + textInputAction: TextInputAction.next, + decoration: const InputDecoration( + hintText: 'Jane', + labelText: 'First Name', + ), + autofillHints: const [AutofillHints.givenName], + ), + TextFormField( + textInputAction: TextInputAction.next, + decoration: const InputDecoration( + hintText: 'Doe', + labelText: 'Last Name', + ), + autofillHints: const [AutofillHints.familyName], + ), + const TextField( + keyboardType: TextInputType.emailAddress, + textInputAction: TextInputAction.next, + decoration: InputDecoration( + hintText: 'foo@example.com', + labelText: 'Email', + ), + autofillHints: [AutofillHints.email], + ), + const TextField( + keyboardType: TextInputType.phone, + textInputAction: TextInputAction.next, + decoration: InputDecoration( + hintText: '(123) 456-7890', + labelText: 'Telephone', + ), + autofillHints: [AutofillHints.telephoneNumber], + ), + const TextField( + keyboardType: TextInputType.streetAddress, + textInputAction: TextInputAction.next, + decoration: InputDecoration( + hintText: '123 4th Ave', + labelText: 'Street Address', + ), + autofillHints: [AutofillHints.streetAddressLine1], + ), + const TextField( + keyboardType: TextInputType.number, + textInputAction: TextInputAction.next, + decoration: InputDecoration( + hintText: '12345', + labelText: 'Postal Code', + ), + autofillHints: [AutofillHints.postalCode], + ), + const TextField( + textInputAction: TextInputAction.next, + decoration: InputDecoration( + hintText: 'United States', + labelText: 'Country', + ), + autofillHints: [AutofillHints.countryName], + ), + const TextField( + keyboardType: TextInputType.number, + decoration: InputDecoration( + hintText: '1', + labelText: 'Country Code', + ), + autofillHints: [AutofillHints.countryCode], + ), + ].expand( + (widget) => [widget, const SizedBox(height: 24)], + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/form_app/lib/src/form_widgets.dart b/form_app/lib/src/form_widgets.dart new file mode 100644 index 0000000..b527eb4 --- /dev/null +++ b/form_app/lib/src/form_widgets.dart @@ -0,0 +1,215 @@ +// Copyright 2020, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart' as intl; + +class FormWidgetsDemo extends StatefulWidget { + const FormWidgetsDemo({super.key}); + + @override + State createState() => _FormWidgetsDemoState(); +} + +class _FormWidgetsDemoState extends State { + final _formKey = GlobalKey(); + String title = ''; + String description = ''; + DateTime date = DateTime.now(); + double maxValue = 0; + bool? brushedTeeth = false; + bool enableFeature = false; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Form widgets')), + body: Form( + key: _formKey, + child: Scrollbar( + child: Align( + alignment: Alignment.topCenter, + child: Card( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 400), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ...[ + TextFormField( + decoration: const InputDecoration( + filled: true, + hintText: 'Enter a title...', + labelText: 'Title', + ), + onChanged: (value) { + setState(() { + title = value; + }); + }, + ), + TextFormField( + decoration: const InputDecoration( + border: OutlineInputBorder(), + filled: true, + hintText: 'Enter a description...', + labelText: 'Description', + ), + onChanged: (value) { + description = value; + }, + maxLines: 5, + ), + _FormDatePicker( + date: date, + onChanged: (value) { + setState(() { + date = value; + }); + }, + ), + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Estimated value', + style: Theme.of( + context, + ).textTheme.bodyLarge, + ), + ], + ), + Text( + intl.NumberFormat.currency( + symbol: "\$", + decimalDigits: 0, + ).format(maxValue), + style: Theme.of( + context, + ).textTheme.titleMedium, + ), + Slider( + min: 0, + max: 500, + divisions: 500, + value: maxValue, + onChanged: (value) { + setState(() { + maxValue = value; + }); + }, + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Checkbox( + value: brushedTeeth, + onChanged: (checked) { + setState(() { + brushedTeeth = checked; + }); + }, + ), + Text( + 'Brushed Teeth', + style: Theme.of( + context, + ).textTheme.titleMedium, + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Enable feature', + style: Theme.of(context).textTheme.bodyLarge, + ), + Switch( + value: enableFeature, + onChanged: (enabled) { + setState(() { + enableFeature = enabled; + }); + }, + ), + ], + ), + ].expand( + (widget) => [widget, const SizedBox(height: 24)], + ), + ], + ), + ), + ), + ), + ), + ), + ), + ); + } +} + +class _FormDatePicker extends StatefulWidget { + final DateTime date; + final ValueChanged onChanged; + + const _FormDatePicker({required this.date, required this.onChanged}); + + @override + State<_FormDatePicker> createState() => _FormDatePickerState(); +} + +class _FormDatePickerState extends State<_FormDatePicker> { + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text('Date', style: Theme.of(context).textTheme.bodyLarge), + Text( + intl.DateFormat.yMd().format(widget.date), + style: Theme.of(context).textTheme.titleMedium, + ), + ], + ), + TextButton( + child: const Text('Edit'), + onPressed: () async { + var newDate = await showDatePicker( + context: context, + initialDate: widget.date, + firstDate: DateTime(1900), + lastDate: DateTime(2100), + ); + + // Don't change the date if the date picker returns null. + if (newDate == null) { + return; + } + + widget.onChanged(newDate); + }, + ), + ], + ); + } +} diff --git a/form_app/lib/src/http/mock_client.dart b/form_app/lib/src/http/mock_client.dart new file mode 100644 index 0000000..0774b6c --- /dev/null +++ b/form_app/lib/src/http/mock_client.dart @@ -0,0 +1,20 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; + +// Set up a mock HTTP client. +final http.Client mockClient = MockClient(_mockHandler); + +Future _mockHandler(http.Request request) async { + var decodedJson = Map.from( + json.decode(request.body) as Map, + ); + + if (decodedJson['email'] == 'root' && + decodedJson['password'] == 'password') { + return http.Response('', 200); + } + + return http.Response('', 401); +} diff --git a/form_app/lib/src/sign_in_http.dart b/form_app/lib/src/sign_in_http.dart new file mode 100644 index 0000000..d014a18 --- /dev/null +++ b/form_app/lib/src/sign_in_http.dart @@ -0,0 +1,111 @@ +// Copyright 2020, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:json_annotation/json_annotation.dart'; + +part 'sign_in_http.g.dart'; + +@JsonSerializable() +class FormData { + String? email; + String? password; + + FormData({this.email, this.password}); + + factory FormData.fromJson(Map json) => + _$FormDataFromJson(json); + + Map toJson() => _$FormDataToJson(this); +} + +class SignInHttpDemo extends StatefulWidget { + final http.Client? httpClient; + + const SignInHttpDemo({this.httpClient, super.key}); + + @override + State createState() => _SignInHttpDemoState(); +} + +class _SignInHttpDemoState extends State { + FormData formData = FormData(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Sign in Form')), + body: Form( + child: Scrollbar( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + ...[ + TextFormField( + autofocus: true, + textInputAction: TextInputAction.next, + decoration: const InputDecoration( + filled: true, + hintText: 'Your email address', + labelText: 'Email', + ), + onChanged: (value) { + formData.email = value; + }, + ), + TextFormField( + decoration: const InputDecoration( + filled: true, + labelText: 'Password', + ), + obscureText: true, + onChanged: (value) { + formData.password = value; + }, + ), + TextButton( + child: const Text('Sign in'), + onPressed: () async { + // Use a JSON encoded string to send + var result = await widget.httpClient!.post( + Uri.parse('https://example.com/signin'), + body: json.encode(formData.toJson()), + headers: {'content-type': 'application/json'}, + ); + + _showDialog(switch (result.statusCode) { + 200 => 'Successfully signed in.', + 401 => 'Unable to sign in.', + _ => 'Something went wrong. Please try again.', + }); + }, + ), + ].expand((widget) => [widget, const SizedBox(height: 24)]), + ], + ), + ), + ), + ), + ); + } + + void _showDialog(String message) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(message), + actions: [ + TextButton( + child: const Text('OK'), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + ); + } +} diff --git a/form_app/lib/src/sign_in_http.g.dart b/form_app/lib/src/sign_in_http.g.dart new file mode 100644 index 0000000..6b70755 --- /dev/null +++ b/form_app/lib/src/sign_in_http.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'sign_in_http.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +FormData _$FormDataFromJson(Map json) { + return FormData( + email: json['email'] as String?, + password: json['password'] as String?, + ); +} + +Map _$FormDataToJson(FormData instance) => { + 'email': instance.email, + 'password': instance.password, +}; diff --git a/form_app/lib/src/validation.dart b/form_app/lib/src/validation.dart new file mode 100644 index 0000000..5a46cc4 --- /dev/null +++ b/form_app/lib/src/validation.dart @@ -0,0 +1,167 @@ +// Copyright 2020, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:english_words/english_words.dart' as english_words; +import 'package:flutter/material.dart'; + +class FormValidationDemo extends StatefulWidget { + const FormValidationDemo({super.key}); + + @override + State createState() => _FormValidationDemoState(); +} + +class _FormValidationDemoState extends State { + final _formKey = GlobalKey(); + String? adjective; + String? noun; + bool? agreedToTerms = false; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('πŸ“– Story Generator'), + actions: [ + Padding( + padding: const EdgeInsets.all(8), + child: TextButton( + child: const Text('Submit'), + onPressed: () { + // Validate the form by getting the FormState from the GlobalKey + // and calling validate() on it. + var valid = _formKey.currentState!.validate(); + if (!valid) { + return; + } + + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Your story'), + content: Text('The $adjective developer saw a $noun'), + actions: [ + TextButton( + child: const Text('Done'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ), + ); + }, + ), + ), + ], + ), + body: Form( + key: _formKey, + child: Scrollbar( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + // A text field that validates that the text is an adjective. + TextFormField( + autofocus: true, + textInputAction: TextInputAction.next, + validator: (value) { + if (value!.isEmpty) { + return 'Please enter an adjective.'; + } + if (english_words.adjectives.contains(value)) { + return null; + } + return 'Not a valid adjective.'; + }, + decoration: const InputDecoration( + filled: true, + hintText: 'e.g. quick, beautiful, interesting', + labelText: 'Enter an adjective', + ), + onChanged: (value) { + adjective = value; + }, + ), + const SizedBox(height: 24), + // A text field that validates that the text is a noun. + TextFormField( + validator: (value) { + if (value!.isEmpty) { + return 'Please enter a noun.'; + } + if (english_words.nouns.contains(value)) { + return null; + } + return 'Not a valid noun.'; + }, + decoration: const InputDecoration( + filled: true, + hintText: 'i.e. a person, place or thing', + labelText: 'Enter a noun', + ), + onChanged: (value) { + noun = value; + }, + ), + const SizedBox(height: 24), + // A custom form field that requires the user to check a + // checkbox. + FormField( + initialValue: false, + validator: (value) { + if (value == false) { + return 'You must agree to the terms of service.'; + } + return null; + }, + builder: (formFieldState) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Checkbox( + value: agreedToTerms, + onChanged: (value) { + // When the value of the checkbox changes, + // update the FormFieldState so the form is + // re-validated. + formFieldState.didChange(value); + setState(() { + agreedToTerms = value; + }); + }, + ), + Text( + 'I agree to the terms of service.', + style: Theme.of( + context, + ).textTheme.titleMedium, + ), + ], + ), + if (!formFieldState.isValid) + Text( + formFieldState.errorText ?? "", + style: Theme.of(context).textTheme.bodySmall! + .copyWith( + color: Theme.of( + context, + ).colorScheme.error, + ), + ), + ], + ); + }, + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/form_app/linux/.gitignore b/form_app/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/form_app/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/form_app/linux/CMakeLists.txt b/form_app/linux/CMakeLists.txt new file mode 100644 index 0000000..2213ae7 --- /dev/null +++ b/form_app/linux/CMakeLists.txt @@ -0,0 +1,145 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "form_app") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "dev.flutter.formApp.form_app") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/form_app/linux/flutter/CMakeLists.txt b/form_app/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/form_app/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/form_app/linux/flutter/generated_plugin_registrant.cc b/form_app/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..e71a16d --- /dev/null +++ b/form_app/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/form_app/linux/flutter/generated_plugin_registrant.h b/form_app/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/form_app/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/form_app/linux/flutter/generated_plugins.cmake b/form_app/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..2e1de87 --- /dev/null +++ b/form_app/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/form_app/linux/main.cc b/form_app/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/form_app/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/form_app/linux/my_application.cc b/form_app/linux/my_application.cc new file mode 100644 index 0000000..352644e --- /dev/null +++ b/form_app/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "form_app"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "form_app"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/form_app/linux/my_application.h b/form_app/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/form_app/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/form_app/macos/.gitignore b/form_app/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/form_app/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/form_app/macos/Flutter/Flutter-Debug.xcconfig b/form_app/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/form_app/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/form_app/macos/Flutter/Flutter-Release.xcconfig b/form_app/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/form_app/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/form_app/macos/Flutter/GeneratedPluginRegistrant.swift b/form_app/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..cccf817 --- /dev/null +++ b/form_app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,10 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { +} diff --git a/form_app/macos/Podfile b/form_app/macos/Podfile new file mode 100644 index 0000000..c795730 --- /dev/null +++ b/form_app/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/form_app/macos/Runner.xcodeproj/project.pbxproj b/form_app/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..4c262df --- /dev/null +++ b/form_app/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,791 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 419E55390D5C6859E46F4E60 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A147F44056269FB6522E97B3 /* Pods_RunnerTests.framework */; }; + AF547EEBA262EA64F2F76407 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B94D806FF0CA835FBE031F4 /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0DE93A2B94DED931F6C86F9D /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 15DA313F278DE4FA51BEA594 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 1C531C59A44050235D715E2A /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* form_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = form_app.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 43D62431B633483AC526F1D4 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 4B94D806FF0CA835FBE031F4 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 8B768768678AD0FB2D54FC9C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + A147F44056269FB6522E97B3 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D22867B9FCEAB182F16A1868 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 419E55390D5C6859E46F4E60 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AF547EEBA262EA64F2F76407 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 40D26E11A172C8577ACA854E /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* form_app.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + 40D26E11A172C8577ACA854E /* Pods */ = { + isa = PBXGroup; + children = ( + 8B768768678AD0FB2D54FC9C /* Pods-Runner.debug.xcconfig */, + 15DA313F278DE4FA51BEA594 /* Pods-Runner.release.xcconfig */, + 43D62431B633483AC526F1D4 /* Pods-Runner.profile.xcconfig */, + D22867B9FCEAB182F16A1868 /* Pods-RunnerTests.debug.xcconfig */, + 0DE93A2B94DED931F6C86F9D /* Pods-RunnerTests.release.xcconfig */, + 1C531C59A44050235D715E2A /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 4B94D806FF0CA835FBE031F4 /* Pods_Runner.framework */, + A147F44056269FB6522E97B3 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 1E216D742C7F4DA91B1E8F16 /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9AE5B69D2A34D8D4264605F5 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + 73BE9168560B8CE626B66A05 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* form_app.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 1E216D742C7F4DA91B1E8F16 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 73BE9168560B8CE626B66A05 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9AE5B69D2A34D8D4264605F5 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D22867B9FCEAB182F16A1868 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.formApp.formApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/form_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/form_app"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 0DE93A2B94DED931F6C86F9D /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.formApp.formApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/form_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/form_app"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1C531C59A44050235D715E2A /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.formApp.formApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/form_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/form_app"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/form_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/form_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/form_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/form_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/form_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..5f438de --- /dev/null +++ b/form_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/form_app/macos/Runner.xcworkspace/contents.xcworkspacedata b/form_app/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/form_app/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/form_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/form_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/form_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/form_app/macos/Runner/AppDelegate.swift b/form_app/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..d53ef64 --- /dev/null +++ b/form_app/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/form_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/form_app/macos/Runner/Base.lproj/MainMenu.xib b/form_app/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/form_app/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/form_app/macos/Runner/Configs/AppInfo.xcconfig b/form_app/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..c38e5ce --- /dev/null +++ b/form_app/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = form_app + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.formApp.formApp + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright Β© 2023 dev.flutter.formApp. All rights reserved. diff --git a/form_app/macos/Runner/Configs/Debug.xcconfig b/form_app/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/form_app/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/form_app/macos/Runner/Configs/Release.xcconfig b/form_app/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/form_app/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/form_app/macos/Runner/Configs/Warnings.xcconfig b/form_app/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/form_app/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/form_app/macos/Runner/DebugProfile.entitlements b/form_app/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/form_app/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/form_app/macos/Runner/Info.plist b/form_app/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/form_app/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/form_app/macos/Runner/MainFlutterWindow.swift b/form_app/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/form_app/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/form_app/macos/Runner/Release.entitlements b/form_app/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/form_app/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/form_app/macos/RunnerTests/RunnerTests.swift b/form_app/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..5418c9f --- /dev/null +++ b/form_app/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import FlutterMacOS +import Cocoa +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/form_app/pubspec.yaml b/form_app/pubspec.yaml new file mode 100644 index 0000000..11b6d02 --- /dev/null +++ b/form_app/pubspec.yaml @@ -0,0 +1,29 @@ +name: form_app +description: A sample demonstrating different types of forms and best practices +publish_to: "none" +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^1.0.0 + intl: ^0.20.0 + http: ^1.0.0 + json_annotation: any + english_words: ^4.0.0 + go_router: ^16.0.0 + +dev_dependencies: + analysis_defaults: + path: ../analysis_defaults + flutter_test: + sdk: flutter + json_serializable: ^6.2.0 + build_runner: ^2.1.8 + +flutter: + uses-material-design: true diff --git a/form_app/test/form_app_test.dart b/form_app/test/form_app_test.dart new file mode 100644 index 0000000..a674e66 --- /dev/null +++ b/form_app/test/form_app_test.dart @@ -0,0 +1,51 @@ +// Copyright 2021, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_app/src/http/mock_client.dart'; +import 'package:form_app/src/sign_in_http.dart'; + +void main() { + testWidgets('sign in', (tester) async { + await _signIn(tester, 'root', 'password'); + expect(find.text('Unable to sign in.'), findsNothing); + expect(find.text('Successfully signed in.'), findsOneWidget); + }); + + testWidgets('sign in with bad password', (tester) async { + await _signIn(tester, 'admin', 'pw'); + expect(find.byType(AlertDialog), findsOneWidget); + expect(find.text('Unable to sign in.'), findsOneWidget); + expect(find.text('Successfully signed in.'), findsNothing); + }); +} + +Future _signIn( + WidgetTester tester, + String email, + String password, +) async { + await tester.pumpWidget( + MaterialApp(home: SignInHttpDemo(httpClient: mockClient)), + ); + + var textFormField = find.byType(TextFormField); + expect(textFormField, findsNWidgets(2)); + + // Enter email / password + var emailField = textFormField.at(0); + var passwordField = textFormField.at(1); + await tester.enterText(emailField, email); + await tester.enterText(passwordField, password); + + // Sign in + var button = find.byType(TextButton); + expect(button, findsOneWidget); + await tester.tap(button); + + // Wait for dialog + await tester.pumpAndSettle(); + expect(find.byType(AlertDialog), findsOneWidget); +} diff --git a/form_app/web/favicon.png b/form_app/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/form_app/web/favicon.png differ diff --git a/form_app/web/icons/Icon-192.png b/form_app/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/form_app/web/icons/Icon-192.png differ diff --git a/form_app/web/icons/Icon-512.png b/form_app/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/form_app/web/icons/Icon-512.png differ diff --git a/form_app/web/icons/Icon-maskable-192.png b/form_app/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/form_app/web/icons/Icon-maskable-192.png differ diff --git a/form_app/web/icons/Icon-maskable-512.png b/form_app/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/form_app/web/icons/Icon-maskable-512.png differ diff --git a/form_app/web/index.html b/form_app/web/index.html new file mode 100644 index 0000000..5c00904 --- /dev/null +++ b/form_app/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + form_app + + + + + + diff --git a/form_app/web/manifest.json b/form_app/web/manifest.json new file mode 100644 index 0000000..8ffb5c6 --- /dev/null +++ b/form_app/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "form_app", + "short_name": "form_app", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A sample demonstrating different types of forms and best practices", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/form_app/windows/.gitignore b/form_app/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/form_app/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/form_app/windows/CMakeLists.txt b/form_app/windows/CMakeLists.txt new file mode 100644 index 0000000..74fe5d9 --- /dev/null +++ b/form_app/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(form_app LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "form_app") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/form_app/windows/flutter/CMakeLists.txt b/form_app/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/form_app/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/form_app/windows/flutter/generated_plugin_registrant.cc b/form_app/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..8b6d468 --- /dev/null +++ b/form_app/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/form_app/windows/flutter/generated_plugin_registrant.h b/form_app/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/form_app/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/form_app/windows/flutter/generated_plugins.cmake b/form_app/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..b93c4c3 --- /dev/null +++ b/form_app/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/form_app/windows/runner/CMakeLists.txt b/form_app/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/form_app/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/form_app/windows/runner/Runner.rc b/form_app/windows/runner/Runner.rc new file mode 100644 index 0000000..46685ba --- /dev/null +++ b/form_app/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "dev.flutter.formApp" "\0" + VALUE "FileDescription", "form_app" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "form_app" "\0" + VALUE "LegalCopyright", "Copyright (C) 2023 dev.flutter.formApp. All rights reserved." "\0" + VALUE "OriginalFilename", "form_app.exe" "\0" + VALUE "ProductName", "form_app" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/form_app/windows/runner/flutter_window.cpp b/form_app/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/form_app/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/form_app/windows/runner/flutter_window.h b/form_app/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/form_app/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/form_app/windows/runner/main.cpp b/form_app/windows/runner/main.cpp new file mode 100644 index 0000000..14ec2e2 --- /dev/null +++ b/form_app/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"form_app", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/form_app/windows/runner/resource.h b/form_app/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/form_app/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/form_app/windows/runner/resources/app_icon.ico b/form_app/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/form_app/windows/runner/resources/app_icon.ico differ diff --git a/form_app/windows/runner/runner.exe.manifest b/form_app/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..a42ea76 --- /dev/null +++ b/form_app/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/form_app/windows/runner/utils.cpp b/form_app/windows/runner/utils.cpp new file mode 100644 index 0000000..b2b0873 --- /dev/null +++ b/form_app/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/form_app/windows/runner/utils.h b/form_app/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/form_app/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/form_app/windows/runner/win32_window.cpp b/form_app/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/form_app/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/form_app/windows/runner/win32_window.h b/form_app/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/form_app/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/google_maps/.gitignore b/google_maps/.gitignore new file mode 100644 index 0000000..24476c5 --- /dev/null +++ b/google_maps/.gitignore @@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/google_maps/.metadata b/google_maps/.metadata new file mode 100644 index 0000000..bd1207f --- /dev/null +++ b/google_maps/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: f468f3366c26a5092eb964a230ce7892fda8f2f8 + channel: stable + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8 + base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8 + - platform: web + create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8 + base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/google_maps/README.md b/google_maps/README.md new file mode 100644 index 0000000..ae658c3 --- /dev/null +++ b/google_maps/README.md @@ -0,0 +1,9 @@ +# Google Maps Demo + +This sample Flutter app showcases the [Google Maps for Flutter plugin](https://developers.google.com/maps/flutter-plugin/overview). + +## Getting Started + +1. See the overview at https://developers.google.com/maps/flutter-plugin/overview. +1. Follow the setup guide at https://developers.google.com/maps/flutter-plugin/config to learn where to insert your API keys. This demo will not run without API keys added. +1. Use the sample code in this folder for the tutorial at https://developers.google.com/maps/flutter-plugin/map-with-marker. \ No newline at end of file diff --git a/google_maps/analysis_options.yaml b/google_maps/analysis_options.yaml new file mode 100644 index 0000000..1347e6d --- /dev/null +++ b/google_maps/analysis_options.yaml @@ -0,0 +1,12 @@ +include: ../analysis_options.yaml + +analyzer: + exclude: + - lib/src/*.g.dart + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** diff --git a/google_maps/android/.gitignore b/google_maps/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/google_maps/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/google_maps/android/app/build.gradle b/google_maps/android/app/build.gradle new file mode 100644 index 0000000..bfc1e6d --- /dev/null +++ b/google_maps/android/app/build.gradle @@ -0,0 +1,71 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.google_maps_in_flutter" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion 21 + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/google_maps/android/app/src/debug/AndroidManifest.xml b/google_maps/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..9ae2de2 --- /dev/null +++ b/google_maps/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/google_maps/android/app/src/main/AndroidManifest.xml b/google_maps/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2bc1132 --- /dev/null +++ b/google_maps/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + diff --git a/google_maps/android/app/src/main/kotlin/com/example/google_maps_in_flutter/MainActivity.kt b/google_maps/android/app/src/main/kotlin/com/example/google_maps_in_flutter/MainActivity.kt new file mode 100644 index 0000000..883dae8 --- /dev/null +++ b/google_maps/android/app/src/main/kotlin/com/example/google_maps_in_flutter/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.google_maps_in_flutter + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/google_maps/android/app/src/main/res/drawable-v21/launch_background.xml b/google_maps/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/google_maps/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/google_maps/android/app/src/main/res/drawable/launch_background.xml b/google_maps/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/google_maps/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/google_maps/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/google_maps/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/google_maps/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/google_maps/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/google_maps/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/google_maps/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/google_maps/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/google_maps/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/google_maps/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/google_maps/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/google_maps/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/google_maps/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/google_maps/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/google_maps/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/google_maps/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/google_maps/android/app/src/main/res/values-night/styles.xml b/google_maps/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/google_maps/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/google_maps/android/app/src/main/res/values/styles.xml b/google_maps/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/google_maps/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/google_maps/android/app/src/profile/AndroidManifest.xml b/google_maps/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..9ae2de2 --- /dev/null +++ b/google_maps/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/google_maps/android/build.gradle b/google_maps/android/build.gradle new file mode 100644 index 0000000..f7eb7f6 --- /dev/null +++ b/google_maps/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.3.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/google_maps/android/gradle.properties b/google_maps/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/google_maps/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/google_maps/android/gradle/wrapper/gradle-wrapper.properties b/google_maps/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/google_maps/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/google_maps/android/settings.gradle b/google_maps/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/google_maps/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/google_maps/assets/locations.json b/google_maps/assets/locations.json new file mode 100644 index 0000000..83aae64 --- /dev/null +++ b/google_maps/assets/locations.json @@ -0,0 +1,901 @@ +{ + "offices": [ + { + "address": "Aabogade 15\n8200 Aarhus\nDenmark", + "id": "aarhus", + "image": "https://lh3.googleusercontent.com/tpBMFN5os8K-qXIHiAX5SZEmN5fCzIGrj9FdJtbZPUkC91ookSoY520NYn7fK5yqmh1L1m3F2SJA58v6Qps3JusdrxoFSwk6Ajv2K88", + "lat": 56.172249, + "lng": 10.187372, + "name": "Aarhus", + "phone": "", + "region": "europe" + }, + { + "address": "Claude Debussylaan 34\n1082 MD, Amsterdam\nNetherlands", + "id": "amsterdam", + "image": "https://lh3.googleusercontent.com/gG1zKXcSmRyYWHwUn2Z0MITpdqwb52RAEp3uthG2J5Xl-4_Wz7_WmoM6T_TBg6Ut3L1eF-8XENO10sxVIFdQHilj8iRG29wROpSoug", + "lat": 52.337801, + "lng": 4.872066, + "name": "Amsterdam", + "phone": "", + "region": "europe" + }, + { + "address": "2300 Traverwood Dr.\nAnn Arbor, MI 48105\nUnited States", + "id": "ann-arbor", + "image": "https://lh3.googleusercontent.com/Iim0OVcAgg9vmXc5ADn9KvOQFplrMZ8hBTg2biiTtuWPy_r56cy4Byx1ROk6coGt7knQdmx_jO45VX1kiCJZ0QzEtS97AP_BYG4F2w", + "lat": 42.3063848, + "lng": -83.7140833, + "name": "Ann Arbor", + "phone": "+1 734-332-6500", + "region": "north-america" + }, + { + "address": "Fragkokklisias 7\nAthens 151 25\nGreece", + "id": "athens", + "image": "https://lh3.googleusercontent.com/XroZnqewSrO6KuvXM5hDHtjUJzUcRQLZYfCKs4jP44dKezRvNx58uxaqUKS4fQ2eXzG2TpJNJ1X2xtfBe7Prl5hSG_xjPEF1xLtFodM", + "lat": 38.03902, + "lng": 23.804595, + "name": "Athens", + "phone": "", + "region": "europe" + }, + { + "address": "10 10th Street NE\nAtlanta, GA 30309\nUnited States", + "id": "atlanta", + "image": "https://lh3.googleusercontent.com/py7Qvqqoec1MB0dMKnGWn7ju9wET_dIneTb24U-ri8XAsECJnOaBoNmvfa51PIaC0rlsyQvQXvAK8RdLqpkhpkRSzmhNKqb-tY2_", + "lat": 33.781827, + "lng": -84.387301, + "name": "Atlanta", + "phone": "+1 404-487-9000", + "region": "north-america" + }, + { + "address": "500 W 2nd St\nSuite 2900\nAustin, TX 78701\nUnited States", + "id": "austin", + "image": "https://lh3.googleusercontent.com/WFaJgWPdd7xPL7CQHizlqEzLDjT_GUAiWHIWUM0PiVSsv8q3Rjt9QgbyQazuQwYfN5qLORajv8eKSHlKwZo-M89T2Y12zFSxSIme08c", + "lat": 30.266035, + "lng": -97.749237, + "name": "Austin", + "phone": "+1 512-343-5283", + "region": "north-america" + }, + { + "address": "No. 3, RMZ Infinity \u2013 Tower E\nOld Madras Road\n4th and 5th Floors\nBangalore, 560 016, India", + "id": "bangalore", + "image": "https://lh3.googleusercontent.com/YDyQevoY-D0eZQ9sYHp8dQjpFF5JpLfLK-0OM-uJK1oNK3_LRnGJAM0uXi9qb9UigKnVIIXlIgidxhRlnaB_FPtUOqPzrsCSiFZyoQ", + "lat": 12.99332, + "lng": 77.660176, + "name": "Bangalore", + "phone": "+91-80-67218000", + "region": "asia-pacific" + }, + { + "address": "57 Park Ventures Ecoplex\n14th Floor, Wireless Road\nBangkok, 10330, Thailand", + "id": "bangkok", + "image": "https://lh3.googleusercontent.com/nh9uOUPj6iWjKZSHIrnkfGhIWGBb8thguRM5_ehCOkyF-qfwzYciDJFVRSvQ6QxlSA6eZUMkzgdW9zR0Gab2ZZPg8NlB7V_V3wB5", + "lat": 13.742866, + "lng": 100.547983, + "name": "Bangkok", + "phone": "", + "region": "asia-pacific" + }, + { + "address": "6th Floor, Tower B, Raycom InfoTech Park\nNo. 2 Kexueyuan South Road\nZhongguancun Beijing 100190", + "id": "beijing", + "image": "https://lh3.googleusercontent.com/v_tD3VvC8-dnhqSF9xhj5Hx7F_bb3-wieM19i-Ho2C3by6mt7-JpPc7KsYVHUZFqQl5ON3adVEV1N4OlzSvHLrr3sr4GtXErDbGC", + "lat": 39.9848878, + "lng": 116.3265708, + "name": "Beijing", + "phone": "+86-10-62503000", + "region": "asia-pacific" + }, + { + "address": "Boulevard Corporate Tower\nAv. dos Andradas, 3000 - Andares 14-17\nSanta Efig\u00eania\nBelo Horizonte\n30260-070, Brazil", + "id": "belo-horizonte", + "image": "https://lh3.googleusercontent.com/f7F8gTi9GSgAZR3lv24I1yb-D0wRlDy0lQTcxCB4yJGtSgxrWdKoB3dX3J8SMrjYLwOSXquO3LuGFUE82QUjzVK9buHGNIRUPGpqM3E", + "lat": -19.920225, + "lng": -43.920845, + "name": "Belo Horizonte", + "phone": "+55-31-2128-6800", + "region": "latin-america" + }, + { + "address": "Tucholskystra\u00dfe 2\n10117 Berlin\nGermany", + "id": "berlin", + "image": "https://lh3.googleusercontent.com/XcPyEMiSlLdZJq7nh3orGy3UqjtUHdhxXiwn52ZY47wfEChfZNDO78zDy9H0tBeegogZBZpIE0Q9mdVBGN4aQ0M5vfgz8ZWMEe43Mg", + "lat": 52.5231079, + "lng": 13.39203120000002, + "name": "Berlin", + "phone": "+49 30 303986300", + "region": "europe" + }, + { + "address": "Carrera 11A 94 - 45\nCentro Empresarial Oxo Centre\nBogota, Colombia", + "id": "bogota", + "image": "https://lh3.googleusercontent.com/_APoV1zAR0g5_pXVDlT2ovgNQfr3zvjOuj4HFHViiy2ahyjapJMXlYRE48qYMyFTWXJybbK4psz-fQ82QhMhO0keYJ27I8tNTHe_ww", + "lat": 4.678267, + "lng": -74.046901, + "name": "Bogota", + "phone": "+57 (1) 5939400", + "region": "latin-america" + }, + { + "address": "2600 Pearl Street\nBoulder, CO 80302\nUnited States", + "id": "boulder", + "image": "https://lh3.googleusercontent.com/lF9KBhOolmb958FFmMdLwFcedQAn1wEsVleBRrJQmyfhvD3u4lwCneR09ADJ9sG4tMBP5LDSLkn9bkbavzyqqql_0X7hj39dzl-n1w", + "lat": 40.021693, + "lng": -105.260139, + "name": "Boulder", + "phone": "+1 303-245-0086", + "region": "north-america" + }, + { + "address": "2930 Pearl St\nBoulder, CO 80301\nUnited States", + "id": "boulder-pearl", + "image": "https://lh3.googleusercontent.com/_JvBccdhLZSIxenZEubM2Qu8Eky6udmnwekH7BhjI1EUo8mCDXDHa0Z7mfNzvZtlmiXI6b6w8U-PY47oUQhPtvYazGS4mG8S61Rr", + "lat": 40.021948, + "lng": -105.253978, + "name": "Boulder \u2013 Pearl Place", + "phone": "+1 303-245-0086", + "region": "north-america" + }, + { + "address": "3333 Walnut St\nBoulder CO, 80301\nUnited States", + "id": "boulder-walnut", + "image": "https://lh3.googleusercontent.com/nGvIFmh9d2J68l-U7gYdQAqLZkLNNS_pqhNMtGopMujEpZEneMSH75NFr1WmXJC0GafDLyTVSpnqbuj5Tfjjjk889Zk23dIggqNN", + "lat": 40.01993, + "lng": -105.24936, + "name": "Boulder \u2013 Walnut", + "phone": "+1 303-245-0086", + "region": "north-america" + }, + { + "address": "Chaussee d'Etterbeek 180\n1040 Brussels\nBelgium", + "id": "brussels", + "image": "https://lh3.googleusercontent.com/Vdcj2ozYBIOyJLAhRyQic7xjw-OfQ_F5b8M9Kaom_56M2zp8UW65Lm1bYJLLEc4_U4tXfAp-CA81U2O2tdHcXPdsCEUO0hyK_SFKF-Y", + "lat": 50.839315, + "lng": 4.380984, + "name": "Brussels", + "phone": "", + "region": "europe" + }, + { + "address": "Alicia M. De Justo 350, 2nd Floor\nBuenos Aires, C1107AAH\nArgentina", + "id": "buenos-aires", + "image": "https://lh3.googleusercontent.com/08n-ZBH23cWYWAbRo7_uZ6VObzDOLdfvxiAy4vZvX2I_FBn4vlUl_qiwALWBMUp7gQ4LEkj7aW6gB_jdJWNmnsmYEKbWzNsh0EaYpw", + "lat": -34.602734, + "lng": -58.366992, + "name": "Buenos Aires", + "phone": "+54-11-5530-3000", + "region": "latin-america" + }, + { + "address": "355 Main Street\nCambridge, MA 02142\nUnited States", + "id": "cambridge", + "image": "https://lh3.googleusercontent.com/OLL4nJ-esDQ3JFh2XpWSpX8WnO69yzFpYPWIy9yL_2WFapf74z_ZYvfqb4XkF0_hT2rCi3pzN2Y-yglQ-jWMw3u89YKwn4GfdT7FfQ", + "lat": 42.362757, + "lng": -71.087109, + "name": "Cambridge", + "phone": "+1 617-575-1300", + "region": "north-america" + }, + { + "address": "200 West Franklin Street\nChapel Hill, NC 27516\nUnited States", + "id": "chapel-hill", + "image": "https://lh3.googleusercontent.com/AHShjZrvscMoWixuAd0zIXqER2wKMXtoqX4edIzur3FRLJ3DBDIAQqD6PZqB4it_ApAVyitFkjsRPER38oX6XHYOl9mxKbLCXrAQKA", + "lat": 35.912445, + "lng": -79.058488, + "name": "Chapel Hill", + "phone": "", + "region": "north-america" + }, + { + "address": "210 Carpenter Ave\nChicago, IL 60607\nUnited States", + "id": "chicago-carpenter", + "image": "https://lh3.googleusercontent.com/pgZ_JGnbpqS4P8H29c6hOCQcLXiG1EZEw5W92FKddWuUTW8618AwIho27aAFPmniDUpH_jS3mCpAx3nY6WkT46oetsFMC__SrPCUmw", + "lat": 41.88609, + "lng": -87.65333, + "name": "Chicago \u2013 Carpenter", + "phone": "", + "region": "north-america" + }, + { + "address": "320 N. Morgan, Suite 600\nChicago, IL 60607\nUnited States", + "id": "chicago-fulton", + "image": "https://lh3.googleusercontent.com/ulGqMc02YGomqRC2EN0JP7jOL-6qaIvhCq225DwyeP7b8l-H7ZTWkYDwVKHc0Z4nXEq_TBRCqqPfcc3N8WHm54XpOh16Yx73F4ng", + "lat": 41.8873457, + "lng": -87.6526874, + "name": "Chicago \u2013 Fulton Market", + "phone": "+1 312-840-4100", + "region": "north-america" + }, + { + "address": "Skt. Petri Passage 5\n1165 Copenhagen\nDenmark", + "id": "copenhagen", + "image": "https://lh3.googleusercontent.com/SNSbrYGI_ZBuCl_S8aRh63IIta895tqIUUX3ZT0FWmK7ykhJRy_HNtzoud7XrohnjnSAkuXg9YykkFZqbvkRiZQC7osXrZzGerWdmG8", + "lat": 55.680452, + "lng": 12.570071, + "name": "Copenhagen", + "phone": "+45 3370 2600", + "region": "europe" + }, + { + "address": "52 Henry St.\n3rd Floor\nDetroit, MI 48201\nUnited States", + "id": "detroit", + "image": "https://lh3.googleusercontent.com/WEP2INGXZc9vRv1ii6KDZGoRFPyumV466B3RzUwyzf8W81a7du2KGXlDEqS5g0nbOHsYTAvagFGVJskSonpt6wJWN2mVq8ti7JYPtvs", + "lat": 42.340458, + "lng": -83.054494, + "name": "Detroit", + "phone": "+1 248-593-4003", + "region": "north-america" + }, + { + "address": "TECOM Zone, Dubai Internet City\nDubai, United Arab Emirates", + "id": "dubai", + "image": "https://lh3.googleusercontent.com/xw0iylnw3b3-qxwoNzLSLJlAAPtkF1KONnIoBTDHtURr04fzH9DeO08GYvEsKYQtE9GCdOMTk_s08H6-btSquKo3moeILfc3Kpu4MA", + "lat": 25.0929, + "lng": 55.1591, + "name": "Dubai", + "phone": "+971 4 4509500", + "region": "africa-middle-east" + }, + { + "address": "Gordon House\nBarrow St\nDublin 4\nIreland", + "id": "dublin", + "image": "https://lh3.googleusercontent.com/1z3Fhr6nKlCDeTwc1KoFAMSrnywR0lb8nNdwTI1YgoKSXKIDjQeVB_I3Q8oDZuqqHtlXiUbPmfoUYyAXMObjvMxDcMeTqSY21YvP_A", + "lat": 53.3399526, + "lng": -6.2360967, + "name": "Dublin", + "phone": "", + "region": "europe" + }, + { + "address": "Taikoo Hui Tower 1, No.383 Tianhe Road\nGuangzhou, 510620\nChina", + "id": "guangzhou", + "image": "https://lh3.googleusercontent.com/BjYQfVMor1QT5hAkc7DcN6_MJdwaySHY6VJ6IY7mQGJRdXjFZhiP-t4MV_QUbp0tBeuYvuMw3nUetTiI-vFl6-BcialJhhurhFrDVeY", + "lat": 23.1339728, + "lng": 113.3332488, + "name": "Guangzhou", + "phone": "", + "region": "asia-pacific" + }, + { + "address": "Sector 15, Part II Village Silokhera\nGurgaon 122001\nIndia", + "id": "gurgaon", + "image": "https://lh3.googleusercontent.com/8plKBiWKmwllCXePad0JJ22u1GG7Qe1hveXlx_xJ87XoiQpclQubwxyGxcDU6tkatpb3oi9MYXjm2XszFi5kGn1flfTtjv6MycBWrQ", + "lat": 28.460581, + "lng": 77.048194, + "name": "Gurgaon", + "phone": "+91-12-44512900", + "region": "asia-pacific" + }, + { + "address": "Building 30\nMATAM, Advanced Technology Centre\nHaifa, 3190500\nIsrael ", + "id": "haifa", + "image": "https://lh3.googleusercontent.com/syKfT9cVMzLi0d4_DSiJztWGwcmWct6IEbpAApEFk_G8ym0xyLLxMBT484zROIOZHMSe9N1o-QQrCAqVWfKRSY6EOeJY9Qa1ftwb", + "lat": 32.78897, + "lng": 34.958432, + "name": "Haifa", + "phone": "+972-74-746-6245", + "region": "africa-middle-east" + }, + { + "address": "ABC-Strasse 19\n20354 Hamburg\nGermany", + "id": "hamburg", + "image": "https://lh3.googleusercontent.com/66R0svr2--6zNOnrqf6JbeZ-bF39bRfHpExjcTlE_AIlPEPk8jO1LjF39FUbDnJB1gh_FiRFX6aCRg4ACjYRbDqb5lf9PdV6qY4S", + "lat": 53.553752, + "lng": 9.986229, + "name": "Hamburg", + "phone": "49 40-80-81-79-000", + "region": "europe" + }, + { + "address": "1 Matheson Street\nCauseway Bay, Hong Kong", + "id": "hong-kong", + "image": "https://lh3.googleusercontent.com/-Ult8_R6TfQAk16CfjSfl6PLypQBYohUjNjE6xeeektZsrP8XwTv7PnVVE-5Ueh3I-2hPnAdRGg6XrMn9IwI7W1h5LJKtlMVe93Wfw", + "lat": 22.278203, + "lng": 114.18176, + "name": "Hong Kong", + "phone": "+852-3923-5400", + "region": "asia-pacific" + }, + { + "address": "Survey No. 13, DivyaSree Omega\nKondapur Village\nHyderabad, Telangana 500084\nIndia", + "id": "hyderabad", + "image": "https://lh3.googleusercontent.com/LAEnc0tzA-JSb5XM5oct5paX98QK9zh_aqa_qKcjAoXo2MChgOjdj_EZpgIZsVAvEY-I0bmMmhCBb5gkoVN4ebqCG9ZfjCbo_stJaw", + "lat": 17.458461, + "lng": 78.372452, + "name": "Hyderabad", + "phone": "+91-40-6619-3000", + "region": "asia-pacific" + }, + { + "address": "19510 Jamboree Road\nIrvine, CA 92612\nUnited States", + "id": "irvine", + "image": "https://lh3.googleusercontent.com/LWGkhXkRRzWnMlNy_Ps74-VTxB2ISXK0Kkick1SujTLYvNAUqo9_HR7SILSZZsiaiGWsXtx7dR5Hz9Q5psu1MWP9BHtDuGYc_hz_eg", + "lat": 33.658792, + "lng": -117.859322, + "name": "Irvine", + "phone": "+1 949-794-1600", + "region": "north-america" + }, + { + "address": "Eski Buyukdere Caddesi No: 209\n34394\nIstanbul, Turkey", + "id": "istanbul", + "image": "https://lh3.googleusercontent.com/_mdN7z1Q-9fKgpHTb1rQJosllxqn7glRJ_G2enX4WPmImuJjLHKw-JBZ8z5B9vMSo12SexGBOD1i2NHXqEy4OaOJekn0g3Fp3bDk_Q", + "lat": 41.081697, + "lng": 29.00859, + "name": "Istanbul", + "phone": "", + "region": "africa-middle-east" + }, + { + "address": "Pacific Century Place Tower Level 45 SCBD Lot 10,\nJl. Jend. Sudirman No.53,\nRT.5/RW.3, Senayan, Kby. Baru,\nKota Jakarta Selatan,\nDaerah Khusus Ibukota Jakarta 12190, \nIndonesia", + "id": "jakarta", + "image": "https://lh3.googleusercontent.com/JEaMUfOUq6bxN7jeIN1z2me5-JvlLRkrFJgf_A0GvqtOquU6Tfjg0ecKeR_Ck27L0S1zC2t_4I6nVP6pBdBtSKst7tkJEoC7LyYq", + "lat": -6.227664, + "lng": 106.808471, + "name": "Jakarta", + "phone": "", + "region": "asia-pacific" + }, + { + "address": "35 Ballyclare Drive, Building E\nJohannesburg\n2191, South Africa", + "id": "johannesburg", + "image": "https://lh3.googleusercontent.com/EDxefwSgeKyh8zN9VUUGhu3hiBqH7Z3UEOXfZeij7YnUhZLqLElu8dhi4FziOepRun-fjfwIWdf5W8CTG5ZSYMu4k8z9QZjTgjQRuQ", + "lat": -26.0734457, + "lng": 28.032035, + "name": "Johannesburg", + "phone": "", + "region": "africa-middle-east" + }, + { + "address": "777 6th Street South\nKirkland, WA\nUnited States", + "id": "kirkland", + "image": "https://lh3.googleusercontent.com/Vgmu21GQbS0pga_tJaG0_35AYOzM64Uxp-zNYyMVnd3oXFHmHeMJpx8UjcsMYdnxbdlFZ4KGFowOtpHxsNlUw8qS21sYBy9jPbqkuA", + "lat": 47.669568, + "lng": -122.196912, + "name": "Kirkland", + "phone": "+1 425-739-5600", + "region": "north-america" + }, + { + "address": "51 Breithaupt Street\nKitchener, ON N2H 5G5\nCanada", + "id": "kitchener", + "image": "https://lh3.googleusercontent.com/jUCZzQYnJXCUZ3ZxAEB14qukCV6aGxfh84hExpcpye314DhOWB4jtpUoNDrCtA2laV7qDHBAYGtIuZan9Ir5Hp6_U0B2zTGgPqsb", + "lat": 43.4541137, + "lng": -80.4992423, + "name": "Kitchener", + "phone": "+1-519-880-2300", + "region": "north-america" + }, + { + "address": "Axiata Tower\nNo. 9, Jalan Stesen Sentral 5\n50470 Kuala Lumpur\nMalaysia", + "id": "kuala-lumpur", + "image": "https://lh3.googleusercontent.com/c5kAdRoyejY1Z5i9A3hYKfIG55GrKdAc0iJjH-gYo-tWd3JUksvsfZx7LU5yzay4HJmxCQEir2cejbZ2LurYfKL_emC9e9PCDVxd", + "lat": 3.133445, + "lng": 101.684609, + "name": "Kuala Lumpur", + "phone": "", + "region": "asia-pacific" + }, + { + "address": "Avenida da Liberdade, 110\nLisbon, 1269-046, Portugal", + "id": "lisbon", + "image": "https://lh3.googleusercontent.com/py3HZVLLpxjMhRL6fgUKmHqGODp6ZH-5abQBHGqyKrCyuoW0t-q0ypNVN_jOfD3ZEO08Y9Q0m-E4ZyuNrMgl-mlaECkCAEyc7Af1", + "lat": 38.718887, + "lng": -9.143781, + "name": "Lisbon", + "phone": "+351 21 122 1803", + "region": "europe" + }, + { + "address": "6 Pancras Square\nLondon N1C 4AG\nUnited Kingdom", + "id": "london-6ps", + "image": "https://lh3.googleusercontent.com/WTxWzt3AOcEMwoT2OonLTlc63pa4V-GsYbZg5Hu7rfe9ZioMaRurkxaQ5tOcuC9nZkCyh2IjQb-xMy4Tq8ISrHjfDHmzZXnExTjP", + "lat": 51.533311, + "lng": -0.126026, + "name": "London \u2013 6PS", + "phone": "+44-20-7031-3000", + "region": "europe" + }, + { + "address": "Belgrave House\n76 Buckingham Palace Road\nLondon SW1W 9TQ\nUnited Kingdom", + "id": "london-bel", + "image": "https://lh3.googleusercontent.com/bLxZNCaDE2Fdj7woV_9JUJEUfUvTrhI57jHNEeW-OenTspzM21miwz1gGydzZ2Ke_vfRdkqdo4dyN2mJCntC2p4qvRUyipPWppAC9g", + "lat": 51.494961, + "lng": -0.146652, + "name": "London \u2013 BEL", + "phone": "+44-20-7031-3001", + "region": "europe" + }, + { + "address": "1\u201313 St Giles High St\nLondon WC2H 8AG\nUnited Kingdom", + "id": "london-csg", + "image": "https://lh3.googleusercontent.com/32nlExbSrV5rJR9Qsqfkbckn6_yd-4QRaoSDmp9JLyaZxojfl9aH1LZSrSvcsT128AUzHqkEfMhTE2miDuOu7gj-7x3Ginqr4rgowg", + "lat": 51.516027, + "lng": -0.12755, + "name": "London \u2013 CSG", + "phone": "+44 (0)20-7031-3000", + "region": "europe" + }, + { + "address": "340 Main Street\nLos Angeles, CA 90291\nUnited States", + "id": "los-angeles", + "image": "https://lh3.googleusercontent.com/MWGnaY3t_1-j7YylPpq35rvBU9gIBJIsnrtW95THrBw9N0PWrAVtbHKUBH8OdxyWI9gYdymndmSgwS8tl23GylytyefNC74i4-pniQ", + "lat": 33.995939, + "lng": -118.4766773, + "name": "Los Angeles, US", + "phone": "+1 310-310-6000", + "region": "north-america" + }, + { + "address": "811 E Washington Ave\nSuite 700\nMadison, WI 53703\nUnited States", + "id": "madison", + "image": "https://lh3.googleusercontent.com/sQDFJpbQl4EVGfdpHsw_24mxsnUNAnDs6f-00QCj0g_Z38CEqjG4PuLPoS_T6eTOPV3QXX907Kap_TkaE3cEG4fhJWIoWsZELIGyvw", + "lat": 43.081091, + "lng": -89.374619, + "name": "Madison", + "phone": "+1 608-669-9841", + "region": "north-america" + }, + { + "address": "Plaza Pablo Ruiz Picasso, I\nMadrid 28020\nSpain", + "id": "madrid", + "image": "https://lh3.googleusercontent.com/x36CdPxkwxxctp0wvDYfTjgTzNgMiZV0xoKeLMwHzdccpJGYUA6a61fSeX1_Rt-lfofMjfUwAhFxd7DbjsE8_393plkTly-T5YkpCA", + "lat": 40.4505331, + "lng": -3.6931161, + "name": "Madrid", + "phone": "+34 91-748-6400", + "region": "europe" + }, + { + "address": "161 Collins Street,\nMelbourne VIC 3000,\nAustralia", + "id": "melbourne", + "image": "https://lh3.googleusercontent.com/U_5KiB8x7T-Rrdp90ygnO1kbZxiWJz4G6CbD6_51CjH5zaMP23upWELryFOe99k_AqlPZschCY7Nx--wYufcIV54HnjGcP3lf28X1A", + "lat": -37.815328, + "lng": 144.968737, + "name": "Melbourne", + "phone": "", + "region": "asia-pacific" + }, + { + "address": "Google Mexico, S. de R.L. de C.V.\nMontes Urales 445\nLomas de Chapultepec\nMexico City 11000, Mexico", + "id": "mexico-city", + "image": "https://lh3.googleusercontent.com/P_U5ECZJ--t8khoKFxoeeJwa7PZy-3TriZbit5sRJDUdupf3NZRJegsnB4ucLqdLEV3De41fmByckDDC6uHMI82cXIFp4C1WwI1a1g", + "lat": 19.4283793, + "lng": -99.2065518, + "name": "Mexico City", + "phone": "+52 55-5342-8400", + "region": "latin-america" + }, + { + "address": "1450 Brickell Ave Ste 900 \nMiami FL 33131\nUnited States", + "id": "miami", + "image": "https://lh3.googleusercontent.com/DTk99d9bCqiCN8sFj3FBr8BdGPYC97PCYbiLbdq6GZ-_Er268DSlvfRM_g8hwA5tOmw_6c3PBjpKpuRQTuXS8H8_hpIlCQKyobyYjQ", + "lat": 25.758473, + "lng": -80.1932144, + "name": "Miami", + "phone": "+1 305-985-7900", + "region": "north-america" + }, + { + "address": "Porta Nuova Isola, Building C, Via Federico Confalonieri 4\n20124 Milan\nItaly", + "id": "milan", + "image": "https://lh3.googleusercontent.com/nZ_KE1LqNmW5qb6i-czLlm_yqRJtLmvEmyLRI0BYjqMlOiC_5VmbEI3DeHQyPOHp6PzoN2gKJ0j73BALkddFmDFXOIe9Wwctmt73cqI", + "lat": 45.486147, + "lng": 9.189546, + "name": "Milan", + "phone": "", + "region": "europe" + }, + { + "address": "1253 McGill College Avenue\nMontreal, QC H3B 2Y5\nCanada", + "id": "montreal", + "image": "https://lh3.googleusercontent.com/S310Um4pKym8bvHQcQmJLc4ohURWEq3AQHjJ-b5aMY-TpA9P4LCKcxGEg4fik-zSL6MrtiEi8Qt3JbAZl8x-GiI31wfm_myGfb3manQ", + "lat": 45.50191, + "lng": -73.570365, + "name": "Montreal", + "phone": "514-670-8700", + "region": "north-america" + }, + { + "address": "7 Balchug St\nMoscow 115035\nRussia", + "id": "moscow", + "image": "https://lh3.googleusercontent.com/i6cwRxcix3LUdviTVKoLG2Ep6q9pjfPIX_nrge-YkgjIvTgCH5QQpSI6wCpKvg0HiH56lHu6K8eAkCrPZUCzspS6Y9K19U47xr4hww", + "lat": 55.746747, + "lng": 37.626435, + "name": "Moscow", + "phone": "+7-495-644-1400", + "region": "europe" + }, + { + "address": "1600 Amphitheatre Parkway\nMountain View, CA 94043\nUnited States", + "id": "mountain-view", + "image": "https://lh3.googleusercontent.com/Mh8P8gvVwO7NOXfg8anxwPXRy5oKZJ6Cz_LbFfOVdeIsdDfogmMcMsiW7HD7HD2NOINzAPH_v8dALWSuDiiTjCjRnenI7B3l6Pg4yw", + "lat": 37.421512, + "lng": -122.084101, + "name": "Mountain View", + "phone": "", + "region": "north-america" + }, + { + "address": "3 North Avenue\nMaker Maxity, Bandra Kurla Complex\nBandra East\nMumbai, Maharashtra 400051\nIndia", + "id": "mumbai", + "image": "https://lh3.googleusercontent.com/twldrlVORn84fYsOLwNLabfRPQYX-dJAzJtpam-Ea4D7QIY1pvMa9FCMbpjUFA8uniBg6XAh8pMijf9qnjmEm4d17UFkwRGToiv5Ug", + "lat": 19.054364, + "lng": 72.850591, + "name": "Mumbai", + "phone": "+91-22-6611-7150", + "region": "asia-pacific" + }, + { + "address": "Erika-Mann-Str. 33\n80636 Munich\nGermany", + "id": "munich", + "image": "https://lh3.googleusercontent.com/sVZqxencTTD84raIgMWd5SbmXZTvQmwUzxj6IakbIGuAua5JDu-Q64uJE-cm3TYeSjKVQo7VSwIODVpwswjtrpwBUvXTa5MDFXoNAw", + "lat": 48.14305556, + "lng": 11.54083333, + "name": "Munich", + "phone": "", + "region": "europe" + }, + { + "address": "111 8th Avenue\nNew York, NY 10011\nUnited States", + "id": "new-york", + "image": "https://lh3.googleusercontent.com/BWdXxSOqBpjGFzAJVVr02QQs5XSe33dEeNDG6lXhd-nuv32ruMjD01yBJX3Rk4_xP6glB1ycMvwypEPr6YO665grfWqEEI2LPYUaMg", + "lat": 40.741445, + "lng": -74.003102, + "name": "New York", + "phone": "+1 212-565-0000", + "region": "north-america" + }, + { + "address": "Aker Brygge\nBryggegata 6\n0250 Oslo\nNorway", + "id": "oslo", + "image": "https://lh3.googleusercontent.com/lc9jPxaz4CzdC3sD4wFlzml1Y221PvtsisYGenint536WNbyIMY2cp2qnQOmnT0IWPoOCjarwMgK6zddvTcOu6YcAuaVLfQAdqZ2UQg", + "lat": 59.90987, + "lng": 10.72598, + "name": "Oslo", + "phone": "", + "region": "europe" + }, + { + "address": "8 Rue de Londres\n75009 Paris\nFrance", + "id": "paris", + "image": "https://lh3.googleusercontent.com/GHZlAB7t3toRGww1NJ6ZC2IpvR0jkgqFkQ0ZvM02dmQWt6fiHIKJZ7Eova959UD0PAapPE2r2TYMe3-dE3jGDgEoqHch0qyjAKvPENc", + "lat": 48.8771, + "lng": 2.33, + "name": "Paris", + "phone": "", + "region": "europe" + }, + { + "address": "6425 Penn Avenue\nPittsburgh, PA 15206\nUnited States", + "id": "pittsburgh", + "image": "https://lh3.googleusercontent.com/47kJwc4CR6oGOI2l_su5CJHnEWkrUZlz7LZRGXHgF71xa-0gJc8qCBhnsNoigcNEGFfBpb3y5AxVXJP_TxvHtgUgTrU8zmBm3Two7w", + "lat": 40.45716, + "lng": -79.916596, + "name": "Pittsburgh", + "phone": "+1 412-345-6700", + "region": "north-america" + }, + { + "address": "12422 W. Bluff Creek Drive\nPlaya Vista, CA 90094\nUnited States", + "id": "playa-vista", + "image": "https://lh3.googleusercontent.com/xnHVNI6bCcQxJyLV6sG3op8PlJcT9XgMAGmHrXtj5axhCZPH7Tbc9Ppjb2gTCtGbKmilT17B0dKzczOJh9JANh8Wwz0SXH0pEqCOkQ", + "lat": 33.97684, + "lng": -118.407244, + "name": "Playa Vista", + "phone": "", + "region": "north-america" + }, + { + "address": "Wells Fargo Building, 309 SW 6th Ave\nPortland, OR 97204\nUnited States", + "id": "portland", + "image": "https://lh3.googleusercontent.com/FMeFmwWFZJD02kj0H73t5v8NPrVOecVxuCl9cA-vLiXgaXErYQxmMXJKvvROgwSNvgPdmRZ4-GQuub74p0dDwJgY37vBNN2vgx7Utw", + "lat": 45.521622, + "lng": -122.677458, + "name": "Portland", + "phone": "", + "region": "north-america" + }, + { + "address": "Stroupeznickeho str. 3191/17\nPrague, Czech Republic\n150 00", + "id": "prague", + "image": "https://lh3.googleusercontent.com/jVNKH2mzDQ4Zu1-1G80-nDvLHYE9dmeetv43WG3zo7-dQWJoX1ghtXvviZHDLRG-ScqA804I2guuExY-8pkzIdkYlU28QGOB8Jkkiw", + "lat": 50.070259, + "lng": 14.402642, + "name": "Prague", + "phone": "", + "region": "europe" + }, + { + "address": "1600 Seaport Boulevard\nRedwood City, CA 94063\nUnited States", + "id": "redwood-city", + "image": "https://lh3.googleusercontent.com/a7GCRT1go5jQzEQj--A-kq98pURYsO4cTCJPj6azEev7za4Y__Kd3E_khFyn5uxRtPC0Co_ZxzQtqrlXeOSNey8fOSV4pK0ffzSW5A", + "lat": 37.512171, + "lng": -122.201178, + "name": "Redwood City", + "phone": "", + "region": "north-america" + }, + { + "address": "1875 Explorer Street \n10th Floor\nReston, VA 20190\nUnited States", + "id": "reston", + "image": "https://lh3.googleusercontent.com/4WuJCZlLflcQjsyhsGX3VSGDEVmC0Ljq291ECgVk3nN89ppnhSbdQIRI1I1-qh5YEf0Yicdc6amuqKz7oAdgLvQoNBrM9Zh3BcUwSw", + "lat": 38.958309, + "lng": -77.359795, + "name": "Reston", + "phone": "+1 202-370-5600", + "region": "north-america" + }, + { + "address": "901 Cherry Avenue\nSan Bruno, CA 94066\nUnited States", + "id": "san-bruno", + "image": "https://lh3.googleusercontent.com/zcy-Un_QDZfx7nTlImk-jCocxSUjQAQ4SS0eVdBuNRZz3Nyb5WK_2oGwYpnBEdqjIcv_b-umq_akpWBEylaEp-wXk3pj9-gu6Ko9Igs", + "lat": 37.62816, + "lng": -122.426491, + "name": "San Bruno", + "phone": "", + "region": "north-america" + }, + { + "address": "6420 Sequence Dr \nSuite 400\nSan Diego, CA 92121\nUnited States", + "id": "san-diego", + "image": "https://lh3.googleusercontent.com/RgGUUE3ra1j-mQIH8vp6an37hlwduD8uVnaCv8ivo5mX6ekdnZYd0-hlQ1hpQzV0ZgPk7y8h60oWy5MK5VF48ozZMYRXnh1ddJjuVGo", + "lat": 32.90961, + "lng": -117.181899, + "name": "San Diego", + "phone": "+1 858-239-4000", + "region": "north-america" + }, + { + "address": "345 Spear Street\nSan Francisco, CA 94105\nUnited States", + "id": "san-francisco", + "image": "https://lh3.googleusercontent.com/OC_0_XdXLar-ytOETAv3uwRGfnLABSRu66hqLQpLrwIhqInPD6ccdZSEu_d5S8wmjc1knb9OM7yNh2K7hoGznvKZOgFlvrxJesd7mQ", + "lat": 37.789972, + "lng": -122.390013, + "name": "San Francisco", + "phone": "+1 415-736-0000", + "region": "north-america" + }, + { + "address": "Costanera Sur Rio 2730 \nLas Condes, Santiago\nChile", + "id": "santiago", + "image": "https://lh3.googleusercontent.com/KrMbZzxFsAcNmYg8BQL_qBAekN1bNNJGo1aar8nkFhYXYDYOBmwJc2x1XElkDdIqLdedU5V7QKTGxXne8-f-qAW_bOy1FUqmJ8JM", + "lat": -33.413383, + "lng": -70.605665, + "name": "Santiago", + "phone": "", + "region": "latin-america" + }, + { + "address": "Av. Brigadeiro Faria Lima, 3477 \nS\u00e3o Paulo\n04538-133, Brazil", + "id": "sao-paulo", + "image": "https://lh3.googleusercontent.com/MwcGyEZBKkmoycVLcpl_U3gdIJBoWDU8u2kUNq57DmZVkWDyraoaZyQC0HOiFQvNHjVugEiVTWsy-poAsNfDLoSkJ5RoTBi1Hpd4GcI", + "lat": -23.586479, + "lng": -46.682078, + "name": "Sao Paulo", + "phone": "", + "region": "latin-america" + }, + { + "address": "601 N. 34th Street\nSeattle, WA 98103\nUnited States", + "id": "seattle", + "image": "https://lh3.googleusercontent.com/pNaRyPV3SkqsVvmdmN0sC-viBupr-41tZM3_cpSNH_3Zdy826gIhM0zHfoowA6SCkcsOkUxDvJ8wG5CodorohisOgR9q_QE7wH1ua-M", + "lat": 47.649316, + "lng": -122.350629, + "name": "Seattle", + "phone": "+1 206-876-1800", + "region": "north-america" + }, + { + "address": "Google Korea LLC.\n22nd Floor, Gangnam Finance Centre\n152 Teheran-ro, Gangnam-gu\nSeoul 06236\nSouth Korea", + "id": "seoul", + "image": "https://lh3.googleusercontent.com/i8rfvJIUNpLBkmWWSoetUzFGHUd_RaulLh8F3EHme3FMTUtDs8EVWsrFLuaemh1Zd60p5ndCcKq8-ZQN8eibbua-YNzlzQ8AKtHFzrQ", + "lat": 37.500295, + "lng": 127.036843, + "name": "Seoul", + "phone": "+82-2-531-9000", + "region": "asia-pacific" + }, + { + "address": "100 Century Avenue, Pudong\nShanghai 200120\nChina", + "id": "shanghai", + "image": "https://lh3.googleusercontent.com/wFCKLAJvrAoE_GiXqRNa0w4Rsr0iY_SyMGbO2jnIhLXGanYs1c5_BPE8TxQJw-e14uaLDHjY772V-Vv-Kf3GmrIRSlHjoV9yD339wRQ", + "lat": 31.23464, + "lng": 121.507662, + "name": "Shanghai", + "phone": "+86-21-6133-7666", + "region": "asia-pacific" + }, + { + "address": "70 Pasir Panjang Road, #03-71, \nMapletree Business City \nSingapore 117371", + "id": "singapore", + "image": "https://lh3.googleusercontent.com/--5H57B8aG4-DX9s79Spo3ygrsI9NMFnZo1uTZzs5s5AeeOvmiy81k__tu9r7JbRTTLzryK-oUy0UREclmD_qfV81VvaT4K9jJa8gg", + "lat": 1.276466, + "lng": 103.798965, + "name": "Singapore", + "phone": "+65 6521-8000", + "region": "asia-pacific" + }, + { + "address": "Kungsbron 2 \n111 22 Stockholm\nSweden", + "id": "stockholm", + "image": "https://lh3.googleusercontent.com/Q2016qdodQKowCyzfN14RLYERc2IplyM2FzJvj0kzbW4eLXnIxoFF1eZMc_CwtodxbpyfhfebUrawHtIgFb2kh9-EQnhcaHXpV0Fnw", + "lat": 59.333432, + "lng": 18.054619, + "name": "Stockholm", + "phone": "", + "region": "europe" + }, + { + "address": "803 11th Avenue\nSunnyvale, CA 94089\nUnited States", + "id": "sunnyvale", + "image": "https://lh3.googleusercontent.com/xd1Z3wr4cee9gtKQSnrOd-NWjc6UTwpwngElt4pkqukzOf-l0hrgQuRRBzvSjqmF4w1ZAHR1I12grFa5Zhqd9-7dKUitPtpMg51Zrf8", + "lat": 37.403694, + "lng": -122.031583, + "name": "Sunnyvale", + "phone": "", + "region": "north-america" + }, + { + "address": "48 Pirrama Road\nSydney, NSW 2009\nAustralia ", + "id": "sydney", + "image": "https://lh3.googleusercontent.com/03Hp4ZwQHs_rWLEWQtrOc62hEHzffD_uoZFCbo56eoeLyZ3L89-Fy5Dd8WcmrGFGK31QC_hZqzuU7f9QhxqjckE7BSLo_arwWjCH1w", + "lat": -33.866638, + "lng": 151.195672, + "name": "Sydney", + "phone": "+61 2 9374 4000", + "region": "asia-pacific" + }, + { + "address": "No. 7 XinYi Road Section 5, Taipei\nTaiwan", + "id": "taipei", + "image": "https://lh3.googleusercontent.com/h19TQz36F4jY_ZfQxuP5F-THZbl4nAIGz473uFfLqzD_6kpw-r3b6M_Wbna5QvvymqZ-wdnhkLCRt63Pypnc9GyawNqMlQwM1_BYbg", + "lat": 25.033447, + "lng": 121.564901, + "name": "Taipei", + "phone": "+886 2 8729 6000", + "region": "asia-pacific" + }, + { + "address": "Yigal Alon 98\nTel Aviv, 6789141\nIsrael ", + "id": "tel-aviv", + "image": "https://lh3.googleusercontent.com/BZxU1dJCWFmtVeBqVYFC8SmSzX4CCzO5eedosW1s7sv2b2HoKwEno15vICfeQdsc_BGIaysKb8VyF64IB9hbFzMZ_MlQDJhP7kfF", + "lat": 32.070043, + "lng": 34.794087, + "name": "Tel Aviv", + "phone": "+972-74-746-6453", + "region": "africa-middle-east" + }, + { + "address": "Roppongi Hills Mori Tower\n6-10-1 Roppongi\nMinato-ku, Tokyo 106-6126\nJapan", + "id": "tokyo-rpg", + "image": "https://lh3.googleusercontent.com/i7PqriAmbeqB7KQ4h_8K0T60DD-oAoz7bvvjlB4vx2267l9QHfKBHb7WUMSutYd88Xu4TRwWqIquL05bYcpTyU_58gWp8Ja2Xo2zOfM", + "lat": 35.66047, + "lng": 139.729231, + "name": "Tokyo \u2013 RPG", + "phone": "+81-3-6384-9000", + "region": "asia-pacific" + }, + { + "address": "Shibuya Stream\n3-21-3 Shibuya\nShibuya-ku, Tokyo 150-0002\nJapan", + "id": "tokyo-strm", + "image": "https://lh3.googleusercontent.com/GzaUJcEqlixelFX8dg1qcLPwAb4RpEXr3JMxyxpgSTWL17Gso_aq3NeMtQPES7f_JdIrGr9YTBSt08XgNAeoLSkxr3Ue_J0cW3VMCw", + "lat": 35.6572564, + "lng": 139.7028246, + "name": "Tokyo \u2013 STRM", + "phone": "+81-3-6384-9000", + "region": "asia-pacific" + }, + { + "address": "111 Richmond Street West\nToronto, ON M5H 2G4\nCanada", + "id": "toronto", + "image": "https://lh3.googleusercontent.com/vZUQcWL3r_bsHBRP-Z0XfhMxjlSAAe9sZLlw5rbBzqsM6w-WVjnTZGaw3w-PkqkhHPy0x-2Xzg_gishFkVjn5r3epKifwhmRc741", + "lat": 43.650477, + "lng": -79.383858, + "name": "Toronto", + "phone": "416-915-8200", + "region": "north-america" + }, + { + "address": "Graben 19\n1010 Wien\nAustria", + "id": "vienna", + "image": "https://lh3.googleusercontent.com/roYQN_TnYd_fP0FCdZxA6lMLbp-h7PyPlDBKwVdfVWKkOCxmLjFHqm-n7hrcakcXHS1FzjXW5XWF_MApzuGIrvy2cewCYd7Z9q5MUw", + "lat": 48.209351, + "lng": 16.368419, + "name": "Vienna", + "phone": "", + "region": "europe" + }, + { + "address": "Emilii Plater 53\n00-113 Warsaw\nPoland ", + "id": "warsaw", + "image": "https://lh3.googleusercontent.com/jTf0m2s5A2qS25ArE3x6Tl1KXtpv3JmHIfcBuw7f-JHsTR0tMiyUVeHO1wBeJ2eEGcAWUbTe3b9B8iP8wyL-TROS5zxmMofMHsnf", + "lat": 52.233448, + "lng": 21.001668, + "name": "Warsaw", + "phone": "+48 22 207 19 00", + "region": "europe" + }, + { + "address": "25 Massachusetts Avenue\nWashington DC, 20001\nUnited States", + "id": "washington-dc", + "image": "https://lh3.googleusercontent.com/6rKu8CCH6nMVKjwpnxDlgf_Sdlc7jk83QBVhoLikzEyibYTZkvNPn-QPCJTv3AkjUYf2dHcE15UvPsrg18xNd4R8_eg3b-yn01yXgQ", + "lat": 38.898337, + "lng": -77.010286, + "name": "Washington DC", + "phone": " (202) 346-1100", + "region": "north-america" + }, + { + "address": "Gen. Jozefa Bema nr 2\n50-265 Wroclaw\nPoland", + "id": "wroclaw", + "image": "https://lh3.googleusercontent.com/Or6dY4MCUCbMnDv4kG8J7u-QTsWhvbqbAbMN9Vp38aJAS7ec7A39gYddcEGbrwd_veFeZo2phypqc1ABk20PZ9jCVxZfuNGYS7j3LDc", + "lat": 51.117687, + "lng": 17.041737, + "name": "Wroclaw", + "phone": "+48 (71) 73 41 000", + "region": "europe" + }, + { + "address": "Brandschenkestrasse 110\n8002 Z\u00fcrich\nSwitzerland", + "id": "zurich", + "image": "https://lh3.googleusercontent.com/kmEsDEYzbMlluwDPYkeEEBpAvL9MJbXZR3hD3uettOqE8T7lbXvV508j4d4QngB7iwYZa8YYlXiVnGWfZ4ZvTJbputGXsfxrLGhD3tI", + "lat": 47.365063, + "lng": 8.524425, + "name": "Zurich", + "phone": "+41 44 668 18 00", + "region": "europe" + } + ], + "regions": [ + { + "coords": { + "lat": 2.9660291, + "lng": 1.3271339 + }, + "id": "africa-middle-east", + "name": "Africa & Middle East", + "zoom": 3.0 + }, + { + "coords": { + "lat": 0.0524811, + "lng": 127.6560787 + }, + "id": "asia-pacific", + "name": "Asia Pacific", + "zoom": 3.0 + }, + { + "coords": { + "lat": 46.1352815, + "lng": 7.4033438 + }, + "id": "europe", + "name": "Europe", + "zoom": 4.0 + }, + { + "coords": { + "lat": -17.5554497, + "lng": -99.2316195 + }, + "id": "latin-america", + "name": "Latin America", + "zoom": 3.0 + }, + { + "coords": { + "lat": 45.7128252, + "lng": -97.1547448 + }, + "id": "north-america", + "name": "North America", + "zoom": 4.0 + } + ] +} diff --git a/google_maps/ios/.gitignore b/google_maps/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/google_maps/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/google_maps/ios/Flutter/AppFrameworkInfo.plist b/google_maps/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/google_maps/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/google_maps/ios/Flutter/Debug.xcconfig b/google_maps/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/google_maps/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/google_maps/ios/Flutter/Release.xcconfig b/google_maps/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/google_maps/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/google_maps/ios/Podfile b/google_maps/ios/Podfile new file mode 100644 index 0000000..54d087f --- /dev/null +++ b/google_maps/ios/Podfile @@ -0,0 +1,44 @@ +# Google Maps SDK requires platform version 14 +platform :ios, '14.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/google_maps/ios/Runner.xcodeproj/project.pbxproj b/google_maps/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..fbbe3e4 --- /dev/null +++ b/google_maps/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,724 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + AF1E4B6CBD38B01355478777 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08C4812F6709F560C59F3FCD /* Pods_RunnerTests.framework */; }; + DD9B56340B35B0842E89B2F6 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 55E455A50CB23D09F40A1D97 /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 08C4812F6709F560C59F3FCD /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 55E455A50CB23D09F40A1D97 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 64610D225D90DA8A981B927A /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7DA65C1178AD96F28D7E762D /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D1388638FBFCC5F8A102D8FD /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + E3AE1957338395D6D0AD3932 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + FC7723BB480EA33966A34168 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + FF6B98AB1191B051A095CDAE /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DD9B56340B35B0842E89B2F6 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9BBD5AE1C4A93B1427B2EC99 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AF1E4B6CBD38B01355478777 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 226331665C060508B487D264 /* Pods */ = { + isa = PBXGroup; + children = ( + D1388638FBFCC5F8A102D8FD /* Pods-Runner.debug.xcconfig */, + 64610D225D90DA8A981B927A /* Pods-Runner.release.xcconfig */, + 7DA65C1178AD96F28D7E762D /* Pods-Runner.profile.xcconfig */, + FF6B98AB1191B051A095CDAE /* Pods-RunnerTests.debug.xcconfig */, + FC7723BB480EA33966A34168 /* Pods-RunnerTests.release.xcconfig */, + E3AE1957338395D6D0AD3932 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 226331665C060508B487D264 /* Pods */, + DDFD2DC1CFC42662DAF0FAFC /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + DDFD2DC1CFC42662DAF0FAFC /* Frameworks */ = { + isa = PBXGroup; + children = ( + 55E455A50CB23D09F40A1D97 /* Pods_Runner.framework */, + 08C4812F6709F560C59F3FCD /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + FDA6296839960C452DD4A64C /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 9BBD5AE1C4A93B1427B2EC99 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 857F6A9BAF9DA553BFDE4EB5 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 17B6E68B22F32C899B6DAF59 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 17B6E68B22F32C899B6DAF59 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 857F6A9BAF9DA553BFDE4EB5 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + FDA6296839960C452DD4A64C /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = TC87DMJLQP; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.googleMapsInFlutter; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FF6B98AB1191B051A095CDAE /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.googleMapsInFlutter.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FC7723BB480EA33966A34168 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.googleMapsInFlutter.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E3AE1957338395D6D0AD3932 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.googleMapsInFlutter.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = TC87DMJLQP; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.googleMapsInFlutter; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = TC87DMJLQP; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.googleMapsInFlutter; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/google_maps/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/google_maps/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/google_maps/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/google_maps/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/google_maps/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/google_maps/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/google_maps/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/google_maps/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/google_maps/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/google_maps/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/google_maps/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..87131a0 --- /dev/null +++ b/google_maps/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/google_maps/ios/Runner.xcworkspace/contents.xcworkspacedata b/google_maps/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/google_maps/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/google_maps/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/google_maps/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/google_maps/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/google_maps/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/google_maps/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/google_maps/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/google_maps/ios/Runner/AppDelegate.swift b/google_maps/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..6861f5a --- /dev/null +++ b/google_maps/ios/Runner/AppDelegate.swift @@ -0,0 +1,18 @@ +import UIKit +import Flutter +import GoogleMaps + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + + // TODO: Add your Google Maps API key + GMSServices.provideAPIKey("YOUR-API-KEY") + + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/google_maps/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/google_maps/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/google_maps/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/google_maps/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/google_maps/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/google_maps/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/google_maps/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/google_maps/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/google_maps/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/google_maps/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/google_maps/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/google_maps/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/google_maps/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/google_maps/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/google_maps/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/google_maps/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/google_maps/ios/Runner/Base.lproj/LaunchScreen.storyboard b/google_maps/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/google_maps/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/google_maps/ios/Runner/Base.lproj/Main.storyboard b/google_maps/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/google_maps/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/google_maps/ios/Runner/Info.plist b/google_maps/ios/Runner/Info.plist new file mode 100644 index 0000000..3d7ffa1 --- /dev/null +++ b/google_maps/ios/Runner/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Google Maps In Flutter + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + google_maps_in_flutter + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/google_maps/ios/Runner/Runner-Bridging-Header.h b/google_maps/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/google_maps/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/google_maps/ios/RunnerTests/RunnerTests.swift b/google_maps/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/google_maps/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/google_maps/lib/main.dart b/google_maps/lib/main.dart new file mode 100644 index 0000000..629ecb6 --- /dev/null +++ b/google_maps/lib/main.dart @@ -0,0 +1,68 @@ +/* + * Copyright 2019 Google LLC + * + * 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 + * + * https://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. + */ + +import 'package:flutter/material.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; + +void main() => runApp(const MyApp()); + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + late GoogleMapController mapController; + + final LatLng _center = const LatLng(-33.86, 151.20); + + void _onMapCreated(GoogleMapController controller) { + mapController = controller; + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData(primarySwatch: Colors.green), + home: Scaffold( + appBar: AppBar( + title: const Text('Sydney'), + backgroundColor: Colors.green[700], + ), + body: GoogleMap( + onMapCreated: _onMapCreated, + initialCameraPosition: CameraPosition( + target: _center, + zoom: 11.0, + ), + markers: { + const Marker( + markerId: MarkerId('Sydney'), + position: LatLng(-33.86, 151.20), + infoWindow: InfoWindow( + title: "Sydney", + snippet: "Capital of New South Wales", + ), + ), + }, + ), + ), + ); + } +} diff --git a/google_maps/lib/src/locations.dart b/google_maps/lib/src/locations.dart new file mode 100644 index 0000000..cdc4a01 --- /dev/null +++ b/google_maps/lib/src/locations.dart @@ -0,0 +1,115 @@ +/* + * Copyright 2019 Google LLC + * + * 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 + * + * https://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. + */ + +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart' show rootBundle; +import 'package:http/http.dart' as http; +import 'package:json_annotation/json_annotation.dart'; + +part 'locations.g.dart'; + +@JsonSerializable() +class LatLng { + LatLng({required this.lat, required this.lng}); + + factory LatLng.fromJson(Map json) => _$LatLngFromJson(json); + Map toJson() => _$LatLngToJson(this); + + final double lat; + final double lng; +} + +@JsonSerializable() +class Region { + Region({ + required this.coords, + required this.id, + required this.name, + required this.zoom, + }); + + factory Region.fromJson(Map json) => _$RegionFromJson(json); + Map toJson() => _$RegionToJson(this); + + final LatLng coords; + final String id; + final String name; + final double zoom; +} + +@JsonSerializable() +class Office { + Office({ + required this.address, + required this.id, + required this.image, + required this.lat, + required this.lng, + required this.name, + required this.phone, + required this.region, + }); + + factory Office.fromJson(Map json) => _$OfficeFromJson(json); + Map toJson() => _$OfficeToJson(this); + + final String address; + final String id; + final String image; + final double lat; + final double lng; + final String name; + final String phone; + final String region; +} + +@JsonSerializable() +class Locations { + Locations({required this.offices, required this.regions}); + + factory Locations.fromJson(Map json) => + _$LocationsFromJson(json); + Map toJson() => _$LocationsToJson(this); + + final List offices; + final List regions; +} + +Future getGoogleOffices() async { + const googleLocationsURL = 'https://about.google/static/data/locations.json'; + + // Retrieve the locations of Google offices + try { + final response = await http.get(Uri.parse(googleLocationsURL)); + if (response.statusCode == 200) { + return Locations.fromJson( + json.decode(response.body) as Map, + ); + } + } catch (e) { + if (kDebugMode) { + print(e); + } + } + + // Fallback for when the above HTTP request fails. + return Locations.fromJson( + json.decode(await rootBundle.loadString('assets/locations.json')) + as Map, + ); +} diff --git a/google_maps/lib/src/locations.g.dart b/google_maps/lib/src/locations.g.dart new file mode 100644 index 0000000..5d67b53 --- /dev/null +++ b/google_maps/lib/src/locations.g.dart @@ -0,0 +1,84 @@ +/* + * Copyright 2019 Google LLC + * + * 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 + * + * https://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. + */ + +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'locations.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +LatLng _$LatLngFromJson(Map json) => LatLng( + lat: (json['lat'] as num).toDouble(), + lng: (json['lng'] as num).toDouble(), +); + +Map _$LatLngToJson(LatLng instance) => { + 'lat': instance.lat, + 'lng': instance.lng, +}; + +Region _$RegionFromJson(Map json) => Region( + coords: LatLng.fromJson(json['coords'] as Map), + id: json['id'] as String, + name: json['name'] as String, + zoom: (json['zoom'] as num).toDouble(), +); + +Map _$RegionToJson(Region instance) => { + 'coords': instance.coords, + 'id': instance.id, + 'name': instance.name, + 'zoom': instance.zoom, +}; + +Office _$OfficeFromJson(Map json) => Office( + address: json['address'] as String, + id: json['id'] as String, + image: json['image'] as String, + lat: (json['lat'] as num).toDouble(), + lng: (json['lng'] as num).toDouble(), + name: json['name'] as String, + phone: json['phone'] as String, + region: json['region'] as String, +); + +Map _$OfficeToJson(Office instance) => { + 'address': instance.address, + 'id': instance.id, + 'image': instance.image, + 'lat': instance.lat, + 'lng': instance.lng, + 'name': instance.name, + 'phone': instance.phone, + 'region': instance.region, +}; + +Locations _$LocationsFromJson(Map json) => Locations( + offices: (json['offices'] as List) + .map((e) => Office.fromJson(e as Map)) + .toList(), + regions: (json['regions'] as List) + .map((e) => Region.fromJson(e as Map)) + .toList(), +); + +Map _$LocationsToJson(Locations instance) => + { + 'offices': instance.offices, + 'regions': instance.regions, + }; diff --git a/google_maps/pubspec.yaml b/google_maps/pubspec.yaml new file mode 100644 index 0000000..b70df98 --- /dev/null +++ b/google_maps/pubspec.yaml @@ -0,0 +1,29 @@ +name: google_maps_in_flutter +description: A new Flutter project. +resolution: workspace +publish_to: 'none' +version: 1.0.0+1 + +environment: + sdk: ^3.9.0-0 + +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^1.0.2 + google_maps_flutter: ^2.7.0 + http: ^1.0.0 + json_annotation: ^4.7.0 + json_serializable: ^6.5.4 + +dev_dependencies: + analysis_defaults: + path: ../analysis_defaults + flutter_test: + sdk: flutter + build_runner: ^2.3.2 + +flutter: + uses-material-design: true + assets: + - assets/locations.json diff --git a/google_maps/test/widget_test.dart b/google_maps/test/widget_test.dart new file mode 100644 index 0000000..f706310 --- /dev/null +++ b/google_maps/test/widget_test.dart @@ -0,0 +1,26 @@ +/* + * Copyright 2019 Google LLC + * + * 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 + * + * https://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. + */ + +import 'package:flutter_test/flutter_test.dart'; + +import 'package:google_maps_in_flutter/main.dart'; + +void main() { + testWidgets('Do nothing test', (tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + }); +} diff --git a/google_maps/web/favicon.png b/google_maps/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/google_maps/web/favicon.png differ diff --git a/google_maps/web/icons/Icon-192.png b/google_maps/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/google_maps/web/icons/Icon-192.png differ diff --git a/google_maps/web/icons/Icon-512.png b/google_maps/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/google_maps/web/icons/Icon-512.png differ diff --git a/google_maps/web/icons/Icon-maskable-192.png b/google_maps/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/google_maps/web/icons/Icon-maskable-192.png differ diff --git a/google_maps/web/icons/Icon-maskable-512.png b/google_maps/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/google_maps/web/icons/Icon-maskable-512.png differ diff --git a/google_maps/web/index.html b/google_maps/web/index.html new file mode 100644 index 0000000..9d0b5db --- /dev/null +++ b/google_maps/web/index.html @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + google_maps_in_flutter + + + + + + + diff --git a/google_maps/web/manifest.json b/google_maps/web/manifest.json new file mode 100644 index 0000000..44ae833 --- /dev/null +++ b/google_maps/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "google_maps_in_flutter", + "short_name": "google_maps_in_flutter", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/ios_app_clip/.gitignore b/ios_app_clip/.gitignore new file mode 100644 index 0000000..5f73eb4 --- /dev/null +++ b/ios_app_clip/.gitignore @@ -0,0 +1,42 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +**/ios/Flutter/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json diff --git a/ios_app_clip/.metadata b/ios_app_clip/.metadata new file mode 100644 index 0000000..a7ecd71 --- /dev/null +++ b/ios_app_clip/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 65ae30c8cc99b7d48b5bd08d3726f5669b819be0 + channel: master + +project_type: app diff --git a/ios_app_clip/README.md b/ios_app_clip/README.md new file mode 100644 index 0000000..e516cab --- /dev/null +++ b/ios_app_clip/README.md @@ -0,0 +1,35 @@ +# ios_app_clip + +A sample project demonstrating integration with iOS App Clip. + +The App Clip target is rendered by Flutter and uses a plugin. + +## Intent + +1. Demonstrate it's possible to produce iOS App Clip targets rendered in Flutter. +2. Serve as a canonical example to test the App Clip scenario. +3. Let users validate their integration steps when following the guide at + [flutter.dev/docs/development/platform-integration/ios-app-clip](https://flutter.dev/docs/development/platform-integration/ios-app-clip) + +## To run + +In order to run. + +1. `flutter build ios --config-only` +1. `open Runner.xcworkspace` +1. Inside Xcode, select the `AppClip` target from the scheme dropdown +1. Select an iOS 16 or higher device +1. Select a development team as needed +1. Press run in Xcode to run + +## Questions/issues + +If you have a general question about iOS App Clips in Flutter, the +best places to go are: + +* [The FlutterDev Google Group](https://groups.google.com/forum/#!forum/flutter-dev) +* [The Flutter Gitter channel](https://gitter.im/flutter/flutter) +* [StackOverflow](https://stackoverflow.com/questions/tagged/flutter) + +If you run into an issue with the sample itself, please file an issue +in the [main Flutter repo](https://github.com/flutter/flutter/issues). \ No newline at end of file diff --git a/ios_app_clip/analysis_options.yaml b/ios_app_clip/analysis_options.yaml new file mode 100644 index 0000000..5e2133e --- /dev/null +++ b/ios_app_clip/analysis_options.yaml @@ -0,0 +1 @@ +include: ../analysis_options.yaml diff --git a/ios_app_clip/ios/.gitignore b/ios_app_clip/ios/.gitignore new file mode 100644 index 0000000..48744a8 --- /dev/null +++ b/ios_app_clip/ios/.gitignore @@ -0,0 +1,33 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* +/build/ + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios_app_clip/ios/AppClip/AppClip.entitlements b/ios_app_clip/ios/AppClip/AppClip.entitlements new file mode 100644 index 0000000..32c76e2 --- /dev/null +++ b/ios_app_clip/ios/AppClip/AppClip.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.developer.associated-domains + + appclips:com.example.iosAppClip + + com.apple.developer.parent-application-identifiers + + $(AppIdentifierPrefix)com.example.iosAppClip + + + diff --git a/ios_app_clip/ios/AppClip/Info.plist b/ios_app_clip/ios/AppClip/Info.plist new file mode 100644 index 0000000..9d1cf22 --- /dev/null +++ b/ios_app_clip/ios/AppClip/Info.plist @@ -0,0 +1,56 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + ios_app_clip + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + NSAppClip + + NSAppClipRequestEphemeralUserNotification + + NSAppClipRequestLocationConfirmation + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/ios_app_clip/ios/Podfile b/ios_app_clip/ios/Podfile new file mode 100644 index 0000000..52afa6a --- /dev/null +++ b/ios_app_clip/ios/Podfile @@ -0,0 +1,42 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +use_frameworks! +use_modular_headers! + +flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + +target 'Runner' +target 'AppClip' + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/ios_app_clip/ios/Runner.xcodeproj/project.pbxproj b/ios_app_clip/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..5b14651 --- /dev/null +++ b/ios_app_clip/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,873 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 09B38F64C10F58478FEDEBFE /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEC364865DEEF28F1CF02E0B /* Pods_Runner.framework */; }; + 0A48A26D2508B57F0036A529 /* AppClip.app in Embed App Clips */ = {isa = PBXBuildFile; fileRef = 0A48A2592508B57F0036A529 /* AppClip.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 0A48A2732508BEC90036A529 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 0A48A2742508BECB0036A529 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 0A48A2752508BECE0036A529 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 0A48A2762508BED70036A529 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 0A48A27A2508CC490036A529 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + EB1E5D6BD1EB7FDD37CA9116 /* Pods_AppClip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6DA2F0CDF100939E2A8955BB /* Pods_AppClip.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 0A48A26B2508B57F0036A529 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0A48A2582508B57F0036A529; + remoteInfo = AppClip; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 0A48A26E2508B57F0036A529 /* Embed App Clips */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(CONTENTS_FOLDER_PATH)/AppClips"; + dstSubfolderSpec = 16; + files = ( + 0A48A26D2508B57F0036A529 /* AppClip.app in Embed App Clips */, + ); + name = "Embed App Clips"; + runOnlyForDeploymentPostprocessing = 0; + }; + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0A48A2592508B57F0036A529 /* AppClip.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppClip.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 0A48A2692508B57F0036A529 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 0A48A26A2508B57F0036A529 /* AppClip.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AppClip.entitlements; sourceTree = ""; }; + 0A48A2772508BF510036A529 /* AppClip.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AppClip.entitlements; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 17DC3A5366CF3F5457184067 /* Pods-AppClip.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppClip.release.xcconfig"; path = "Target Support Files/Pods-AppClip/Pods-AppClip.release.xcconfig"; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 58DC8FC02239CD3EF83751F5 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 67DF00203AD017BBEAB63A8A /* Pods-AppClip.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppClip.profile.xcconfig"; path = "Target Support Files/Pods-AppClip/Pods-AppClip.profile.xcconfig"; sourceTree = ""; }; + 6DA2F0CDF100939E2A8955BB /* Pods_AppClip.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AppClip.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 94359FA0D98CE970F6CB5214 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9CE646F23E778C421B36CFF0 /* Pods-AppClip.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppClip.debug.xcconfig"; path = "Target Support Files/Pods-AppClip/Pods-AppClip.debug.xcconfig"; sourceTree = ""; }; + A826C8E63259424B8CCDA39A /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + DEC364865DEEF28F1CF02E0B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 0A48A2562508B57F0036A529 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EB1E5D6BD1EB7FDD37CA9116 /* Pods_AppClip.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 09B38F64C10F58478FEDEBFE /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0A48A25A2508B57F0036A529 /* AppClip */ = { + isa = PBXGroup; + children = ( + 0A48A2692508B57F0036A529 /* Info.plist */, + 0A48A26A2508B57F0036A529 /* AppClip.entitlements */, + ); + path = AppClip; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 0A48A25A2508B57F0036A529 /* AppClip */, + 97C146EF1CF9000F007C117D /* Products */, + FED81B5367470433A96020E4 /* Pods */, + D738E0138D09B2AA86ECB47A /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 0A48A2592508B57F0036A529 /* AppClip.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 0A48A2772508BF510036A529 /* AppClip.entitlements */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + D738E0138D09B2AA86ECB47A /* Frameworks */ = { + isa = PBXGroup; + children = ( + DEC364865DEEF28F1CF02E0B /* Pods_Runner.framework */, + 6DA2F0CDF100939E2A8955BB /* Pods_AppClip.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + FED81B5367470433A96020E4 /* Pods */ = { + isa = PBXGroup; + children = ( + 58DC8FC02239CD3EF83751F5 /* Pods-Runner.debug.xcconfig */, + 94359FA0D98CE970F6CB5214 /* Pods-Runner.release.xcconfig */, + A826C8E63259424B8CCDA39A /* Pods-Runner.profile.xcconfig */, + 9CE646F23E778C421B36CFF0 /* Pods-AppClip.debug.xcconfig */, + 17DC3A5366CF3F5457184067 /* Pods-AppClip.release.xcconfig */, + 67DF00203AD017BBEAB63A8A /* Pods-AppClip.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 0A48A2582508B57F0036A529 /* AppClip */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0A48A2722508B57F0036A529 /* Build configuration list for PBXNativeTarget "AppClip" */; + buildPhases = ( + 67F74C59571624F7D7F2B9BC /* [CP] Check Pods Manifest.lock */, + 0A48A2782508C8E40036A529 /* Build Flutter */, + 0A48A2552508B57F0036A529 /* Sources */, + 0A48A2562508B57F0036A529 /* Frameworks */, + 0A48A2572508B57F0036A529 /* Resources */, + 0A48A2792508C8F70036A529 /* Embed Flutter */, + 1999EAF6C873AB834CA4DC07 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AppClip; + productName = AppClip; + productReference = 0A48A2592508B57F0036A529 /* AppClip.app */; + productType = "com.apple.product-type.application.on-demand-install-capable"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 8D1CA438EAC9C57C39938BF6 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 0A48A26E2508B57F0036A529 /* Embed App Clips */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + CF23DA206BDE2D699E40B8E8 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 0A48A26C2508B57F0036A529 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1200; + LastUpgradeCheck = 1530; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 0A48A2582508B57F0036A529 = { + CreatedOnToolsVersion = 12.0; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 0A48A2582508B57F0036A529 /* AppClip */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 0A48A2572508B57F0036A529 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0A48A2752508BECE0036A529 /* LaunchScreen.storyboard in Resources */, + 0A48A2742508BECB0036A529 /* Assets.xcassets in Resources */, + 0A48A2732508BEC90036A529 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 0A48A2782508C8E40036A529 /* Build Flutter */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Build Flutter"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; + }; + 0A48A2792508C8F70036A529 /* Embed Flutter */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Embed Flutter"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; + }; + 1999EAF6C873AB834CA4DC07 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-AppClip/Pods-AppClip-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-AppClip/Pods-AppClip-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AppClip/Pods-AppClip-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; + }; + 67F74C59571624F7D7F2B9BC /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-AppClip-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 8D1CA438EAC9C57C39938BF6 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; + }; + CF23DA206BDE2D699E40B8E8 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 0A48A2552508B57F0036A529 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0A48A2762508BED70036A529 /* AppDelegate.swift in Sources */, + 0A48A27A2508CC490036A529 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 0A48A26C2508B57F0036A529 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0A48A2582508B57F0036A529 /* AppClip */; + targetProxy = 0A48A26B2508B57F0036A529 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 0A48A26F2508B57F0036A529 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = AppClip/AppClip.entitlements; + CODE_SIGN_STYLE = Automatic; + ENABLE_BITCODE = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = AppClip/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.example.iosAppClip${DEVELOPMENT_TEAM}.Clip"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 0A48A2702508B57F0036A529 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = AppClip/AppClip.entitlements; + CODE_SIGN_STYLE = Automatic; + ENABLE_BITCODE = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = AppClip/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.example.iosAppClip${DEVELOPMENT_TEAM}.Clip"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 0A48A2712508B57F0036A529 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = AppClip/AppClip.entitlements; + CODE_SIGN_STYLE = Automatic; + ENABLE_BITCODE = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = AppClip/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.example.iosAppClip${DEVELOPMENT_TEAM}.Clip"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Profile; + }; + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/AppClip.entitlements; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.example.iosAppClip${DEVELOPMENT_TEAM}"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/AppClip.entitlements; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.example.iosAppClip${DEVELOPMENT_TEAM}"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/AppClip.entitlements; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.example.iosAppClip${DEVELOPMENT_TEAM}"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 0A48A2722508B57F0036A529 /* Build configuration list for PBXNativeTarget "AppClip" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0A48A26F2508B57F0036A529 /* Debug */, + 0A48A2702508B57F0036A529 /* Release */, + 0A48A2712508B57F0036A529 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios_app_clip/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios_app_clip/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios_app_clip/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios_app_clip/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios_app_clip/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios_app_clip/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios_app_clip/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios_app_clip/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios_app_clip/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios_app_clip/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios_app_clip/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..fcac534 --- /dev/null +++ b/ios_app_clip/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios_app_clip/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios_app_clip/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/ios_app_clip/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/ios_app_clip/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios_app_clip/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios_app_clip/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios_app_clip/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios_app_clip/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios_app_clip/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios_app_clip/ios/Runner/AppClip.entitlements b/ios_app_clip/ios/Runner/AppClip.entitlements new file mode 100644 index 0000000..5097f70 --- /dev/null +++ b/ios_app_clip/ios/Runner/AppClip.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.associated-domains + + appclips:com.example.iosAppClip + + + diff --git a/ios_app_clip/ios/Runner/AppDelegate.swift b/ios_app_clip/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..b636303 --- /dev/null +++ b/ios_app_clip/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..28c6bf0 Binary files /dev/null and b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..f091b6b Binary files /dev/null and b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cde121 Binary files /dev/null and b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..d0ef06e Binary files /dev/null and b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..dcdc230 Binary files /dev/null and b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..c8f9ed8 Binary files /dev/null and b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..75b2d16 Binary files /dev/null and b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..c4df70d Binary files /dev/null and b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..6a84f41 Binary files /dev/null and b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..d0e1f58 Binary files /dev/null and b/ios_app_clip/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios_app_clip/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios_app_clip/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/ios_app_clip/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios_app_clip/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios_app_clip/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios_app_clip/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/ios_app_clip/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios_app_clip/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios_app_clip/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/ios_app_clip/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios_app_clip/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios_app_clip/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/ios_app_clip/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios_app_clip/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/ios_app_clip/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/ios_app_clip/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios_app_clip/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/ios_app_clip/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios_app_clip/ios/Runner/Base.lproj/Main.storyboard b/ios_app_clip/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..4d6689f --- /dev/null +++ b/ios_app_clip/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios_app_clip/ios/Runner/Info.plist b/ios_app_clip/ios/Runner/Info.plist new file mode 100644 index 0000000..6ba2055 --- /dev/null +++ b/ios_app_clip/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ios_app_clip + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/ios_app_clip/ios/Runner/Runner-Bridging-Header.h b/ios_app_clip/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios_app_clip/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/ios_app_clip/lib/main.dart b/ios_app_clip/lib/main.dart new file mode 100644 index 0000000..4b26c03 --- /dev/null +++ b/ios_app_clip/lib/main.dart @@ -0,0 +1,56 @@ +// Copyright 2020 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. + +import 'package:device_info/device_info.dart'; +import 'package:flutter/cupertino.dart'; + +void main() { + runApp(const Demo()); +} + +// The same content is shown for both the main app target and in the App +// Clip. +class Demo extends StatefulWidget { + const Demo({super.key}); + + @override + State createState() => _DemoState(); +} + +class _DemoState extends State { + String deviceInfo = ''; + + @override + void initState() { + DeviceInfoPlugin().iosInfo.then((info) { + setState(() { + deviceInfo = + '${info.name} on ${info.systemName} version ' + '${info.systemVersion}'; + }); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return CupertinoApp( + home: CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar( + middle: Text('App Clip'), + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(deviceInfo), + const Padding(padding: EdgeInsets.only(top: 18)), + const FlutterLogo(size: 128), + ], + ), + ), + ), + ); + } +} diff --git a/ios_app_clip/pubspec.yaml b/ios_app_clip/pubspec.yaml new file mode 100644 index 0000000..4aad2d1 --- /dev/null +++ b/ios_app_clip/pubspec.yaml @@ -0,0 +1,21 @@ +name: ios_app_clip +description: An example Flutter project that can build as an App Clip. +publish_to: "none" # Remove this line if you wish to publish to pub.dev +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^1.0.0 + device_info: ^2.0.3 + +dev_dependencies: + analysis_defaults: + path: ../analysis_defaults + flutter_test: + sdk: flutter +flutter: diff --git a/ios_app_clip/test/widget_test.dart b/ios_app_clip/test/widget_test.dart new file mode 100644 index 0000000..a93c160 --- /dev/null +++ b/ios_app_clip/test/widget_test.dart @@ -0,0 +1,24 @@ +// Copyright 2020 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. + +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:ios_app_clip/main.dart'; + +void main() { + testWidgets('Smoke test', (tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const Demo()); + + expect(find.byType(FlutterLogo), findsOneWidget); + }); +} diff --git a/material_3_demo/.gitignore b/material_3_demo/.gitignore new file mode 100644 index 0000000..24476c5 --- /dev/null +++ b/material_3_demo/.gitignore @@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/material_3_demo/.metadata b/material_3_demo/.metadata new file mode 100644 index 0000000..a7cbe79 --- /dev/null +++ b/material_3_demo/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + channel: beta + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + base_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + - platform: android + create_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + base_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + - platform: ios + create_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + base_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + - platform: linux + create_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + base_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + - platform: macos + create_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + base_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + - platform: web + create_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + base_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + - platform: windows + create_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + base_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/material_3_demo/README.md b/material_3_demo/README.md new file mode 100644 index 0000000..c55eb4c --- /dev/null +++ b/material_3_demo/README.md @@ -0,0 +1,35 @@ +# Material 3 Demo + +This sample Flutter app showcases Material 3 features in the Flutter Material library. These features include updated components, typography, color system and elevation support. The app supports light and dark themes, different color palettes, as well as the ability to switch between Material 2 and Material 3. For more information about Material 3, the guidance is now live at https://m3.material.io/. + +This app also includes new M3 components such as IconButtons, Chips, TextFields, Switches, Checkboxes, Radio buttons and ProgressIndicators. + +# Preview + +Screen Shot 2022-08-12 at 12 00 28 PMScreen Shot 2022-08-12 at 12 00 38 PM + + +# Features +## Icon Buttons on the Top App Bar + Users can switch between a light or dark theme with this button. + + Users can switch between Material 2 and Material 3 for the displayed components with this button. + + This button will bring up a pop-up menu that allows the user to change the base color used for the light and dark themes. This uses a new color seed feature to generate entire color schemes from a single color. + +## Component Screen +The default screen displays all the updated components in Material 3: AppBar, common Buttons, Floating Action Button(FAB), Chips, Card, Checkbox, Dialog, NavigationBar, NavigationRail, ProgressIndicators, Radio buttons, TextFields and Switch. + +### Adaptive Layout +Based on the fact that NavigationRail is not recommended on a small screen, the app changes its layout based on the screen width. If it's played on iOS or Android devices which have a narrow screen, a Navigation Bar will show at the bottom and will be used to navigate. But if it's played as a desktop or a web app, a Navigation Rail will show on the left side and at the same time, a Navigation Bar will show as an example but will not have any functionality. + +Users can see both layouts on one device by running a desktop app and adjusting the screen width. + +## Color Screen +With Material 3, we have added support for generating a full color scheme from a single seed color. The Color Screen shows users all of the colors in light and dark color palettes that are generated from the currently selected color. + +## Typography Screen +The Typography Screen displays the text styles used in for the default TextTheme. + +## Elevation Screen +The Elevation screen shows different ways of elevation with a new supported feature "surfaceTintColor" in the Material library. diff --git a/material_3_demo/analysis_options.yaml b/material_3_demo/analysis_options.yaml new file mode 100644 index 0000000..5e2133e --- /dev/null +++ b/material_3_demo/analysis_options.yaml @@ -0,0 +1 @@ +include: ../analysis_options.yaml diff --git a/material_3_demo/android/.gitignore b/material_3_demo/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/material_3_demo/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/material_3_demo/android/app/build.gradle b/material_3_demo/android/app/build.gradle new file mode 100644 index 0000000..70c38f5 --- /dev/null +++ b/material_3_demo/android/app/build.gradle @@ -0,0 +1,71 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.material_3_demo" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/material_3_demo/android/app/src/debug/AndroidManifest.xml b/material_3_demo/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..2773b0b --- /dev/null +++ b/material_3_demo/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/material_3_demo/android/app/src/main/AndroidManifest.xml b/material_3_demo/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a863694 --- /dev/null +++ b/material_3_demo/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/material_3_demo/android/app/src/main/kotlin/com/example/material_3_demo/MainActivity.kt b/material_3_demo/android/app/src/main/kotlin/com/example/material_3_demo/MainActivity.kt new file mode 100644 index 0000000..738e295 --- /dev/null +++ b/material_3_demo/android/app/src/main/kotlin/com/example/material_3_demo/MainActivity.kt @@ -0,0 +1,14 @@ +package com.example.material_3_demo + +import io.flutter.embedding.android.FlutterActivity +import android.os.Bundle +import androidx.core.view.WindowCompat + +class MainActivity: FlutterActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + WindowCompat.setDecorFitsSystemWindows(window, false) + } + +} diff --git a/material_3_demo/android/app/src/main/res/drawable-v21/launch_background.xml b/material_3_demo/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/material_3_demo/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/material_3_demo/android/app/src/main/res/drawable/launch_background.xml b/material_3_demo/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/material_3_demo/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/material_3_demo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/material_3_demo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/material_3_demo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/material_3_demo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/material_3_demo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/material_3_demo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/material_3_demo/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/material_3_demo/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/material_3_demo/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/material_3_demo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/material_3_demo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/material_3_demo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/material_3_demo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/material_3_demo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/material_3_demo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/material_3_demo/android/app/src/main/res/values-night/styles.xml b/material_3_demo/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/material_3_demo/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/material_3_demo/android/app/src/main/res/values/styles.xml b/material_3_demo/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..e0ae24b --- /dev/null +++ b/material_3_demo/android/app/src/main/res/values/styles.xml @@ -0,0 +1,32 @@ + + + + + + + diff --git a/material_3_demo/android/app/src/profile/AndroidManifest.xml b/material_3_demo/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..2773b0b --- /dev/null +++ b/material_3_demo/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/material_3_demo/android/build.gradle b/material_3_demo/android/build.gradle new file mode 100644 index 0000000..f7eb7f6 --- /dev/null +++ b/material_3_demo/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.3.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/material_3_demo/android/gradle.properties b/material_3_demo/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/material_3_demo/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/material_3_demo/android/gradle/wrapper/gradle-wrapper.properties b/material_3_demo/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/material_3_demo/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/material_3_demo/android/settings.gradle b/material_3_demo/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/material_3_demo/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/material_3_demo/integration_test/integration_test.dart b/material_3_demo/integration_test/integration_test.dart new file mode 100644 index 0000000..a8d59c4 --- /dev/null +++ b/material_3_demo/integration_test/integration_test.dart @@ -0,0 +1,15 @@ +// Copyright 2021 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. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:material_3_demo/main.dart' as app; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('verify app can run', (tester) async { + app.main(); + }); +} diff --git a/material_3_demo/ios/.gitignore b/material_3_demo/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/material_3_demo/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/material_3_demo/ios/Flutter/AppFrameworkInfo.plist b/material_3_demo/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/material_3_demo/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/material_3_demo/ios/Flutter/Debug.xcconfig b/material_3_demo/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/material_3_demo/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/material_3_demo/ios/Flutter/Release.xcconfig b/material_3_demo/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/material_3_demo/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/material_3_demo/ios/Podfile b/material_3_demo/ios/Podfile new file mode 100644 index 0000000..fdcc671 --- /dev/null +++ b/material_3_demo/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '11.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/material_3_demo/ios/Runner.xcodeproj/project.pbxproj b/material_3_demo/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..85f86f7 --- /dev/null +++ b/material_3_demo/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,616 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807E294A63A400263BE5 /* Frameworks */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = TC87DMJLQP; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.material3Demo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.material3Demo.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.material3Demo.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.material3Demo.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = TC87DMJLQP; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.material3Demo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = TC87DMJLQP; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.material3Demo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/material_3_demo/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/material_3_demo/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/material_3_demo/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/material_3_demo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/material_3_demo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/material_3_demo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/material_3_demo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/material_3_demo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/material_3_demo/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/material_3_demo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/material_3_demo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..e42adcb --- /dev/null +++ b/material_3_demo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/material_3_demo/ios/Runner.xcworkspace/contents.xcworkspacedata b/material_3_demo/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/material_3_demo/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/material_3_demo/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/material_3_demo/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/material_3_demo/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/material_3_demo/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/material_3_demo/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/material_3_demo/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/material_3_demo/ios/Runner/AppDelegate.swift b/material_3_demo/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/material_3_demo/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/material_3_demo/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/material_3_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/material_3_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/material_3_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/material_3_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/material_3_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/material_3_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/material_3_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/material_3_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/material_3_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/material_3_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/material_3_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/material_3_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/material_3_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/material_3_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/material_3_demo/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/material_3_demo/ios/Runner/Base.lproj/LaunchScreen.storyboard b/material_3_demo/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/material_3_demo/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/material_3_demo/ios/Runner/Base.lproj/Main.storyboard b/material_3_demo/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/material_3_demo/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/material_3_demo/ios/Runner/Info.plist b/material_3_demo/ios/Runner/Info.plist new file mode 100644 index 0000000..45576ea --- /dev/null +++ b/material_3_demo/ios/Runner/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Material 3 Demo + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + material_3_demo + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/material_3_demo/ios/Runner/Runner-Bridging-Header.h b/material_3_demo/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/material_3_demo/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/material_3_demo/ios/RunnerTests/RunnerTests.swift b/material_3_demo/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/material_3_demo/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/material_3_demo/lib/main.dart b/material_3_demo/lib/main.dart new file mode 100644 index 0000000..07c5b86 --- /dev/null +++ b/material_3_demo/lib/main.dart @@ -0,0 +1,107 @@ +// Copyright 2021 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. + +import 'package:flutter/material.dart'; + +import 'src/constants.dart'; +import 'src/home.dart'; + +void main() async { + runApp(const App()); +} + +class App extends StatefulWidget { + const App({super.key}); + + @override + State createState() => _AppState(); +} + +class _AppState extends State { + bool _useMaterial3 = true; + ThemeMode _themeMode = ThemeMode.system; + ColorSeed _colorSelected = ColorSeed.baseColor; + ColorImageProvider _imageSelected = ColorImageProvider.leaves; + ColorScheme? _imageColorScheme = const ColorScheme.light(); + ColorSelectionMethod _colorSelectionMethod = ColorSelectionMethod.colorSeed; + + bool get _useLightMode => switch (_themeMode) { + ThemeMode.system => + View.of(context).platformDispatcher.platformBrightness == + Brightness.light, + ThemeMode.light => true, + ThemeMode.dark => false, + }; + + void _handleBrightnessChange(bool useLightMode) { + setState(() { + _themeMode = useLightMode ? ThemeMode.light : ThemeMode.dark; + }); + } + + void _handleMaterialVersionChange() { + setState(() { + _useMaterial3 = !_useMaterial3; + }); + } + + void _handleColorSelect(int value) { + setState(() { + _colorSelectionMethod = ColorSelectionMethod.colorSeed; + _colorSelected = ColorSeed.values[value]; + }); + } + + void _handleImageSelect(int value) { + final String url = ColorImageProvider.values[value].url; + ColorScheme.fromImageProvider(provider: NetworkImage(url)).then(( + newScheme, + ) { + setState(() { + _colorSelectionMethod = ColorSelectionMethod.image; + _imageSelected = ColorImageProvider.values[value]; + _imageColorScheme = newScheme; + }); + }); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + title: 'Material 3', + themeMode: _themeMode, + theme: ThemeData( + colorSchemeSeed: + _colorSelectionMethod == ColorSelectionMethod.colorSeed + ? _colorSelected.color + : null, + colorScheme: _colorSelectionMethod == ColorSelectionMethod.image + ? _imageColorScheme + : null, + useMaterial3: _useMaterial3, + brightness: Brightness.light, + ), + darkTheme: ThemeData( + colorSchemeSeed: + _colorSelectionMethod == ColorSelectionMethod.colorSeed + ? _colorSelected.color + : _imageColorScheme!.primary, + useMaterial3: _useMaterial3, + brightness: Brightness.dark, + ), + home: Home( + useLightMode: _useLightMode, + useMaterial3: _useMaterial3, + colorSelected: _colorSelected, + imageSelected: _imageSelected, + handleBrightnessChange: _handleBrightnessChange, + handleMaterialVersionChange: _handleMaterialVersionChange, + handleColorSelect: _handleColorSelect, + handleImageSelect: _handleImageSelect, + colorSelectionMethod: _colorSelectionMethod, + ), + ); + } +} diff --git a/material_3_demo/lib/src/animations.dart b/material_3_demo/lib/src/animations.dart new file mode 100644 index 0000000..54559b8 --- /dev/null +++ b/material_3_demo/lib/src/animations.dart @@ -0,0 +1,39 @@ +// Copyright 2021 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. + +import 'package:flutter/material.dart'; + +class SizeAnimation extends CurvedAnimation { + SizeAnimation(Animation parent) + : super( + parent: parent, + curve: const Interval( + 0.2, + 0.8, + curve: Curves.easeInOutCubicEmphasized, + ), + reverseCurve: Interval( + 0, + 0.2, + curve: Curves.easeInOutCubicEmphasized.flipped, + ), + ); +} + +class OffsetAnimation extends CurvedAnimation { + OffsetAnimation(Animation parent) + : super( + parent: parent, + curve: const Interval( + 0.4, + 1.0, + curve: Curves.easeInOutCubicEmphasized, + ), + reverseCurve: Interval( + 0, + 0.2, + curve: Curves.easeInOutCubicEmphasized.flipped, + ), + ); +} diff --git a/material_3_demo/lib/src/bar_transition.dart b/material_3_demo/lib/src/bar_transition.dart new file mode 100644 index 0000000..5a04122 --- /dev/null +++ b/material_3_demo/lib/src/bar_transition.dart @@ -0,0 +1,59 @@ +// Copyright 2021 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. +import 'package:flutter/widgets.dart'; + +import 'animations.dart'; + +class BarTransition extends StatefulWidget { + const BarTransition({ + super.key, + required this.animation, + required this.backgroundColor, + required this.child, + }); + + final Animation animation; + final Color backgroundColor; + final Widget child; + + @override + State createState() => _BarTransition(); +} + +class _BarTransition extends State { + late final Animation offsetAnimation; + late final Animation heightAnimation; + + @override + void initState() { + super.initState(); + + offsetAnimation = Tween( + begin: const Offset(0, 1), + end: Offset.zero, + ).animate(OffsetAnimation(widget.animation)); + + heightAnimation = Tween( + begin: 0, + end: 1, + ).animate(SizeAnimation(widget.animation)); + } + + @override + Widget build(BuildContext context) { + return ClipRect( + child: DecoratedBox( + decoration: BoxDecoration(color: widget.backgroundColor), + child: Align( + alignment: Alignment.topLeft, + heightFactor: heightAnimation.value, + child: FractionalTranslation( + translation: offsetAnimation.value, + child: widget.child, + ), + ), + ), + ); + } +} diff --git a/material_3_demo/lib/src/buttons.dart b/material_3_demo/lib/src/buttons.dart new file mode 100644 index 0000000..7ec5153 --- /dev/null +++ b/material_3_demo/lib/src/buttons.dart @@ -0,0 +1,175 @@ +// Copyright 2021 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. + +import 'package:flutter/material.dart'; + +import 'constants.dart'; + +class BrightnessButton extends StatelessWidget { + const BrightnessButton({ + super.key, + required this.handleBrightnessChange, + this.showTooltipBelow = true, + }); + + final void Function(bool useLightMode) handleBrightnessChange; + final bool showTooltipBelow; + + @override + Widget build(BuildContext context) { + final isBright = Theme.of(context).brightness == Brightness.light; + return Tooltip( + preferBelow: showTooltipBelow, + message: 'Toggle brightness', + child: IconButton( + icon: isBright + ? const Icon(Icons.dark_mode_outlined) + : const Icon(Icons.light_mode_outlined), + onPressed: () => handleBrightnessChange(!isBright), + ), + ); + } +} + +class Material3Button extends StatelessWidget { + const Material3Button({ + super.key, + required this.handleMaterialVersionChange, + this.showTooltipBelow = true, + }); + + final void Function() handleMaterialVersionChange; + final bool showTooltipBelow; + + @override + Widget build(BuildContext context) { + final useMaterial3 = Theme.of(context).useMaterial3; + return Tooltip( + preferBelow: showTooltipBelow, + message: 'Switch to Material ${useMaterial3 ? 2 : 3}', + child: IconButton( + icon: useMaterial3 + ? const Icon(Icons.filter_2) + : const Icon(Icons.filter_3), + onPressed: handleMaterialVersionChange, + ), + ); + } +} + +class ColorSeedButton extends StatelessWidget { + const ColorSeedButton({ + super.key, + required this.handleColorSelect, + required this.colorSelected, + required this.colorSelectionMethod, + }); + + final void Function(int) handleColorSelect; + final ColorSeed colorSelected; + final ColorSelectionMethod colorSelectionMethod; + + @override + Widget build(BuildContext context) { + return PopupMenuButton( + icon: const Icon(Icons.palette_outlined), + tooltip: 'Select a seed color', + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + itemBuilder: (context) { + return List.generate(ColorSeed.values.length, (index) { + ColorSeed currentColor = ColorSeed.values[index]; + + return PopupMenuItem( + value: index, + enabled: + currentColor != colorSelected || + colorSelectionMethod != ColorSelectionMethod.colorSeed, + child: Wrap( + children: [ + Padding( + padding: const EdgeInsets.only(left: 10), + child: Icon( + currentColor == colorSelected && + colorSelectionMethod != ColorSelectionMethod.image + ? Icons.color_lens + : Icons.color_lens_outlined, + color: currentColor.color, + ), + ), + Padding( + padding: const EdgeInsets.only(left: 20), + child: Text(currentColor.label), + ), + ], + ), + ); + }); + }, + onSelected: handleColorSelect, + ); + } +} + +class ColorImageButton extends StatelessWidget { + const ColorImageButton({ + super.key, + required this.handleImageSelect, + required this.imageSelected, + required this.colorSelectionMethod, + }); + + final void Function(int) handleImageSelect; + final ColorImageProvider imageSelected; + final ColorSelectionMethod colorSelectionMethod; + + @override + Widget build(BuildContext context) { + return PopupMenuButton( + icon: const Icon(Icons.image_outlined), + tooltip: 'Select a color extraction image', + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + itemBuilder: (context) { + return List.generate(ColorImageProvider.values.length, (index) { + final currentImageProvider = ColorImageProvider.values[index]; + + return PopupMenuItem( + value: index, + enabled: + currentImageProvider != imageSelected || + colorSelectionMethod != ColorSelectionMethod.image, + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(left: 10), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 48), + child: Padding( + padding: const EdgeInsets.all(4.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: Image( + image: NetworkImage(currentImageProvider.url), + ), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 20), + child: Text(currentImageProvider.label), + ), + ], + ), + ); + }); + }, + onSelected: handleImageSelect, + ); + } +} diff --git a/material_3_demo/lib/src/color_box.dart b/material_3_demo/lib/src/color_box.dart new file mode 100644 index 0000000..e5032d8 --- /dev/null +++ b/material_3_demo/lib/src/color_box.dart @@ -0,0 +1,98 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class ColorBox extends StatefulWidget { + const ColorBox({ + super.key, + required this.label, + required this.tone, + required this.color, + required this.onColor, + required this.height, + required this.width, + this.displayPaletteInfo = false, + }); + + final String label; + final String tone; + final Color color, onColor; + final double height, width; + final bool displayPaletteInfo; + + @override + State createState() => _ColorBoxState(); +} + +class _ColorBoxState extends State { + bool hovered = false; + + @override + Widget build(BuildContext context) { + final fonts = Theme.of(context).textTheme; + return MouseRegion( + onEnter: (_) { + if (mounted) setState(() => hovered = true); + }, + onExit: (_) { + if (mounted) setState(() => hovered = false); + }, + child: Container( + color: widget.color, + height: widget.height, + width: widget.width, + child: DefaultTextStyle( + style: fonts.labelSmall!.copyWith(color: widget.onColor), + child: Stack( + children: [ + Positioned(top: 10, left: 10, child: Text(widget.label)), + Positioned( + bottom: 10, + right: 10, + child: Text(widget.displayPaletteInfo ? widget.tone : ''), + ), + if (hovered) + Positioned( + top: 0, + right: 0, + child: IconButton( + padding: EdgeInsets.zero, + color: widget.onColor, + tooltip: 'Copy hex color', + icon: const Icon(Icons.copy, size: 24), + onPressed: () async { + final messenger = ScaffoldMessenger.of(context); + // Copy color as hex to clipboard + final c = widget.color; + + final hex = + '#${_colorChannelToHex(c.r)}' + '${_colorChannelToHex(c.g)}' + '${_colorChannelToHex(c.b)}'; + + final data = ClipboardData(text: hex); + await Clipboard.setData(data); + messenger.hideCurrentSnackBar(); + messenger.showSnackBar( + SnackBar( + content: Text('Copied $hex to clipboard'), + ), + ); + }, + ), + ), + ], + ), + ), + ), + ); + } +} + +String _colorChannelToHex(double value) { + final intVal = (value * 255).round(); + return intVal.toRadixString(16).padLeft(2, '0'); +} diff --git a/material_3_demo/lib/src/color_palettes_screen.dart b/material_3_demo/lib/src/color_palettes_screen.dart new file mode 100644 index 0000000..933beef --- /dev/null +++ b/material_3_demo/lib/src/color_palettes_screen.dart @@ -0,0 +1,464 @@ +// Copyright 2021 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. + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import 'scheme.dart'; + +const Widget divider = SizedBox(height: 10); + +// If screen content width is greater or equal to this value, the light and dark +// color schemes will be displayed in a column. Otherwise, they will +// be displayed in a row. +const double narrowScreenWidthThreshold = 500; + +class ColorPalettesScreen extends StatelessWidget { + const ColorPalettesScreen({super.key}); + + @override + Widget build(BuildContext context) { + Color selectedColor = Theme.of(context).primaryColor; + ThemeData lightTheme = ThemeData( + colorSchemeSeed: selectedColor, + brightness: Brightness.light, + ); + ThemeData darkTheme = ThemeData( + colorSchemeSeed: selectedColor, + brightness: Brightness.dark, + ); + + Widget schemeLabel(String brightness) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 15), + child: Text( + brightness, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ); + } + + Widget schemeView(ThemeData theme) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: ColorSchemeView(colorScheme: theme.colorScheme), + ); + } + + Widget dynamicColorNotice() => RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: Theme.of(context).textTheme.bodySmall, + children: [ + const TextSpan( + text: + 'To create color schemes based on a ' + 'platform\'s implementation of dynamic color, ' + 'use the ', + ), + TextSpan( + text: 'dynamic_color', + style: const TextStyle(decoration: TextDecoration.underline), + recognizer: TapGestureRecognizer() + ..onTap = () async { + final url = Uri.parse( + 'https://pub.dev/packages/dynamic_color', + ); + if (!await launchUrl(url)) { + throw Exception('Could not launch $url'); + } + }, + ), + const TextSpan(text: ' package.'), + ], + ), + ); + + return Expanded( + child: LayoutBuilder( + builder: (context, constraints) { + if (constraints.maxWidth < narrowScreenWidthThreshold) { + return SingleChildScrollView( + child: Column( + children: [ + dynamicColorNotice(), + divider, + schemeLabel('Light ColorScheme'), + schemeView(lightTheme), + divider, + divider, + schemeLabel('Dark ColorScheme'), + schemeView(darkTheme), + ], + ), + ); + } else { + Color seed = Theme.of(context).colorScheme.primary; + ColorScheme lightScheme = ColorScheme.fromSeed( + seedColor: seed, + brightness: Brightness.light, + ); + ColorScheme darkScheme = ColorScheme.fromSeed( + seedColor: seed, + brightness: Brightness.dark, + ); + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + children: [ + SchemePreview( + label: "Light ColorScheme", + scheme: lightScheme, + brightness: Brightness.light, + contrast: 1.0, + colorMatch: false, + ), + const SizedBox(height: 16), + SchemePreview( + label: "Dark ColorScheme", + scheme: darkScheme, + brightness: Brightness.dark, + contrast: 1.0, + colorMatch: false, + ), + const SizedBox(height: 16), + ], + ), + ), + ); + } + }, + ), + ); + } +} + +class ColorSchemeView extends StatelessWidget { + const ColorSchemeView({super.key, required this.colorScheme}); + + final ColorScheme colorScheme; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + ColorGroup( + children: [ + ColorChip( + label: 'primary', + color: colorScheme.primary, + onColor: colorScheme.onPrimary, + ), + ColorChip( + label: 'onPrimary', + color: colorScheme.onPrimary, + onColor: colorScheme.primary, + ), + ColorChip( + label: 'primaryContainer', + color: colorScheme.primaryContainer, + onColor: colorScheme.onPrimaryContainer, + ), + ColorChip( + label: 'onPrimaryContainer', + color: colorScheme.onPrimaryContainer, + onColor: colorScheme.primaryContainer, + ), + ], + ), + divider, + ColorGroup( + children: [ + ColorChip( + label: 'primaryFixed', + color: colorScheme.primaryFixed, + onColor: colorScheme.onPrimaryFixed, + ), + ColorChip( + label: 'onPrimaryFixed', + color: colorScheme.onPrimaryFixed, + onColor: colorScheme.primaryFixed, + ), + ColorChip( + label: 'primaryFixedDim', + color: colorScheme.primaryFixedDim, + onColor: colorScheme.onPrimaryFixedVariant, + ), + ColorChip( + label: 'onPrimaryFixedVariant', + color: colorScheme.onPrimaryFixedVariant, + onColor: colorScheme.primaryFixedDim, + ), + ], + ), + divider, + ColorGroup( + children: [ + ColorChip( + label: 'secondary', + color: colorScheme.secondary, + onColor: colorScheme.onSecondary, + ), + ColorChip( + label: 'onSecondary', + color: colorScheme.onSecondary, + onColor: colorScheme.secondary, + ), + ColorChip( + label: 'secondaryContainer', + color: colorScheme.secondaryContainer, + onColor: colorScheme.onSecondaryContainer, + ), + ColorChip( + label: 'onSecondaryContainer', + color: colorScheme.onSecondaryContainer, + onColor: colorScheme.secondaryContainer, + ), + ], + ), + divider, + ColorGroup( + children: [ + ColorChip( + label: 'secondaryFixed', + color: colorScheme.secondaryFixed, + onColor: colorScheme.onSecondaryFixed, + ), + ColorChip( + label: 'onSecondaryFixed', + color: colorScheme.onSecondaryFixed, + onColor: colorScheme.secondaryFixed, + ), + ColorChip( + label: 'secondaryFixedDim', + color: colorScheme.secondaryFixedDim, + onColor: colorScheme.onSecondaryFixedVariant, + ), + ColorChip( + label: 'onSecondaryFixedVariant', + color: colorScheme.onSecondaryFixedVariant, + onColor: colorScheme.secondaryFixedDim, + ), + ], + ), + divider, + ColorGroup( + children: [ + ColorChip( + label: 'tertiary', + color: colorScheme.tertiary, + onColor: colorScheme.onTertiary, + ), + ColorChip( + label: 'onTertiary', + color: colorScheme.onTertiary, + onColor: colorScheme.tertiary, + ), + ColorChip( + label: 'tertiaryContainer', + color: colorScheme.tertiaryContainer, + onColor: colorScheme.onTertiaryContainer, + ), + ColorChip( + label: 'onTertiaryContainer', + color: colorScheme.onTertiaryContainer, + onColor: colorScheme.tertiaryContainer, + ), + ], + ), + divider, + ColorGroup( + children: [ + ColorChip( + label: 'tertiaryFixed', + color: colorScheme.tertiaryFixed, + onColor: colorScheme.onTertiaryFixed, + ), + ColorChip( + label: 'onTertiaryFixed', + color: colorScheme.onTertiaryFixed, + onColor: colorScheme.tertiaryFixed, + ), + ColorChip( + label: 'tertiaryFixedDim', + color: colorScheme.tertiaryFixedDim, + onColor: colorScheme.onTertiaryFixedVariant, + ), + ColorChip( + label: 'onTertiaryFixedVariant', + color: colorScheme.onTertiaryFixedVariant, + onColor: colorScheme.tertiaryFixedDim, + ), + ], + ), + divider, + ColorGroup( + children: [ + ColorChip( + label: 'error', + color: colorScheme.error, + onColor: colorScheme.onError, + ), + ColorChip( + label: 'onError', + color: colorScheme.onError, + onColor: colorScheme.error, + ), + ColorChip( + label: 'errorContainer', + color: colorScheme.errorContainer, + onColor: colorScheme.onErrorContainer, + ), + ColorChip( + label: 'onErrorContainer', + color: colorScheme.onErrorContainer, + onColor: colorScheme.errorContainer, + ), + ], + ), + divider, + ColorGroup( + children: [ + ColorChip( + label: 'surfaceDim', + color: colorScheme.surfaceDim, + onColor: colorScheme.onSurface, + ), + ColorChip( + label: 'surface', + color: colorScheme.surface, + onColor: colorScheme.onSurface, + ), + ColorChip( + label: 'surfaceBright', + color: colorScheme.surfaceBright, + onColor: colorScheme.onSurface, + ), + ColorChip( + label: 'surfaceContainerLowest', + color: colorScheme.surfaceContainerLowest, + onColor: colorScheme.onSurface, + ), + ColorChip( + label: 'surfaceContainerLow', + color: colorScheme.surfaceContainerLow, + onColor: colorScheme.onSurface, + ), + ColorChip( + label: 'surfaceContainer', + color: colorScheme.surfaceContainer, + onColor: colorScheme.onSurface, + ), + ColorChip( + label: 'surfaceContainerHigh', + color: colorScheme.surfaceContainerHigh, + onColor: colorScheme.onSurface, + ), + ColorChip( + label: 'surfaceContainerHighest', + color: colorScheme.surfaceContainerHighest, + onColor: colorScheme.onSurface, + ), + ColorChip( + label: 'onSurface', + color: colorScheme.onSurface, + onColor: colorScheme.surface, + ), + ColorChip( + label: 'onSurfaceVariant', + color: colorScheme.onSurfaceVariant, + onColor: colorScheme.surfaceContainerHighest, + ), + ], + ), + divider, + ColorGroup( + children: [ + ColorChip( + label: 'outline', + color: colorScheme.outline, + onColor: null, + ), + ColorChip( + label: 'shadow', + color: colorScheme.shadow, + onColor: null, + ), + ColorChip( + label: 'inverseSurface', + color: colorScheme.inverseSurface, + onColor: colorScheme.onInverseSurface, + ), + ColorChip( + label: 'onInverseSurface', + color: colorScheme.onInverseSurface, + onColor: colorScheme.inverseSurface, + ), + ColorChip( + label: 'inversePrimary', + color: colorScheme.inversePrimary, + onColor: colorScheme.primary, + ), + ], + ), + ], + ); + } +} + +class ColorGroup extends StatelessWidget { + const ColorGroup({super.key, required this.children}); + + final List children; + + @override + Widget build(BuildContext context) { + return RepaintBoundary( + child: Card( + clipBehavior: Clip.antiAlias, + child: Column(children: children), + ), + ); + } +} + +class ColorChip extends StatelessWidget { + const ColorChip({ + super.key, + required this.color, + required this.label, + this.onColor, + }); + + final Color color; + final Color? onColor; + final String label; + + static Color contrastColor(Color color) => + switch (ThemeData.estimateBrightnessForColor(color)) { + Brightness.dark => Colors.white, + Brightness.light => Colors.black, + }; + + @override + Widget build(BuildContext context) { + final Color labelColor = onColor ?? contrastColor(color); + + return Container( + color: color, + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Expanded( + child: Text(label, style: TextStyle(color: labelColor)), + ), + ], + ), + ), + ); + } +} diff --git a/material_3_demo/lib/src/component_screen.dart b/material_3_demo/lib/src/component_screen.dart new file mode 100644 index 0000000..6026f10 --- /dev/null +++ b/material_3_demo/lib/src/component_screen.dart @@ -0,0 +1,2676 @@ +// Copyright 2021 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. + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; + +const rowDivider = SizedBox(width: 20); +const colDivider = SizedBox(height: 10); +const tinySpacing = 3.0; +const smallSpacing = 10.0; +const double cardWidth = 115; +const double widthConstraint = 450; + +class FirstComponentList extends StatelessWidget { + const FirstComponentList({ + super.key, + required this.showNavBottomBar, + required this.scaffoldKey, + required this.showSecondList, + }); + + final bool showNavBottomBar; + final GlobalKey scaffoldKey; + final bool showSecondList; + + @override + Widget build(BuildContext context) { + List children = [ + const Actions(), + colDivider, + const Communication(), + colDivider, + const Containment(), + if (!showSecondList) ...[ + colDivider, + Navigation(scaffoldKey: scaffoldKey), + colDivider, + const Selection(), + colDivider, + const TextInputs(), + ], + ]; + List heights = List.filled(children.length, null); + + // Fully traverse this list before moving on. + return FocusTraversalGroup( + child: CustomScrollView( + slivers: [ + SliverPadding( + padding: showSecondList + ? const EdgeInsetsDirectional.only(end: smallSpacing) + : EdgeInsets.zero, + sliver: SliverList( + delegate: BuildSlivers( + heights: heights, + builder: (context, index) { + return _CacheHeight( + heights: heights, + index: index, + child: children[index], + ); + }, + ), + ), + ), + ], + ), + ); + } +} + +class SecondComponentList extends StatelessWidget { + const SecondComponentList({super.key, required this.scaffoldKey}); + + final GlobalKey scaffoldKey; + + @override + Widget build(BuildContext context) { + List children = [ + Navigation(scaffoldKey: scaffoldKey), + colDivider, + const Selection(), + colDivider, + const TextInputs(), + ]; + List heights = List.filled(children.length, null); + + // Fully traverse this list before moving on. + return FocusTraversalGroup( + child: CustomScrollView( + slivers: [ + SliverPadding( + padding: const EdgeInsetsDirectional.only(end: smallSpacing), + sliver: SliverList( + delegate: BuildSlivers( + heights: heights, + builder: (context, index) { + return _CacheHeight( + heights: heights, + index: index, + child: children[index], + ); + }, + ), + ), + ), + ], + ), + ); + } +} + +// If the content of a CustomScrollView does not change, then it's +// safe to cache the heights of each item as they are laid out. The +// sum of the cached heights are returned by an override of +// `SliverChildDelegate.estimateMaxScrollOffset`. The default version +// of this method bases its estimate on the average height of the +// visible items. The override ensures that the scrollbar thumb's +// size, which depends on the max scroll offset, will shrink smoothly +// as the contents of the list are exposed for the first time, and +// then remain fixed. +class _CacheHeight extends SingleChildRenderObjectWidget { + const _CacheHeight({ + super.child, + required this.heights, + required this.index, + }); + + final List heights; + final int index; + + @override + RenderObject createRenderObject(BuildContext context) { + return _RenderCacheHeight(heights: heights, index: index); + } + + @override + void updateRenderObject( + BuildContext context, + _RenderCacheHeight renderObject, + ) { + renderObject + ..heights = heights + ..index = index; + } +} + +class _RenderCacheHeight extends RenderProxyBox { + _RenderCacheHeight({required List heights, required int index}) + : _heights = heights, + _index = index, + super(); + + List _heights; + List get heights => _heights; + set heights(List value) { + if (value == _heights) { + return; + } + _heights = value; + markNeedsLayout(); + } + + int _index; + int get index => _index; + set index(int value) { + if (value == index) { + return; + } + _index = value; + markNeedsLayout(); + } + + @override + void performLayout() { + super.performLayout(); + heights[index] = size.height; + } +} + +// The heights information is used to override the `estimateMaxScrollOffset` and +// provide a more accurate estimation for the max scroll offset. +class BuildSlivers extends SliverChildBuilderDelegate { + BuildSlivers({ + required NullableIndexedWidgetBuilder builder, + required this.heights, + }) : super(builder, childCount: heights.length); + + final List heights; + + @override + double? estimateMaxScrollOffset( + int firstIndex, + int lastIndex, + double leadingScrollOffset, + double trailingScrollOffset, + ) { + return heights.reduce((sum, height) => (sum ?? 0) + (height ?? 0))!; + } +} + +class Actions extends StatelessWidget { + const Actions({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentGroupDecoration( + label: 'Actions', + children: [ + Buttons(), + FloatingActionButtons(), + IconToggleButtons(), + SegmentedButtons(), + ], + ); + } +} + +class Communication extends StatelessWidget { + const Communication({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentGroupDecoration( + label: 'Communication', + children: [ + NavigationBars( + selectedIndex: 1, + isExampleBar: true, + isBadgeExample: true, + ), + ProgressIndicators(), + SnackBarSection(), + ], + ); + } +} + +class Containment extends StatelessWidget { + const Containment({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentGroupDecoration( + label: 'Containment', + children: [ + BottomSheetSection(), + Cards(), + Carousels(), + Dialogs(), + Dividers(), + ], + ); + } +} + +class Navigation extends StatelessWidget { + const Navigation({super.key, required this.scaffoldKey}); + + final GlobalKey scaffoldKey; + + @override + Widget build(BuildContext context) { + return ComponentGroupDecoration( + label: 'Navigation', + children: [ + const BottomAppBars(), + const NavigationBars(selectedIndex: 0, isExampleBar: true), + NavigationDrawers(scaffoldKey: scaffoldKey), + const NavigationRails(), + const Tabs(), + const SearchAnchors(), + const TopAppBars(), + ], + ); + } +} + +class Selection extends StatelessWidget { + const Selection({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentGroupDecoration( + label: 'Selection', + children: [ + Checkboxes(), + Chips(), + DatePicker(), + TimePicker(), + Menus(), + Radios(), + Sliders(), + Switches(), + ], + ); + } +} + +class TextInputs extends StatelessWidget { + const TextInputs({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentGroupDecoration( + label: 'Text inputs', + children: [TextFields()], + ); + } +} + +class Buttons extends StatefulWidget { + const Buttons({super.key}); + + @override + State createState() => _ButtonsState(); +} + +class _ButtonsState extends State { + @override + Widget build(BuildContext context) { + return const ComponentDecoration( + label: 'Common buttons', + tooltipMessage: + 'Use ElevatedButton, FilledButton, FilledButton.tonal, OutlinedButton, or TextButton', + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ButtonsWithoutIcon(isDisabled: false), + ButtonsWithIcon(), + ButtonsWithoutIcon(isDisabled: true), + ], + ), + ), + ); + } +} + +class ButtonsWithoutIcon extends StatelessWidget { + final bool isDisabled; + + const ButtonsWithoutIcon({super.key, required this.isDisabled}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 5.0), + child: IntrinsicWidth( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ElevatedButton( + onPressed: isDisabled ? null : () {}, + child: const Text('Elevated'), + ), + colDivider, + FilledButton( + onPressed: isDisabled ? null : () {}, + child: const Text('Filled'), + ), + colDivider, + FilledButton.tonal( + onPressed: isDisabled ? null : () {}, + child: const Text('Filled tonal'), + ), + colDivider, + OutlinedButton( + onPressed: isDisabled ? null : () {}, + child: const Text('Outlined'), + ), + colDivider, + TextButton( + onPressed: isDisabled ? null : () {}, + child: const Text('Text'), + ), + ], + ), + ), + ); + } +} + +class ButtonsWithIcon extends StatelessWidget { + const ButtonsWithIcon({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: IntrinsicWidth( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ElevatedButton.icon( + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Icon'), + ), + colDivider, + FilledButton.icon( + onPressed: () {}, + label: const Text('Icon'), + icon: const Icon(Icons.add), + ), + colDivider, + FilledButton.tonalIcon( + onPressed: () {}, + label: const Text('Icon'), + icon: const Icon(Icons.add), + ), + colDivider, + OutlinedButton.icon( + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Icon'), + ), + colDivider, + TextButton.icon( + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Icon'), + ), + ], + ), + ), + ); + } +} + +class FloatingActionButtons extends StatelessWidget { + const FloatingActionButtons({super.key}); + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Floating action buttons', + tooltipMessage: + 'Use FloatingActionButton or FloatingActionButton.extended', + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + runSpacing: smallSpacing, + spacing: smallSpacing, + children: [ + FloatingActionButton.small( + onPressed: () {}, + tooltip: 'Small', + child: const Icon(Icons.add), + ), + FloatingActionButton.extended( + onPressed: () {}, + tooltip: 'Extended', + icon: const Icon(Icons.add), + label: const Text('Create'), + ), + FloatingActionButton( + onPressed: () {}, + tooltip: 'Standard', + child: const Icon(Icons.add), + ), + FloatingActionButton.large( + onPressed: () {}, + tooltip: 'Large', + child: const Icon(Icons.add), + ), + ], + ), + ); + } +} + +class Cards extends StatelessWidget { + const Cards({super.key}); + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Cards', + tooltipMessage: 'Use Card', + child: Wrap( + alignment: WrapAlignment.spaceEvenly, + children: [ + SizedBox( + width: cardWidth, + child: Card( + child: Container( + padding: const EdgeInsets.fromLTRB(10, 5, 5, 10), + child: Column( + children: [ + Align( + alignment: Alignment.topRight, + child: IconButton( + icon: const Icon(Icons.more_vert), + onPressed: () {}, + ), + ), + const SizedBox(height: 20), + const Align( + alignment: Alignment.bottomLeft, + child: Text('Elevated'), + ), + ], + ), + ), + ), + ), + SizedBox( + width: cardWidth, + child: Card( + color: Theme.of(context).colorScheme.surfaceContainerHighest, + elevation: 0, + child: Container( + padding: const EdgeInsets.fromLTRB(10, 5, 5, 10), + child: Column( + children: [ + Align( + alignment: Alignment.topRight, + child: IconButton( + icon: const Icon(Icons.more_vert), + onPressed: () {}, + ), + ), + const SizedBox(height: 20), + const Align( + alignment: Alignment.bottomLeft, + child: Text('Filled'), + ), + ], + ), + ), + ), + ), + SizedBox( + width: cardWidth, + child: Card( + elevation: 0, + shape: RoundedRectangleBorder( + side: BorderSide( + color: Theme.of(context).colorScheme.outline, + ), + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + child: Container( + padding: const EdgeInsets.fromLTRB(10, 5, 5, 10), + child: Column( + children: [ + Align( + alignment: Alignment.topRight, + child: IconButton( + icon: const Icon(Icons.more_vert), + onPressed: () {}, + ), + ), + const SizedBox(height: 20), + const Align( + alignment: Alignment.bottomLeft, + child: Text('Outlined'), + ), + ], + ), + ), + ), + ), + ], + ), + ); + } +} + +class _ClearButton extends StatelessWidget { + const _ClearButton({required this.controller}); + + final TextEditingController controller; + + @override + Widget build(BuildContext context) => IconButton( + icon: const Icon(Icons.clear), + onPressed: () => controller.clear(), + ); +} + +class TextFields extends StatefulWidget { + const TextFields({super.key}); + + @override + State createState() => _TextFieldsState(); +} + +class _TextFieldsState extends State { + final TextEditingController _controllerFilled = TextEditingController(); + final TextEditingController _controllerOutlined = TextEditingController(); + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Text fields', + tooltipMessage: 'Use TextField with different InputDecoration', + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(smallSpacing), + child: TextField( + controller: _controllerFilled, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search), + suffixIcon: _ClearButton(controller: _controllerFilled), + labelText: 'Filled', + hintText: 'hint text', + helperText: 'supporting text', + filled: true, + ), + ), + ), + Padding( + padding: const EdgeInsets.all(smallSpacing), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: SizedBox( + width: 200, + child: TextField( + maxLength: 10, + maxLengthEnforcement: MaxLengthEnforcement.none, + controller: _controllerFilled, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search), + suffixIcon: _ClearButton( + controller: _controllerFilled, + ), + labelText: 'Filled', + hintText: 'hint text', + helperText: 'supporting text', + filled: true, + errorText: 'error text', + ), + ), + ), + ), + const SizedBox(width: smallSpacing), + Flexible( + child: SizedBox( + width: 200, + child: TextField( + controller: _controllerFilled, + enabled: false, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search), + suffixIcon: _ClearButton( + controller: _controllerFilled, + ), + labelText: 'Disabled', + hintText: 'hint text', + helperText: 'supporting text', + filled: true, + ), + ), + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(smallSpacing), + child: TextField( + controller: _controllerOutlined, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search), + suffixIcon: _ClearButton(controller: _controllerOutlined), + labelText: 'Outlined', + hintText: 'hint text', + helperText: 'supporting text', + border: const OutlineInputBorder(), + ), + ), + ), + Padding( + padding: const EdgeInsets.all(smallSpacing), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: SizedBox( + width: 200, + child: TextField( + controller: _controllerOutlined, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search), + suffixIcon: _ClearButton( + controller: _controllerOutlined, + ), + labelText: 'Outlined', + hintText: 'hint text', + helperText: 'supporting text', + errorText: 'error text', + border: const OutlineInputBorder(), + filled: true, + ), + ), + ), + ), + const SizedBox(width: smallSpacing), + Flexible( + child: SizedBox( + width: 200, + child: TextField( + controller: _controllerOutlined, + enabled: false, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search), + suffixIcon: _ClearButton( + controller: _controllerOutlined, + ), + labelText: 'Disabled', + hintText: 'hint text', + helperText: 'supporting text', + border: const OutlineInputBorder(), + filled: true, + ), + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} + +class Dialogs extends StatefulWidget { + const Dialogs({super.key}); + + @override + State createState() => _DialogsState(); +} + +class _DialogsState extends State { + void openDialog(BuildContext context) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('What is a dialog?'), + content: const Text( + 'A dialog is a type of modal window that appears in front of app content to provide critical information, or prompt for a decision to be made.', + ), + actions: [ + TextButton( + child: const Text('Dismiss'), + onPressed: () => Navigator.of(context).pop(), + ), + FilledButton( + child: const Text('Okay'), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + ); + } + + void openFullscreenDialog(BuildContext context) { + showDialog( + context: context, + builder: (context) => Dialog.fullscreen( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Scaffold( + appBar: AppBar( + title: const Text('Full-screen dialog'), + centerTitle: false, + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: () => Navigator.of(context).pop(), + ), + actions: [ + TextButton( + child: const Text('Close'), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Dialog', + tooltipMessage: + 'Use showDialog with Dialog.fullscreen, AlertDialog, or SimpleDialog', + child: Wrap( + alignment: WrapAlignment.spaceBetween, + children: [ + TextButton( + child: const Text( + 'Show dialog', + style: TextStyle(fontWeight: FontWeight.bold), + ), + onPressed: () => openDialog(context), + ), + TextButton( + child: const Text( + 'Show full-screen dialog', + style: TextStyle(fontWeight: FontWeight.bold), + ), + onPressed: () => openFullscreenDialog(context), + ), + ], + ), + ); + } +} + +class Dividers extends StatelessWidget { + const Dividers({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentDecoration( + label: 'Dividers', + tooltipMessage: 'Use Divider or VerticalDivider', + child: Column(children: [Divider(key: Key('divider'))]), + ); + } +} + +class Switches extends StatelessWidget { + const Switches({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentDecoration( + label: 'Switches', + tooltipMessage: 'Use SwitchListTile or Switch', + child: Column( + children: [ + SwitchRow(isEnabled: true), + SwitchRow(isEnabled: false), + ], + ), + ); + } +} + +class SwitchRow extends StatefulWidget { + const SwitchRow({super.key, required this.isEnabled}); + + final bool isEnabled; + + @override + State createState() => _SwitchRowState(); +} + +class _SwitchRowState extends State { + bool value0 = false; + bool value1 = true; + + final WidgetStateProperty thumbIcon = + WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return const Icon(Icons.check); + } + return const Icon(Icons.close); + }); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Switch( + value: value0, + onChanged: widget.isEnabled + ? (value) { + setState(() { + value0 = value; + }); + } + : null, + ), + Switch( + thumbIcon: thumbIcon, + value: value1, + onChanged: widget.isEnabled + ? (value) { + setState(() { + value1 = value; + }); + } + : null, + ), + ], + ); + } +} + +class Checkboxes extends StatefulWidget { + const Checkboxes({super.key}); + + @override + State createState() => _CheckboxesState(); +} + +class _CheckboxesState extends State { + bool? isChecked0 = true; + bool? isChecked1; + bool? isChecked2 = false; + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Checkboxes', + tooltipMessage: 'Use CheckboxListTile or Checkbox', + child: Column( + children: [ + CheckboxListTile( + tristate: true, + value: isChecked0, + title: const Text('Option 1'), + onChanged: (value) { + setState(() { + isChecked0 = value; + }); + }, + ), + CheckboxListTile( + tristate: true, + value: isChecked1, + title: const Text('Option 2'), + onChanged: (value) { + setState(() { + isChecked1 = value; + }); + }, + ), + CheckboxListTile( + tristate: true, + value: isChecked2, + title: const Text('Option 3'), + onChanged: (value) { + setState(() { + isChecked2 = value; + }); + }, + ), + const CheckboxListTile( + tristate: true, + title: Text('Option 4'), + value: true, + onChanged: null, + ), + ], + ), + ); + } +} + +enum Value { first, second } + +class Radios extends StatefulWidget { + const Radios({super.key}); + + @override + State createState() => _RadiosState(); +} + +enum Options { option1, option2, option3 } + +class _RadiosState extends State { + Options? _selectedOption = Options.option1; + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Radio buttons', + tooltipMessage: 'Use RadioListTile or Radio', + child: RadioGroup( + groupValue: _selectedOption, + onChanged: (value) { + setState(() { + _selectedOption = value; + }); + }, + child: Column( + children: [ + RadioListTile( + title: const Text('Option 1'), + value: Options.option1, + ), + RadioListTile( + title: const Text('Option 2'), + value: Options.option2, + ), + RadioListTile( + title: const Text('Option 3'), + value: Options.option3, + enabled: false, + ), + ], + ), + ), + ); + } +} + +class ProgressIndicators extends StatefulWidget { + const ProgressIndicators({super.key}); + + @override + State createState() => _ProgressIndicatorsState(); +} + +class _ProgressIndicatorsState extends State { + bool playProgressIndicator = false; + + @override + Widget build(BuildContext context) { + final double? progressValue = playProgressIndicator ? null : 0.7; + + return ComponentDecoration( + label: 'Progress indicators', + tooltipMessage: + 'Use CircularProgressIndicator or LinearProgressIndicator', + child: Column( + children: [ + Row( + children: [ + IconButton( + isSelected: playProgressIndicator, + selectedIcon: const Icon(Icons.pause), + icon: const Icon(Icons.play_arrow), + onPressed: () { + setState(() { + playProgressIndicator = !playProgressIndicator; + }); + }, + ), + Expanded( + child: Row( + children: [ + rowDivider, + CircularProgressIndicator(value: progressValue), + rowDivider, + Expanded( + child: LinearProgressIndicator(value: progressValue), + ), + rowDivider, + ], + ), + ), + ], + ), + ], + ), + ); + } +} + +const List appBarDestinations = [ + NavigationDestination( + tooltip: '', + icon: Icon(Icons.widgets_outlined), + label: 'Components', + selectedIcon: Icon(Icons.widgets), + ), + NavigationDestination( + tooltip: '', + icon: Icon(Icons.format_paint_outlined), + label: 'Color', + selectedIcon: Icon(Icons.format_paint), + ), + NavigationDestination( + tooltip: '', + icon: Icon(Icons.text_snippet_outlined), + label: 'Typography', + selectedIcon: Icon(Icons.text_snippet), + ), + NavigationDestination( + tooltip: '', + icon: Icon(Icons.invert_colors_on_outlined), + label: 'Elevation', + selectedIcon: Icon(Icons.opacity), + ), +]; + +const List exampleBarDestinations = [ + NavigationDestination( + tooltip: '', + icon: Icon(Icons.explore_outlined), + label: 'Explore', + selectedIcon: Icon(Icons.explore), + ), + NavigationDestination( + tooltip: '', + icon: Icon(Icons.pets_outlined), + label: 'Pets', + selectedIcon: Icon(Icons.pets), + ), + NavigationDestination( + tooltip: '', + icon: Icon(Icons.account_box_outlined), + label: 'Account', + selectedIcon: Icon(Icons.account_box), + ), +]; + +List barWithBadgeDestinations = [ + NavigationDestination( + tooltip: '', + icon: Badge.count(count: 1000, child: const Icon(Icons.mail_outlined)), + label: 'Mail', + selectedIcon: Badge.count(count: 1000, child: const Icon(Icons.mail)), + ), + const NavigationDestination( + tooltip: '', + icon: Badge(label: Text('10'), child: Icon(Icons.chat_bubble_outline)), + label: 'Chat', + selectedIcon: Badge(label: Text('10'), child: Icon(Icons.chat_bubble)), + ), + const NavigationDestination( + tooltip: '', + icon: Badge(child: Icon(Icons.group_outlined)), + label: 'Rooms', + selectedIcon: Badge(child: Icon(Icons.group_rounded)), + ), + NavigationDestination( + tooltip: '', + icon: Badge.count( + count: 3, + child: const Icon(Icons.videocam_outlined), + ), + label: 'Meet', + selectedIcon: Badge.count(count: 3, child: const Icon(Icons.videocam)), + ), +]; + +class NavigationBars extends StatefulWidget { + const NavigationBars({ + super.key, + this.onSelectItem, + required this.selectedIndex, + required this.isExampleBar, + this.isBadgeExample = false, + }); + + final void Function(int)? onSelectItem; + final int selectedIndex; + final bool isExampleBar; + final bool isBadgeExample; + + @override + State createState() => _NavigationBarsState(); +} + +class _NavigationBarsState extends State { + late int selectedIndex; + + @override + void initState() { + super.initState(); + selectedIndex = widget.selectedIndex; + } + + @override + void didUpdateWidget(covariant NavigationBars oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.selectedIndex != oldWidget.selectedIndex) { + selectedIndex = widget.selectedIndex; + } + } + + @override + Widget build(BuildContext context) { + // App NavigationBar should get first focus. + Widget navigationBar = Focus( + autofocus: !(widget.isExampleBar || widget.isBadgeExample), + child: NavigationBar( + selectedIndex: selectedIndex, + onDestinationSelected: (index) { + setState(() { + selectedIndex = index; + }); + if (!widget.isExampleBar) widget.onSelectItem!(index); + }, + destinations: widget.isExampleBar && widget.isBadgeExample + ? barWithBadgeDestinations + : widget.isExampleBar + ? exampleBarDestinations + : appBarDestinations, + ), + ); + + if (widget.isExampleBar && widget.isBadgeExample) { + navigationBar = ComponentDecoration( + label: 'Badges', + tooltipMessage: 'Use Badge or Badge.count', + child: navigationBar, + ); + } else if (widget.isExampleBar) { + navigationBar = ComponentDecoration( + label: 'Navigation bar', + tooltipMessage: 'Use NavigationBar', + child: navigationBar, + ); + } + + return navigationBar; + } +} + +class IconToggleButtons extends StatefulWidget { + const IconToggleButtons({super.key}); + + @override + State createState() => _IconToggleButtonsState(); +} + +class _IconToggleButtonsState extends State { + bool standardSelected = false; + bool filledSelected = false; + bool tonalSelected = false; + bool outlinedSelected = false; + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Icon buttons', + tooltipMessage: + 'Use IconButton, IconButton.filled, IconButton.filledTonal, and IconButton.outlined', + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Column( + // Standard IconButton + children: [ + IconButton( + isSelected: standardSelected, + icon: const Icon(Icons.settings_outlined), + selectedIcon: const Icon(Icons.settings), + onPressed: () { + setState(() { + standardSelected = !standardSelected; + }); + }, + ), + colDivider, + IconButton( + isSelected: standardSelected, + icon: const Icon(Icons.settings_outlined), + selectedIcon: const Icon(Icons.settings), + onPressed: null, + ), + ], + ), + Column( + children: [ + // Filled IconButton + IconButton.filled( + isSelected: filledSelected, + icon: const Icon(Icons.settings_outlined), + selectedIcon: const Icon(Icons.settings), + onPressed: () { + setState(() { + filledSelected = !filledSelected; + }); + }, + ), + colDivider, + IconButton.filled( + isSelected: filledSelected, + icon: const Icon(Icons.settings_outlined), + selectedIcon: const Icon(Icons.settings), + onPressed: null, + ), + ], + ), + Column( + children: [ + // Filled Tonal IconButton + IconButton.filledTonal( + isSelected: tonalSelected, + icon: const Icon(Icons.settings_outlined), + selectedIcon: const Icon(Icons.settings), + onPressed: () { + setState(() { + tonalSelected = !tonalSelected; + }); + }, + ), + colDivider, + IconButton.filledTonal( + isSelected: tonalSelected, + icon: const Icon(Icons.settings_outlined), + selectedIcon: const Icon(Icons.settings), + onPressed: null, + ), + ], + ), + Column( + children: [ + // Outlined IconButton + IconButton.outlined( + isSelected: outlinedSelected, + icon: const Icon(Icons.settings_outlined), + selectedIcon: const Icon(Icons.settings), + onPressed: () { + setState(() { + outlinedSelected = !outlinedSelected; + }); + }, + ), + colDivider, + IconButton.outlined( + isSelected: outlinedSelected, + icon: const Icon(Icons.settings_outlined), + selectedIcon: const Icon(Icons.settings), + onPressed: null, + ), + ], + ), + ], + ), + ); + } +} + +class Chips extends StatefulWidget { + const Chips({super.key}); + + @override + State createState() => _ChipsState(); +} + +class _ChipsState extends State { + bool isFiltered = true; + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Chips', + tooltipMessage: + 'Use ActionChip, FilterChip, or InputChip. \nActionChip can also be used for suggestion chip', + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Wrap( + spacing: smallSpacing, + runSpacing: smallSpacing, + children: [ + ActionChip( + label: const Text('Assist'), + avatar: const Icon(Icons.event), + onPressed: () {}, + ), + FilterChip( + label: const Text('Filter'), + selected: isFiltered, + onSelected: (selected) { + setState(() => isFiltered = selected); + }, + ), + InputChip( + label: const Text('Input'), + onPressed: () {}, + onDeleted: () {}, + ), + ActionChip( + label: const Text('Suggestion'), + onPressed: () {}, + ), + ], + ), + colDivider, + Wrap( + spacing: smallSpacing, + runSpacing: smallSpacing, + children: [ + const ActionChip( + label: Text('Assist'), + avatar: Icon(Icons.event), + ), + FilterChip( + label: const Text('Filter'), + selected: isFiltered, + onSelected: null, + ), + InputChip( + label: const Text('Input'), + onDeleted: () {}, + isEnabled: false, + ), + const ActionChip(label: Text('Suggestion')), + ], + ), + ], + ), + ); + } +} + +class DatePicker extends StatefulWidget { + const DatePicker({super.key}); + + @override + State createState() => _DatePickerState(); +} + +class _DatePickerState extends State { + DateTime? selectedDate; + final DateTime _firstDate = DateTime(DateTime.now().year - 2); + final DateTime _lastDate = DateTime(DateTime.now().year + 1); + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Date picker', + tooltipMessage: 'Use showDatePicker', + child: TextButton.icon( + onPressed: () async { + DateTime? date = await showDatePicker( + context: context, + initialDate: selectedDate ?? DateTime.now(), + firstDate: _firstDate, + lastDate: _lastDate, + ); + setState(() { + selectedDate = date; + if (selectedDate != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Selected Date: ${selectedDate!.day}/${selectedDate!.month}/${selectedDate!.year}', + ), + ), + ); + } + }); + }, + icon: const Icon(Icons.calendar_month), + label: const Text( + 'Show date picker', + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + ); + } +} + +class TimePicker extends StatefulWidget { + const TimePicker({super.key}); + + @override + State createState() => _TimePickerState(); +} + +class _TimePickerState extends State { + TimeOfDay? selectedTime; + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Time picker', + tooltipMessage: 'Use showTimePicker', + child: TextButton.icon( + onPressed: () async { + final TimeOfDay? time = await showTimePicker( + context: context, + initialTime: selectedTime ?? TimeOfDay.now(), + builder: (context, child) { + return MediaQuery( + data: MediaQuery.of( + context, + ).copyWith(alwaysUse24HourFormat: true), + child: child!, + ); + }, + ); + setState(() { + selectedTime = time; + if (selectedTime != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Selected time: ${selectedTime!.format(context)}', + ), + ), + ); + } + }); + }, + icon: const Icon(Icons.schedule), + label: const Text( + 'Show time picker', + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + ); + } +} + +class SegmentedButtons extends StatelessWidget { + const SegmentedButtons({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentDecoration( + label: 'Segmented buttons', + tooltipMessage: 'Use SegmentedButton', + child: Column( + children: [SingleChoice(), colDivider, MultipleChoice()], + ), + ); + } +} + +enum Calendar { day, week, month, year } + +class SingleChoice extends StatefulWidget { + const SingleChoice({super.key}); + + @override + State createState() => _SingleChoiceState(); +} + +class _SingleChoiceState extends State { + Calendar calendarView = Calendar.day; + + @override + Widget build(BuildContext context) { + return SegmentedButton( + segments: const >[ + ButtonSegment( + value: Calendar.day, + label: Text('Day'), + icon: Icon(Icons.calendar_view_day), + ), + ButtonSegment( + value: Calendar.week, + label: Text('Week'), + icon: Icon(Icons.calendar_view_week), + ), + ButtonSegment( + value: Calendar.month, + label: Text('Month'), + icon: Icon(Icons.calendar_view_month), + ), + ButtonSegment( + value: Calendar.year, + label: Text('Year'), + icon: Icon(Icons.calendar_today), + ), + ], + selected: {calendarView}, + onSelectionChanged: (newSelection) { + setState(() { + // By default there is only a single segment that can be + // selected at one time, so its value is always the first + // item in the selected set. + calendarView = newSelection.first; + }); + }, + ); + } +} + +enum Sizes { extraSmall, small, medium, large, extraLarge } + +class MultipleChoice extends StatefulWidget { + const MultipleChoice({super.key}); + + @override + State createState() => _MultipleChoiceState(); +} + +class _MultipleChoiceState extends State { + Set selection = {Sizes.large, Sizes.extraLarge}; + + @override + Widget build(BuildContext context) { + return SegmentedButton( + segments: const >[ + ButtonSegment(value: Sizes.extraSmall, label: Text('XS')), + ButtonSegment(value: Sizes.small, label: Text('S')), + ButtonSegment(value: Sizes.medium, label: Text('M')), + ButtonSegment(value: Sizes.large, label: Text('L')), + ButtonSegment(value: Sizes.extraLarge, label: Text('XL')), + ], + selected: selection, + onSelectionChanged: (newSelection) { + setState(() { + selection = newSelection; + }); + }, + multiSelectionEnabled: true, + ); + } +} + +class SnackBarSection extends StatelessWidget { + const SnackBarSection({super.key}); + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Snackbar', + tooltipMessage: + 'Use ScaffoldMessenger.of(context).showSnackBar with SnackBar', + child: TextButton( + onPressed: () { + final snackBar = SnackBar( + behavior: SnackBarBehavior.floating, + width: 400.0, + content: const Text('This is a snackbar'), + action: SnackBarAction(label: 'Close', onPressed: () {}), + ); + + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + }, + child: const Text( + 'Show snackbar', + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + ); + } +} + +class BottomSheetSection extends StatefulWidget { + const BottomSheetSection({super.key}); + + @override + State createState() => _BottomSheetSectionState(); +} + +class _BottomSheetSectionState extends State { + bool isNonModalBottomSheetOpen = false; + PersistentBottomSheetController? _nonModalBottomSheetController; + + @override + Widget build(BuildContext context) { + List buttonList = [ + IconButton(onPressed: () {}, icon: const Icon(Icons.share_outlined)), + IconButton(onPressed: () {}, icon: const Icon(Icons.add)), + IconButton(onPressed: () {}, icon: const Icon(Icons.delete_outline)), + IconButton( + onPressed: () {}, + icon: const Icon(Icons.archive_outlined), + ), + IconButton( + onPressed: () {}, + icon: const Icon(Icons.settings_outlined), + ), + IconButton( + onPressed: () {}, + icon: const Icon(Icons.favorite_border), + ), + ]; + List labelList = const [ + Text('Share'), + Text('Add to'), + Text('Trash'), + Text('Archive'), + Text('Settings'), + Text('Favorite'), + ]; + + buttonList = List.generate( + buttonList.length, + (index) => Padding( + padding: const EdgeInsets.fromLTRB(20.0, 30.0, 20.0, 20.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [buttonList[index], labelList[index]], + ), + ), + ); + + return ComponentDecoration( + label: 'Bottom sheet', + tooltipMessage: 'Use showModalBottomSheet or showBottomSheet', + child: Wrap( + alignment: WrapAlignment.spaceEvenly, + children: [ + TextButton( + child: const Text( + 'Show modal bottom sheet', + style: TextStyle(fontWeight: FontWeight.bold), + ), + onPressed: () { + showModalBottomSheet( + showDragHandle: true, + context: context, + constraints: const BoxConstraints(maxWidth: 640), + builder: (context) { + return SizedBox( + height: 150, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32.0, + ), + child: ListView( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + children: buttonList, + ), + ), + ); + }, + ); + }, + ), + TextButton( + child: Text( + isNonModalBottomSheetOpen + ? 'Hide bottom sheet' + : 'Show bottom sheet', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + onPressed: () { + if (isNonModalBottomSheetOpen) { + _nonModalBottomSheetController?.close(); + setState(() { + isNonModalBottomSheetOpen = false; + }); + return; + } else { + setState(() { + isNonModalBottomSheetOpen = true; + }); + } + + _nonModalBottomSheetController = showBottomSheet( + elevation: 8.0, + context: context, + constraints: const BoxConstraints(maxWidth: 640), + builder: (context) { + return SizedBox( + height: 150, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32.0, + ), + child: ListView( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + children: buttonList, + ), + ), + ); + }, + ); + }, + ), + ], + ), + ); + } +} + +class BottomAppBars extends StatelessWidget { + const BottomAppBars({super.key}); + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Bottom app bar', + tooltipMessage: 'Use BottomAppBar', + child: Column( + children: [ + SizedBox( + height: 80, + child: Scaffold( + floatingActionButton: FloatingActionButton( + onPressed: () {}, + elevation: 0.0, + child: const Icon(Icons.add), + ), + floatingActionButtonLocation: + FloatingActionButtonLocation.endContained, + bottomNavigationBar: BottomAppBar( + child: Row( + children: [ + const IconButtonAnchorExample(), + IconButton( + tooltip: 'Search', + icon: const Icon(Icons.search), + onPressed: () {}, + ), + IconButton( + tooltip: 'Favorite', + icon: const Icon(Icons.favorite), + onPressed: () {}, + ), + ], + ), + ), + ), + ), + ], + ), + ); + } +} + +class IconButtonAnchorExample extends StatelessWidget { + const IconButtonAnchorExample({super.key}); + + @override + Widget build(BuildContext context) { + return MenuAnchor( + builder: (context, controller, child) { + return IconButton( + onPressed: () { + if (controller.isOpen) { + controller.close(); + } else { + controller.open(); + } + }, + icon: const Icon(Icons.more_vert), + ); + }, + menuChildren: [ + MenuItemButton(child: const Text('Menu 1'), onPressed: () {}), + MenuItemButton(child: const Text('Menu 2'), onPressed: () {}), + SubmenuButton( + menuChildren: [ + MenuItemButton( + onPressed: () {}, + child: const Text('Menu 3.1'), + ), + MenuItemButton( + onPressed: () {}, + child: const Text('Menu 3.2'), + ), + MenuItemButton( + onPressed: () {}, + child: const Text('Menu 3.3'), + ), + ], + child: const Text('Menu 3'), + ), + ], + ); + } +} + +class ButtonAnchorExample extends StatelessWidget { + const ButtonAnchorExample({super.key}); + + @override + Widget build(BuildContext context) { + return MenuAnchor( + builder: (context, controller, child) { + return FilledButton.tonal( + onPressed: () { + if (controller.isOpen) { + controller.close(); + } else { + controller.open(); + } + }, + child: const Text('Show menu'), + ); + }, + menuChildren: [ + MenuItemButton( + leadingIcon: const Icon(Icons.people_alt_outlined), + child: const Text('Item 1'), + onPressed: () {}, + ), + MenuItemButton( + leadingIcon: const Icon(Icons.remove_red_eye_outlined), + child: const Text('Item 2'), + onPressed: () {}, + ), + MenuItemButton( + leadingIcon: const Icon(Icons.refresh), + onPressed: () {}, + child: const Text('Item 3'), + ), + ], + ); + } +} + +class NavigationDrawers extends StatelessWidget { + const NavigationDrawers({super.key, required this.scaffoldKey}); + final GlobalKey scaffoldKey; + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Navigation drawer', + tooltipMessage: + 'Use NavigationDrawer. For modal navigation drawers, see Scaffold.endDrawer', + child: Column( + children: [ + const SizedBox(height: 520, child: NavigationDrawerSection()), + colDivider, + colDivider, + TextButton( + child: const Text( + 'Show modal navigation drawer', + style: TextStyle(fontWeight: FontWeight.bold), + ), + onPressed: () { + scaffoldKey.currentState!.openEndDrawer(); + }, + ), + ], + ), + ); + } +} + +class NavigationDrawerSection extends StatefulWidget { + const NavigationDrawerSection({super.key}); + + @override + State createState() => + _NavigationDrawerSectionState(); +} + +class _NavigationDrawerSectionState extends State { + int navDrawerIndex = 0; + + @override + Widget build(BuildContext context) { + return NavigationDrawer( + onDestinationSelected: (selectedIndex) { + setState(() { + navDrawerIndex = selectedIndex; + }); + }, + selectedIndex: navDrawerIndex, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(28, 16, 16, 10), + child: Text( + 'Mail', + style: Theme.of(context).textTheme.titleSmall, + ), + ), + ...destinations.map((destination) { + return NavigationDrawerDestination( + label: Text(destination.label), + icon: destination.icon, + selectedIcon: destination.selectedIcon, + ); + }), + const Divider(indent: 28, endIndent: 28), + Padding( + padding: const EdgeInsets.fromLTRB(28, 16, 16, 10), + child: Text( + 'Labels', + style: Theme.of(context).textTheme.titleSmall, + ), + ), + ...labelDestinations.map((destination) { + return NavigationDrawerDestination( + label: Text(destination.label), + icon: destination.icon, + selectedIcon: destination.selectedIcon, + ); + }), + ], + ); + } +} + +class ExampleDestination { + const ExampleDestination(this.label, this.icon, this.selectedIcon); + + final String label; + final Widget icon; + final Widget selectedIcon; +} + +const List destinations = [ + ExampleDestination( + 'Inbox', + Icon(Icons.inbox_outlined), + Icon(Icons.inbox), + ), + ExampleDestination( + 'Outbox', + Icon(Icons.send_outlined), + Icon(Icons.send), + ), + ExampleDestination( + 'Favorites', + Icon(Icons.favorite_outline), + Icon(Icons.favorite), + ), + ExampleDestination( + 'Trash', + Icon(Icons.delete_outline), + Icon(Icons.delete), + ), +]; + +const List labelDestinations = [ + ExampleDestination( + 'Family', + Icon(Icons.bookmark_border), + Icon(Icons.bookmark), + ), + ExampleDestination( + 'School', + Icon(Icons.bookmark_border), + Icon(Icons.bookmark), + ), + ExampleDestination( + 'Work', + Icon(Icons.bookmark_border), + Icon(Icons.bookmark), + ), +]; + +class NavigationRails extends StatelessWidget { + const NavigationRails({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentDecoration( + label: 'Navigation rail', + tooltipMessage: 'Use NavigationRail', + child: IntrinsicWidth( + child: SizedBox(height: 420, child: NavigationRailSection()), + ), + ); + } +} + +class NavigationRailSection extends StatefulWidget { + const NavigationRailSection({super.key}); + + @override + State createState() => _NavigationRailSectionState(); +} + +class _NavigationRailSectionState extends State { + int navRailIndex = 0; + + @override + Widget build(BuildContext context) { + return NavigationRail( + onDestinationSelected: (selectedIndex) { + setState(() { + navRailIndex = selectedIndex; + }); + }, + elevation: 4, + leading: FloatingActionButton( + child: const Icon(Icons.create), + onPressed: () {}, + ), + groupAlignment: 0.0, + selectedIndex: navRailIndex, + labelType: NavigationRailLabelType.selected, + destinations: [ + ...destinations.map((destination) { + return NavigationRailDestination( + label: Text(destination.label), + icon: destination.icon, + selectedIcon: destination.selectedIcon, + ); + }), + ], + ); + } +} + +class Tabs extends StatefulWidget { + const Tabs({super.key}); + + @override + State createState() => _TabsState(); +} + +class _TabsState extends State with TickerProviderStateMixin { + late TabController _tabController; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 3, vsync: this); + } + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Tabs', + tooltipMessage: 'Use TabBar', + child: SizedBox( + height: 80, + child: Scaffold( + appBar: AppBar( + bottom: TabBar( + controller: _tabController, + tabs: const [ + Tab( + icon: Icon(Icons.videocam_outlined), + text: 'Video', + iconMargin: EdgeInsets.only(bottom: 0.0), + ), + Tab( + icon: Icon(Icons.photo_outlined), + text: 'Photos', + iconMargin: EdgeInsets.only(bottom: 0.0), + ), + Tab( + icon: Icon(Icons.audiotrack_sharp), + text: 'Audio', + iconMargin: EdgeInsets.only(bottom: 0.0), + ), + ], + ), + ), + ), + ), + ); + } +} + +class TopAppBars extends StatelessWidget { + const TopAppBars({super.key}); + + static final actions = [ + IconButton(icon: const Icon(Icons.attach_file), onPressed: () {}), + IconButton(icon: const Icon(Icons.event), onPressed: () {}), + IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}), + ]; + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Top app bars', + tooltipMessage: + 'Use AppBar, SliverAppBar, SliverAppBar.medium, or SliverAppBar.large', + child: Column( + children: [ + AppBar( + title: const Text('Center-aligned'), + leading: const BackButton(), + actions: [ + IconButton( + iconSize: 32, + icon: const Icon(Icons.account_circle_outlined), + onPressed: () {}, + ), + ], + centerTitle: true, + ), + colDivider, + AppBar( + title: const Text('Small'), + leading: const BackButton(), + actions: actions, + centerTitle: false, + ), + colDivider, + SizedBox( + height: 100, + child: CustomScrollView( + slivers: [ + SliverAppBar.medium( + title: const Text('Medium'), + leading: const BackButton(), + actions: actions, + ), + const SliverFillRemaining(), + ], + ), + ), + colDivider, + SizedBox( + height: 130, + child: CustomScrollView( + slivers: [ + SliverAppBar.large( + title: const Text('Large'), + leading: const BackButton(), + actions: actions, + ), + const SliverFillRemaining(), + ], + ), + ), + ], + ), + ); + } +} + +class Menus extends StatefulWidget { + const Menus({super.key}); + + @override + State createState() => _MenusState(); +} + +class _MenusState extends State { + final TextEditingController colorController = TextEditingController(); + final TextEditingController iconController = TextEditingController(); + IconLabel? selectedIcon = IconLabel.smile; + ColorLabel? selectedColor; + + @override + Widget build(BuildContext context) { + final List> colorEntries = + >[]; + for (final ColorLabel color in ColorLabel.values) { + colorEntries.add( + DropdownMenuEntry( + value: color, + label: color.label, + enabled: color.label != 'Grey', + ), + ); + } + + final List> iconEntries = + >[]; + for (final IconLabel icon in IconLabel.values) { + iconEntries.add( + DropdownMenuEntry(value: icon, label: icon.label), + ); + } + + return ComponentDecoration( + label: 'Menus', + tooltipMessage: 'Use MenuAnchor or DropdownMenu', + child: Column( + children: [ + const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ButtonAnchorExample(), + rowDivider, + IconButtonAnchorExample(), + ], + ), + colDivider, + Wrap( + alignment: WrapAlignment.spaceAround, + runAlignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + spacing: smallSpacing, + runSpacing: smallSpacing, + children: [ + DropdownMenu( + controller: colorController, + label: const Text('Color'), + enableFilter: true, + dropdownMenuEntries: colorEntries, + inputDecorationTheme: const InputDecorationTheme( + filled: true, + ), + onSelected: (color) { + setState(() { + selectedColor = color; + }); + }, + ), + DropdownMenu( + initialSelection: IconLabel.smile, + controller: iconController, + leadingIcon: const Icon(Icons.search), + label: const Text('Icon'), + dropdownMenuEntries: iconEntries, + onSelected: (icon) { + setState(() { + selectedIcon = icon; + }); + }, + ), + Icon( + selectedIcon?.icon, + color: selectedColor?.color ?? Colors.grey.withAlpha(128), + ), + ], + ), + ], + ), + ); + } +} + +enum ColorLabel { + blue('Blue', Colors.blue), + pink('Pink', Colors.pink), + green('Green', Colors.green), + yellow('Yellow', Colors.yellow), + grey('Grey', Colors.grey); + + const ColorLabel(this.label, this.color); + final String label; + final Color color; +} + +enum IconLabel { + smile('Smile', Icons.sentiment_satisfied_outlined), + cloud('Cloud', Icons.cloud_outlined), + brush('Brush', Icons.brush_outlined), + heart('Heart', Icons.favorite); + + const IconLabel(this.label, this.icon); + final String label; + final IconData icon; +} + +class Sliders extends StatefulWidget { + const Sliders({super.key}); + + @override + State createState() => _SlidersState(); +} + +class _SlidersState extends State { + double sliderValue0 = 30.0; + double sliderValue1 = 20.0; + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Sliders', + tooltipMessage: 'Use Slider or RangeSlider', + child: Column( + children: [ + Slider( + max: 100, + value: sliderValue0, + onChanged: (value) { + setState(() { + sliderValue0 = value; + }); + }, + ), + const SizedBox(height: 20), + Slider( + max: 100, + divisions: 5, + value: sliderValue1, + label: sliderValue1.round().toString(), + onChanged: (value) { + setState(() { + sliderValue1 = value; + }); + }, + ), + ], + ), + ); + } +} + +class SearchAnchors extends StatefulWidget { + const SearchAnchors({super.key}); + + @override + State createState() => _SearchAnchorsState(); +} + +class _SearchAnchorsState extends State { + String? selectedColor; + List searchHistory = []; + + Iterable getHistoryList(SearchController controller) { + return searchHistory.map( + (color) => ListTile( + leading: const Icon(Icons.history), + title: Text(color.label), + trailing: IconButton( + icon: const Icon(Icons.call_missed), + onPressed: () { + controller.text = color.label; + controller.selection = TextSelection.collapsed( + offset: controller.text.length, + ); + }, + ), + onTap: () { + controller.closeView(color.label); + handleSelection(color); + }, + ), + ); + } + + Iterable getSuggestions(SearchController controller) { + final String input = controller.value.text; + return ColorItem.values + .where((color) => color.label.contains(input)) + .map( + (filteredColor) => ListTile( + leading: CircleAvatar(backgroundColor: filteredColor.color), + title: Text(filteredColor.label), + trailing: IconButton( + icon: const Icon(Icons.call_missed), + onPressed: () { + controller.text = filteredColor.label; + controller.selection = TextSelection.collapsed( + offset: controller.text.length, + ); + }, + ), + onTap: () { + controller.closeView(filteredColor.label); + handleSelection(filteredColor); + }, + ), + ); + } + + void handleSelection(ColorItem color) { + setState(() { + selectedColor = color.label; + if (searchHistory.length >= 5) { + searchHistory.removeLast(); + } + searchHistory.insert(0, color); + }); + } + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Search', + tooltipMessage: 'Use SearchAnchor or SearchAnchor.bar', + child: Column( + children: [ + SearchAnchor.bar( + barHintText: 'Search colors', + suggestionsBuilder: (context, controller) { + if (controller.text.isEmpty) { + if (searchHistory.isNotEmpty) { + return getHistoryList(controller); + } + return [ + const Center( + child: Text( + 'No search history.', + style: TextStyle(color: Colors.grey), + ), + ), + ]; + } + return getSuggestions(controller); + }, + ), + const SizedBox(height: 20), + if (selectedColor == null) + const Text('Select a color') + else + Text('Last selected color is $selectedColor'), + ], + ), + ); + } +} + +class Carousels extends StatelessWidget { + const Carousels({super.key}); + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Carousel', + tooltipMessage: 'Use CarouselView', + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Padding( + padding: EdgeInsets.only(left: 8.0), + child: Text('Uncontained Carousel'), + ), + ConstrainedBox( + constraints: const BoxConstraints.tightFor(height: 150), + child: CarouselView( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + side: BorderSide( + color: Theme.of(context).colorScheme.outline, + ), + ), + shrinkExtent: 100, + itemExtent: 180, + children: List.generate(20, (index) { + return Center(child: Text('Item $index')); + }), + ), + ), + colDivider, + const Padding( + padding: EdgeInsets.only(left: 8.0), + child: Text('Uncontained Carousel with snapping effect'), + ), + ConstrainedBox( + constraints: const BoxConstraints.tightFor(height: 150), + child: CarouselView( + itemSnapping: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + side: BorderSide( + color: Theme.of(context).colorScheme.outline, + ), + ), + shrinkExtent: 100, + itemExtent: 180, + children: List.generate(20, (index) { + return Center(child: Text('Item $index')); + }), + ), + ), + ], + ), + ); + } +} + +class ComponentDecoration extends StatefulWidget { + const ComponentDecoration({ + super.key, + required this.label, + required this.child, + this.tooltipMessage = '', + }); + + final String label; + final Widget child; + final String? tooltipMessage; + + @override + State createState() => _ComponentDecorationState(); +} + +class _ComponentDecorationState extends State { + final focusNode = FocusNode(); + + @override + Widget build(BuildContext context) { + return RepaintBoundary( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: smallSpacing), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + widget.label, + style: Theme.of(context).textTheme.titleMedium, + ), + Tooltip( + message: widget.tooltipMessage, + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 5.0), + child: Icon(Icons.info_outline, size: 16), + ), + ), + ], + ), + ConstrainedBox( + constraints: const BoxConstraints.tightFor( + width: widthConstraint, + ), + // Tapping within the a component card should request focus + // for that component's children. + child: Focus( + focusNode: focusNode, + canRequestFocus: true, + child: GestureDetector( + onTapDown: (_) { + focusNode.requestFocus(); + }, + behavior: HitTestBehavior.opaque, + child: Card( + elevation: 0, + shape: RoundedRectangleBorder( + side: BorderSide( + color: Theme.of( + context, + ).colorScheme.outlineVariant, + ), + borderRadius: const BorderRadius.all( + Radius.circular(12), + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 5.0, + vertical: 20.0, + ), + child: Center(child: widget.child), + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} + +class ComponentGroupDecoration extends StatelessWidget { + const ComponentGroupDecoration({ + super.key, + required this.label, + required this.children, + }); + + final String label; + final List children; + + @override + Widget build(BuildContext context) { + // Fully traverse this component group before moving on + return FocusTraversalGroup( + child: Card( + margin: EdgeInsets.zero, + elevation: 0, + color: Theme.of( + context, + ).colorScheme.surfaceContainerHighest.withAlpha(77), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 20.0), + child: Center( + child: Column( + children: [ + Text(label, style: Theme.of(context).textTheme.titleLarge), + colDivider, + ...children, + ], + ), + ), + ), + ), + ); + } +} + +enum ColorItem { + red('red', Colors.red), + orange('orange', Colors.orange), + yellow('yellow', Colors.yellow), + green('green', Colors.green), + blue('blue', Colors.blue), + indigo('indigo', Colors.indigo), + violet('violet', Color(0xFF8F00FF)), + purple('purple', Colors.purple), + pink('pink', Colors.pink), + silver('silver', Color(0xFF808080)), + gold('gold', Color(0xFFFFD700)), + beige('beige', Color(0xFFF5F5DC)), + brown('brown', Colors.brown), + grey('grey', Colors.grey), + black('black', Colors.black), + white('white', Colors.white); + + const ColorItem(this.label, this.color); + final String label; + final Color color; +} diff --git a/material_3_demo/lib/src/constants.dart b/material_3_demo/lib/src/constants.dart new file mode 100644 index 0000000..0c82786 --- /dev/null +++ b/material_3_demo/lib/src/constants.dart @@ -0,0 +1,75 @@ +// Copyright 2021 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. + +import 'package:flutter/material.dart'; + +// NavigationRail shows if the screen width is greater or equal to +// narrowScreenWidthThreshold; otherwise, NavigationBar is used for navigation. +const double narrowScreenWidthThreshold = 450; + +const double mediumWidthBreakpoint = 1000; +const double largeWidthBreakpoint = 1500; + +const double transitionLength = 500; + +// Whether the user has chosen a theme color via a direct [ColorSeed] selection, +// or an image [ColorImageProvider]. +enum ColorSelectionMethod { colorSeed, image } + +enum ColorSeed { + baseColor('M3 Baseline', Color(0xff6750a4)), + indigo('Indigo', Colors.indigo), + blue('Blue', Colors.blue), + teal('Teal', Colors.teal), + green('Green', Colors.green), + yellow('Yellow', Colors.yellow), + orange('Orange', Colors.orange), + deepOrange('Deep Orange', Colors.deepOrange), + pink('Pink', Colors.pink); + + const ColorSeed(this.label, this.color); + final String label; + final Color color; +} + +enum ColorImageProvider { + leaves( + 'Leaves', + 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_1.png', + ), + peonies( + 'Peonies', + 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_2.png', + ), + bubbles( + 'Bubbles', + 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_3.png', + ), + seaweed( + 'Seaweed', + 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_4.png', + ), + seagrapes( + 'Sea Grapes', + 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_5.png', + ), + petals( + 'Petals', + 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_6.png', + ); + + const ColorImageProvider(this.label, this.url); + final String label; + final String url; +} + +enum ScreenSelected { + component(0), + color(1), + typography(2), + elevation(3); + + const ScreenSelected(this.value); + final int value; +} diff --git a/material_3_demo/lib/src/elevation_screen.dart b/material_3_demo/lib/src/elevation_screen.dart new file mode 100644 index 0000000..3ea88f8 --- /dev/null +++ b/material_3_demo/lib/src/elevation_screen.dart @@ -0,0 +1,201 @@ +// Copyright 2021 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. + +import 'package:flutter/material.dart'; + +class ElevationScreen extends StatelessWidget { + const ElevationScreen({super.key}); + + @override + Widget build(BuildContext context) { + Color shadowColor = Theme.of(context).colorScheme.shadow; + Color surfaceTint = Theme.of(context).colorScheme.primary; + return Expanded( + child: CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.fromLTRB(16.0, 20, 16.0, 0), + child: Text( + 'Surface Tint Color Only', + style: Theme.of(context).textTheme.titleLarge, + ), + ), + ), + ElevationGrid( + surfaceTintColor: surfaceTint, + shadowColor: Colors.transparent, + ), + SliverList( + delegate: SliverChildListDelegate([ + const SizedBox(height: 10), + Padding( + padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 0), + child: Text( + 'Surface Tint Color and Shadow Color', + style: Theme.of(context).textTheme.titleLarge, + ), + ), + ]), + ), + ElevationGrid( + shadowColor: shadowColor, + surfaceTintColor: surfaceTint, + ), + SliverList( + delegate: SliverChildListDelegate([ + const SizedBox(height: 10), + Padding( + padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 0), + child: Text( + 'Shadow Color Only', + style: Theme.of(context).textTheme.titleLarge, + ), + ), + ]), + ), + ElevationGrid(shadowColor: shadowColor), + ], + ), + ); + } +} + +const double narrowScreenWidthThreshold = 450; + +class ElevationGrid extends StatelessWidget { + const ElevationGrid({ + super.key, + this.shadowColor, + this.surfaceTintColor, + }); + + final Color? shadowColor; + final Color? surfaceTintColor; + + List elevationCards( + Color? shadowColor, + Color? surfaceTintColor, + ) { + return elevations + .map( + (elevationInfo) => ElevationCard( + info: elevationInfo, + shadowColor: shadowColor, + surfaceTint: surfaceTintColor, + ), + ) + .toList(); + } + + @override + Widget build(BuildContext context) { + return SliverPadding( + padding: const EdgeInsets.all(8), + sliver: SliverLayoutBuilder( + builder: (context, constraints) { + if (constraints.crossAxisExtent < narrowScreenWidthThreshold) { + return SliverGrid.count( + crossAxisCount: 3, + children: elevationCards(shadowColor, surfaceTintColor), + ); + } else { + return SliverGrid.count( + crossAxisCount: 6, + children: elevationCards(shadowColor, surfaceTintColor), + ); + } + }, + ), + ); + } +} + +class ElevationCard extends StatefulWidget { + const ElevationCard({ + super.key, + required this.info, + this.shadowColor, + this.surfaceTint, + }); + + final ElevationInfo info; + final Color? shadowColor; + final Color? surfaceTint; + + @override + State createState() => _ElevationCardState(); +} + +class _ElevationCardState extends State { + late double _elevation; + + @override + void initState() { + super.initState(); + _elevation = widget.info.elevation; + } + + @override + Widget build(BuildContext context) { + const BorderRadius borderRadius = BorderRadius.all( + Radius.circular(4.0), + ); + final Color color = Theme.of(context).colorScheme.surface; + + return Padding( + padding: const EdgeInsets.all(8.0), + child: Material( + borderRadius: borderRadius, + elevation: _elevation, + color: color, + shadowColor: widget.shadowColor, + surfaceTintColor: widget.surfaceTint, + type: MaterialType.card, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Level ${widget.info.level}', + style: Theme.of(context).textTheme.labelMedium, + ), + Text( + '${widget.info.elevation.toInt()} dp', + style: Theme.of(context).textTheme.labelMedium, + ), + if (widget.surfaceTint != null) + Expanded( + child: Align( + alignment: Alignment.bottomRight, + child: Text( + '${widget.info.overlayPercent}%', + style: Theme.of(context).textTheme.bodySmall, + ), + ), + ), + ], + ), + ), + ), + ); + } +} + +class ElevationInfo { + const ElevationInfo(this.level, this.elevation, this.overlayPercent); + final int level; + final double elevation; + final int overlayPercent; +} + +const List elevations = [ + ElevationInfo(0, 0.0, 0), + ElevationInfo(1, 1.0, 5), + ElevationInfo(2, 3.0, 8), + ElevationInfo(3, 6.0, 11), + ElevationInfo(4, 8.0, 12), + ElevationInfo(5, 12.0, 14), +]; diff --git a/material_3_demo/lib/src/expanded_color_seed_action.dart b/material_3_demo/lib/src/expanded_color_seed_action.dart new file mode 100644 index 0000000..241b86e --- /dev/null +++ b/material_3_demo/lib/src/expanded_color_seed_action.dart @@ -0,0 +1,45 @@ +// Copyright 2021 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. + +import 'package:flutter/material.dart'; + +import 'constants.dart'; + +class ExpandedColorSeedAction extends StatelessWidget { + const ExpandedColorSeedAction({ + super.key, + required this.handleColorSelect, + required this.colorSelected, + required this.colorSelectionMethod, + }); + + final void Function(int) handleColorSelect; + final ColorSeed colorSelected; + final ColorSelectionMethod colorSelectionMethod; + + @override + Widget build(BuildContext context) { + return ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 200.0), + child: GridView.count( + crossAxisCount: 3, + children: List.generate( + ColorSeed.values.length, + (i) => IconButton( + icon: const Icon(Icons.radio_button_unchecked), + color: ColorSeed.values[i].color, + isSelected: + colorSelected.color == ColorSeed.values[i].color && + colorSelectionMethod == ColorSelectionMethod.colorSeed, + selectedIcon: const Icon(Icons.circle), + onPressed: () { + handleColorSelect(i); + }, + tooltip: ColorSeed.values[i].label, + ), + ), + ), + ); + } +} diff --git a/material_3_demo/lib/src/expanded_image_color_action.dart b/material_3_demo/lib/src/expanded_image_color_action.dart new file mode 100644 index 0000000..4cd640e --- /dev/null +++ b/material_3_demo/lib/src/expanded_image_color_action.dart @@ -0,0 +1,80 @@ +// Copyright 2021 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. + +import 'package:flutter/material.dart'; + +import 'constants.dart'; + +class ExpandedImageColorAction extends StatelessWidget { + const ExpandedImageColorAction({ + super.key, + required this.handleImageSelect, + required this.imageSelected, + required this.colorSelectionMethod, + }); + + final void Function(int) handleImageSelect; + final ColorImageProvider imageSelected; + final ColorSelectionMethod colorSelectionMethod; + + @override + Widget build(BuildContext context) { + return ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 150.0), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: GridView.count( + crossAxisCount: 3, + children: List.generate( + ColorImageProvider.values.length, + (i) => _ImageButton( + index: i, + select: + imageSelected == ColorImageProvider.values[i] && + colorSelectionMethod == ColorSelectionMethod.image + ? null + : () => handleImageSelect(i), + ), + ), + ), + ), + ); + } +} + +class _ImageButton extends StatelessWidget { + const _ImageButton({required this.index, required this.select}); + + final void Function()? select; + final int index; + + @override + Widget build(BuildContext context) { + return Tooltip( + message: ColorImageProvider.values[index].name, + child: InkWell( + borderRadius: BorderRadius.circular(4.0), + onTap: select, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Material( + borderRadius: BorderRadius.circular(4.0), + elevation: select != null ? 3 : 0, + child: Padding( + padding: const EdgeInsets.all(4.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(4.0), + child: Image( + image: NetworkImage( + ColorImageProvider.values[index].url, + ), + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/material_3_demo/lib/src/expanded_trailing_actions.dart b/material_3_demo/lib/src/expanded_trailing_actions.dart new file mode 100644 index 0000000..c4bf046 --- /dev/null +++ b/material_3_demo/lib/src/expanded_trailing_actions.dart @@ -0,0 +1,92 @@ +// Copyright 2021 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. + +import 'package:flutter/material.dart'; + +import 'constants.dart'; +import 'expanded_color_seed_action.dart'; +import 'expanded_image_color_action.dart'; + +class ExpandedTrailingActions extends StatelessWidget { + const ExpandedTrailingActions({ + super.key, + required this.useLightMode, + required this.handleBrightnessChange, + required this.useMaterial3, + required this.handleMaterialVersionChange, + required this.handleColorSelect, + required this.handleImageSelect, + required this.imageSelected, + required this.colorSelected, + required this.colorSelectionMethod, + }); + + final void Function(bool) handleBrightnessChange; + final void Function() handleMaterialVersionChange; + final void Function(int) handleImageSelect; + final void Function(int) handleColorSelect; + + final bool useLightMode; + final bool useMaterial3; + + final ColorImageProvider imageSelected; + final ColorSeed colorSelected; + final ColorSelectionMethod colorSelectionMethod; + + @override + Widget build(BuildContext context) { + final screenHeight = MediaQuery.of(context).size.height; + final trailingActionsBody = Container( + constraints: const BoxConstraints.tightFor(width: 250), + padding: const EdgeInsets.symmetric(horizontal: 30), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + const Text('Brightness'), + Expanded(child: Container()), + Switch( + value: useLightMode, + onChanged: (value) { + handleBrightnessChange(value); + }, + ), + ], + ), + Row( + children: [ + useMaterial3 + ? const Text('Material 3') + : const Text('Material 2'), + Expanded(child: Container()), + Switch( + value: useMaterial3, + onChanged: (_) { + handleMaterialVersionChange(); + }, + ), + ], + ), + const Divider(), + ExpandedColorSeedAction( + handleColorSelect: handleColorSelect, + colorSelected: colorSelected, + colorSelectionMethod: colorSelectionMethod, + ), + const Divider(), + ExpandedImageColorAction( + handleImageSelect: handleImageSelect, + imageSelected: imageSelected, + colorSelectionMethod: colorSelectionMethod, + ), + ], + ), + ); + return screenHeight > 740 + ? trailingActionsBody + : SingleChildScrollView(child: trailingActionsBody); + } +} diff --git a/material_3_demo/lib/src/home.dart b/material_3_demo/lib/src/home.dart new file mode 100644 index 0000000..8b2cadf --- /dev/null +++ b/material_3_demo/lib/src/home.dart @@ -0,0 +1,269 @@ +// Copyright 2021 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. + +import 'package:flutter/material.dart'; + +import 'buttons.dart'; +import 'color_palettes_screen.dart'; +import 'component_screen.dart'; +import 'constants.dart'; +import 'elevation_screen.dart'; +import 'expanded_trailing_actions.dart'; +import 'navigation_transition.dart'; +import 'one_two_transition.dart'; +import 'typography_screen.dart'; + +class Home extends StatefulWidget { + const Home({ + super.key, + required this.useLightMode, + required this.useMaterial3, + required this.colorSelected, + required this.handleBrightnessChange, + required this.handleMaterialVersionChange, + required this.handleColorSelect, + required this.handleImageSelect, + required this.colorSelectionMethod, + required this.imageSelected, + }); + + final bool useLightMode; + final bool useMaterial3; + final ColorSeed colorSelected; + final ColorImageProvider imageSelected; + final ColorSelectionMethod colorSelectionMethod; + + final void Function(bool useLightMode) handleBrightnessChange; + final void Function() handleMaterialVersionChange; + final void Function(int value) handleColorSelect; + final void Function(int value) handleImageSelect; + + @override + State createState() => _HomeState(); +} + +class _HomeState extends State with SingleTickerProviderStateMixin { + final GlobalKey scaffoldKey = GlobalKey(); + late final AnimationController controller; + late final CurvedAnimation railAnimation; + bool controllerInitialized = false; + bool showMediumSizeLayout = false; + bool showLargeSizeLayout = false; + + int screenIndex = ScreenSelected.component.value; + + @override + initState() { + super.initState(); + controller = AnimationController( + duration: Duration(milliseconds: transitionLength.toInt() * 2), + value: 0, + vsync: this, + ); + railAnimation = CurvedAnimation( + parent: controller, + curve: const Interval(0.5, 1.0), + ); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + final double width = MediaQuery.of(context).size.width; + final AnimationStatus status = controller.status; + if (width > mediumWidthBreakpoint) { + if (width > largeWidthBreakpoint) { + showMediumSizeLayout = false; + showLargeSizeLayout = true; + } else { + showMediumSizeLayout = true; + showLargeSizeLayout = false; + } + if (status != AnimationStatus.forward && + status != AnimationStatus.completed) { + controller.forward(); + } + } else { + showMediumSizeLayout = false; + showLargeSizeLayout = false; + if (status != AnimationStatus.reverse && + status != AnimationStatus.dismissed) { + controller.reverse(); + } + } + if (!controllerInitialized) { + controllerInitialized = true; + controller.value = width > mediumWidthBreakpoint ? 1 : 0; + } + } + + void handleScreenChanged(int screenSelected) { + setState(() { + screenIndex = screenSelected; + }); + } + + Widget createScreenFor( + ScreenSelected screenSelected, + bool showNavBarExample, + ) => switch (screenSelected) { + ScreenSelected.component => Expanded( + child: OneTwoTransition( + animation: railAnimation, + one: FirstComponentList( + showNavBottomBar: showNavBarExample, + scaffoldKey: scaffoldKey, + showSecondList: showMediumSizeLayout || showLargeSizeLayout, + ), + two: SecondComponentList(scaffoldKey: scaffoldKey), + ), + ), + ScreenSelected.color => const ColorPalettesScreen(), + ScreenSelected.typography => const TypographyScreen(), + ScreenSelected.elevation => const ElevationScreen(), + }; + + PreferredSizeWidget _createAppBar() { + return AppBar( + title: widget.useMaterial3 + ? const Text('Material 3') + : const Text('Material 2'), + actions: !showMediumSizeLayout && !showLargeSizeLayout + ? [ + BrightnessButton( + handleBrightnessChange: widget.handleBrightnessChange, + ), + Material3Button( + handleMaterialVersionChange: + widget.handleMaterialVersionChange, + ), + ColorSeedButton( + handleColorSelect: widget.handleColorSelect, + colorSelected: widget.colorSelected, + colorSelectionMethod: widget.colorSelectionMethod, + ), + ColorImageButton( + handleImageSelect: widget.handleImageSelect, + imageSelected: widget.imageSelected, + colorSelectionMethod: widget.colorSelectionMethod, + ), + ] + : [Container()], + ); + } + + Widget _trailingActions() => Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Flexible( + child: BrightnessButton( + handleBrightnessChange: widget.handleBrightnessChange, + showTooltipBelow: false, + ), + ), + Flexible( + child: Material3Button( + handleMaterialVersionChange: widget.handleMaterialVersionChange, + showTooltipBelow: false, + ), + ), + Flexible( + child: ColorSeedButton( + handleColorSelect: widget.handleColorSelect, + colorSelected: widget.colorSelected, + colorSelectionMethod: widget.colorSelectionMethod, + ), + ), + Flexible( + child: ColorImageButton( + handleImageSelect: widget.handleImageSelect, + imageSelected: widget.imageSelected, + colorSelectionMethod: widget.colorSelectionMethod, + ), + ), + ], + ); + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: controller, + builder: (context, child) { + return NavigationTransition( + scaffoldKey: scaffoldKey, + animationController: controller, + railAnimation: railAnimation, + appBar: _createAppBar(), + body: createScreenFor( + ScreenSelected.values[screenIndex], + controller.value == 1, + ), + navigationRail: NavigationRail( + extended: showLargeSizeLayout, + destinations: _navRailDestinations, + selectedIndex: screenIndex, + onDestinationSelected: (index) { + setState(() { + screenIndex = index; + handleScreenChanged(screenIndex); + }); + }, + trailing: Expanded( + child: Padding( + padding: const EdgeInsets.only(bottom: 20), + child: showLargeSizeLayout + ? ExpandedTrailingActions( + useLightMode: widget.useLightMode, + handleBrightnessChange: widget.handleBrightnessChange, + useMaterial3: widget.useMaterial3, + handleMaterialVersionChange: + widget.handleMaterialVersionChange, + handleImageSelect: widget.handleImageSelect, + handleColorSelect: widget.handleColorSelect, + colorSelectionMethod: widget.colorSelectionMethod, + imageSelected: widget.imageSelected, + colorSelected: widget.colorSelected, + ) + : _trailingActions(), + ), + ), + ), + navigationBar: NavigationBars( + onSelectItem: (index) { + setState(() { + screenIndex = index; + handleScreenChanged(screenIndex); + }); + }, + selectedIndex: screenIndex, + isExampleBar: false, + ), + ); + }, + ); + } +} + +final List _navRailDestinations = appBarDestinations + .map( + (destination) => NavigationRailDestination( + icon: Tooltip( + message: destination.label, + child: destination.icon, + ), + selectedIcon: Tooltip( + message: destination.label, + child: destination.selectedIcon, + ), + label: Text(destination.label), + ), + ) + .toList(growable: false); diff --git a/material_3_demo/lib/src/navigation_transition.dart b/material_3_demo/lib/src/navigation_transition.dart new file mode 100644 index 0000000..2159a60 --- /dev/null +++ b/material_3_demo/lib/src/navigation_transition.dart @@ -0,0 +1,79 @@ +// Copyright 2021 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. + +import 'package:flutter/material.dart'; + +import 'bar_transition.dart'; +import 'component_screen.dart'; +import 'rail_transition.dart'; + +class NavigationTransition extends StatefulWidget { + const NavigationTransition({ + super.key, + required this.scaffoldKey, + required this.animationController, + required this.railAnimation, + required this.navigationRail, + required this.navigationBar, + required this.appBar, + required this.body, + }); + + final GlobalKey scaffoldKey; + final AnimationController animationController; + final CurvedAnimation railAnimation; + final Widget navigationRail; + final Widget navigationBar; + final PreferredSizeWidget appBar; + final Widget body; + + @override + State createState() => _NavigationTransitionState(); +} + +class _NavigationTransitionState extends State { + late final AnimationController controller; + late final CurvedAnimation railAnimation; + late final ReverseAnimation barAnimation; + bool controllerInitialized = false; + bool showDivider = false; + + @override + void initState() { + super.initState(); + + controller = widget.animationController; + railAnimation = widget.railAnimation; + + barAnimation = ReverseAnimation( + CurvedAnimation(parent: controller, curve: const Interval(0.0, 0.5)), + ); + } + + @override + Widget build(BuildContext context) { + final ColorScheme colorScheme = Theme.of(context).colorScheme; + + return Scaffold( + key: widget.scaffoldKey, + appBar: widget.appBar, + body: Row( + children: [ + RailTransition( + animation: railAnimation, + backgroundColor: colorScheme.surface, + child: widget.navigationRail, + ), + widget.body, + ], + ), + bottomNavigationBar: BarTransition( + animation: barAnimation, + backgroundColor: colorScheme.surface, + child: widget.navigationBar, + ), + endDrawer: const NavigationDrawerSection(), + ); + } +} diff --git a/material_3_demo/lib/src/one_two_transition.dart b/material_3_demo/lib/src/one_two_transition.dart new file mode 100644 index 0000000..1027249 --- /dev/null +++ b/material_3_demo/lib/src/one_two_transition.dart @@ -0,0 +1,62 @@ +// Copyright 2021 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. + +import 'package:flutter/material.dart'; + +import 'animations.dart'; +import 'constants.dart'; + +class OneTwoTransition extends StatefulWidget { + const OneTwoTransition({ + super.key, + required this.animation, + required this.one, + required this.two, + }); + + final Animation animation; + final Widget one; + final Widget two; + + @override + State createState() => _OneTwoTransitionState(); +} + +class _OneTwoTransitionState extends State { + late final Animation offsetAnimation; + late final Animation widthAnimation; + + @override + void initState() { + super.initState(); + + offsetAnimation = Tween( + begin: const Offset(1, 0), + end: Offset.zero, + ).animate(OffsetAnimation(widget.animation)); + + widthAnimation = Tween( + begin: 0, + end: mediumWidthBreakpoint, + ).animate(SizeAnimation(widget.animation)); + } + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Flexible(flex: mediumWidthBreakpoint.toInt(), child: widget.one), + if (widthAnimation.value.toInt() > 0) ...[ + Flexible( + flex: widthAnimation.value.toInt(), + child: FractionalTranslation( + translation: offsetAnimation.value, + child: widget.two, + ), + ), + ], + ], + ); + } +} diff --git a/material_3_demo/lib/src/rail_transition.dart b/material_3_demo/lib/src/rail_transition.dart new file mode 100644 index 0000000..f8e3ed3 --- /dev/null +++ b/material_3_demo/lib/src/rail_transition.dart @@ -0,0 +1,64 @@ +// Copyright 2021 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. + +import 'package:flutter/widgets.dart'; + +import 'animations.dart'; + +class RailTransition extends StatefulWidget { + const RailTransition({ + super.key, + required this.animation, + required this.backgroundColor, + required this.child, + }); + + final Animation animation; + final Widget child; + final Color backgroundColor; + + @override + State createState() => _RailTransition(); +} + +class _RailTransition extends State { + late Animation offsetAnimation; + late Animation widthAnimation; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + // The animations are only rebuilt by this method when the text + // direction changes because this widget only depends on Directionality. + final bool ltr = Directionality.of(context) == TextDirection.ltr; + + widthAnimation = Tween( + begin: 0, + end: 1, + ).animate(SizeAnimation(widget.animation)); + + offsetAnimation = Tween( + begin: ltr ? const Offset(-1, 0) : const Offset(1, 0), + end: Offset.zero, + ).animate(OffsetAnimation(widget.animation)); + } + + @override + Widget build(BuildContext context) { + return ClipRect( + child: DecoratedBox( + decoration: BoxDecoration(color: widget.backgroundColor), + child: Align( + alignment: Alignment.topLeft, + widthFactor: widthAnimation.value, + child: FractionalTranslation( + translation: offsetAnimation.value, + child: widget.child, + ), + ), + ), + ); + } +} diff --git a/material_3_demo/lib/src/scheme.dart b/material_3_demo/lib/src/scheme.dart new file mode 100644 index 0000000..6458af2 --- /dev/null +++ b/material_3_demo/lib/src/scheme.dart @@ -0,0 +1,417 @@ +// 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. + +import 'package:flutter/material.dart'; + +import 'color_box.dart'; + +class SchemePreview extends StatefulWidget { + const SchemePreview({ + super.key, + required this.label, + required this.scheme, + required this.brightness, + required this.colorMatch, + required this.contrast, + }); + + final String label; + final ColorScheme scheme; + final Brightness brightness; + final bool colorMatch; + final double contrast; + + @override + State createState() => _SchemePreviewState(); +} + +class _SchemePreviewState extends State { + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final fonts = theme.textTheme; + final colors = theme.colorScheme; + final dark = widget.brightness == Brightness.dark; + + final scheme = widget.scheme; + + return Theme( + data: theme.copyWith(colorScheme: scheme), + child: FittedBox( + fit: BoxFit.fitWidth, + child: Container( + width: 902, + decoration: BoxDecoration( + color: scheme.surface, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: theme.brightness == widget.brightness + ? colors.outlineVariant + : Colors.transparent, + ), + ), + padding: const EdgeInsets.only(top: 16, left: 16, right: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + widget.label, + style: fonts.titleMedium!.copyWith( + color: scheme.onSurface, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.start, + ), + ), + ], + ), + const SizedBox(height: 20), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + children: [ + ColorBox( + label: 'Primary', + tone: dark ? 'P-80' : 'P-40', + color: scheme.primary, + onColor: scheme.onPrimary, + height: 87, + width: 208, + ), + ColorBox( + label: 'On Primary', + tone: dark ? 'P-20' : 'P-100', + color: scheme.onPrimary, + onColor: scheme.primary, + height: 40, + width: 208, + ), + const SizedBox(height: 5), + ColorBox( + label: 'Primary Container', + tone: dark ? 'P-30' : 'P-90', + color: scheme.primaryContainer, + onColor: scheme.onPrimaryContainer, + height: 87, + width: 208, + ), + ColorBox( + label: 'On Primary Container', + tone: dark ? 'P-90' : 'P-10', + color: scheme.onPrimaryContainer, + onColor: scheme.primaryContainer, + height: 40, + width: 208, + ), + ], + ), + const SizedBox(width: 5), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + ColorBox( + label: 'Secondary', + tone: dark ? 'S-80' : 'S-40', + color: scheme.secondary, + onColor: scheme.onSecondary, + height: 87, + width: 208, + ), + ColorBox( + label: 'On Secondary', + tone: dark ? 'S-20' : 'S-100', + color: scheme.onSecondary, + onColor: scheme.secondary, + height: 40, + width: 208, + ), + const SizedBox(height: 5), + ColorBox( + label: 'Secondary Container', + tone: dark ? 'S-30' : 'S-90', + color: scheme.secondaryContainer, + onColor: scheme.onSecondaryContainer, + height: 87, + width: 208, + ), + ColorBox( + label: 'On Secondary Container', + tone: dark ? 'S-90' : 'S-10', + color: scheme.onSecondaryContainer, + onColor: scheme.secondaryContainer, + height: 40, + width: 208, + ), + ], + ), + const SizedBox(width: 5), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + ColorBox( + label: 'Tertiary', + tone: dark ? 'T-80' : 'T-40', + color: scheme.tertiary, + onColor: scheme.onTertiary, + height: 87, + width: 208, + ), + ColorBox( + label: 'On Tertiary', + tone: dark ? 'T-20' : 'T-100', + color: scheme.onTertiary, + onColor: scheme.tertiary, + height: 40, + width: 208, + ), + const SizedBox(height: 5), + ColorBox( + label: 'Tertiary Container', + tone: dark ? 'T-30' : 'T-90', + color: scheme.tertiaryContainer, + onColor: scheme.onTertiaryContainer, + height: 87, + width: 208, + ), + ColorBox( + label: 'On Tertiary Container', + tone: dark ? 'T-90' : 'T-10', + color: scheme.onTertiaryContainer, + onColor: scheme.tertiaryContainer, + height: 40, + width: 208, + ), + ], + ), + ], + ), + const SizedBox(height: 20), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + ColorBox( + label: 'Surface Dim', + tone: dark ? 'N-6' : 'N-87', + color: scheme.surfaceDim, + onColor: scheme.onSurface, + height: 105, + width: 211.45, + ), + ColorBox( + label: 'Surface', + tone: dark ? 'N-6' : 'N-98', + color: scheme.surface, + onColor: scheme.onSurface, + height: 105, + width: 211.45, + ), + ColorBox( + label: 'Surface Bright', + tone: dark ? 'N-24' : 'N-98', + color: scheme.surfaceBright, + onColor: scheme.onSurface, + height: 105, + width: 211.45, + ), + ], + ), + const SizedBox(height: 5), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + ColorBox( + label: 'Surf. Container\nLowest', + tone: dark ? 'N-4' : 'N-100', + color: scheme.surfaceContainerLowest, + onColor: scheme.onSurface, + height: 105, + width: 126.87, + ), + ColorBox( + label: 'Surf. Container\nLow', + tone: dark ? 'N-10' : 'N-96', + color: scheme.surfaceContainerLow, + onColor: scheme.onSurface, + height: 105, + width: 126.87, + ), + ColorBox( + label: 'Surf. Container', + tone: dark ? 'N-12' : 'N-94', + color: scheme.surfaceContainer, + onColor: scheme.onSurface, + height: 105, + width: 126.87, + ), + ColorBox( + label: 'Surf. Container\nHigh', + tone: dark ? 'N-17' : 'N-92', + color: scheme.surfaceContainerHigh, + onColor: scheme.onSurface, + height: 105, + width: 126.87, + ), + ColorBox( + label: 'Surf. Container\nHighest', + tone: dark ? 'N-24' : 'N-90', + color: scheme.surfaceContainerHighest, + onColor: scheme.onSurface, + height: 105, + width: 126.87, + ), + ], + ), + const SizedBox(height: 5), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + ColorBox( + label: 'On Surface', + tone: dark ? 'N-90' : 'N-10', + color: scheme.onSurface, + onColor: scheme.surface, + height: 40, + width: 158.59, + ), + ColorBox( + label: 'On Surface Var.', + tone: dark ? 'NV-90' : 'NV-30', + color: scheme.onSurfaceVariant, + onColor: scheme.surfaceContainerHighest, + height: 40, + width: 158.59, + ), + ColorBox( + label: 'Outline', + tone: dark ? 'NV-60' : 'NV-50', + color: scheme.outline, + onColor: scheme.surface, + height: 40, + width: 158.59, + ), + ColorBox( + label: 'Outline Variant', + tone: dark ? 'NV-30' : 'NV-80', + color: scheme.outlineVariant, + onColor: scheme.onSurface, + height: 40, + width: 158.59, + ), + ], + ), + ], + ), + const SizedBox(width: 20), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + ColorBox( + label: 'Error', + tone: dark ? 'E-80' : 'E-40', + color: scheme.error, + onColor: scheme.onError, + height: 87, + width: 208, + ), + ColorBox( + label: 'On Error', + tone: dark ? 'E-20' : 'E-100', + color: scheme.onError, + onColor: scheme.error, + height: 40, + width: 208, + ), + const SizedBox(height: 5), + ColorBox( + label: 'Error Container', + tone: dark ? 'E-30' : 'E-90', + color: scheme.errorContainer, + onColor: scheme.onErrorContainer, + height: 87, + width: 208, + ), + ColorBox( + label: 'On Error Container', + tone: dark ? 'E-90' : 'E-10', + color: scheme.onErrorContainer, + onColor: scheme.errorContainer, + height: 40, + width: 208, + ), + const SizedBox(height: 20), + ColorBox( + label: 'Inverse Surface', + tone: dark ? 'N-90' : 'N-20', + color: scheme.inverseSurface, + onColor: scheme.onInverseSurface, + height: 120, + width: 208, + ), + ColorBox( + label: 'Inverse On Surface', + tone: dark ? 'N-20' : 'N-95', + color: scheme.onInverseSurface, + onColor: scheme.inverseSurface, + height: 40, + width: 208, + ), + const SizedBox(height: 5), + ColorBox( + label: 'Inverse Primary', + tone: dark ? 'P-40' : 'P-80', + color: scheme.inversePrimary, + onColor: scheme.onSurface, + height: 40, + width: 208, + ), + const SizedBox(height: 16), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + ColorBox( + label: 'Scrim', + tone: 'N-0', + color: scheme.scrim, + onColor: Colors.white, + height: 40, + width: 96.31, + ), + const SizedBox(width: 20), + ColorBox( + label: 'Shadow', + tone: 'N-0', + color: scheme.shadow, + onColor: Colors.white, + height: 40, + width: 96.31, + ), + ], + ), + const SizedBox(height: 8), + ], + ), + ], + ), + ], + ), + ), + ), + ); + } +} diff --git a/material_3_demo/lib/src/typography_screen.dart b/material_3_demo/lib/src/typography_screen.dart new file mode 100644 index 0000000..e23ef5d --- /dev/null +++ b/material_3_demo/lib/src/typography_screen.dart @@ -0,0 +1,105 @@ +// Copyright 2021 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. + +import 'package:flutter/material.dart'; + +class TypographyScreen extends StatelessWidget { + const TypographyScreen({super.key}); + + @override + Widget build(BuildContext context) { + final textTheme = + Theme.of( + context, + ).textTheme.apply( + displayColor: Theme.of(context).colorScheme.onSurface, + ); + return Expanded( + child: ListView( + children: [ + const SizedBox(height: 8), + TextStyleExample( + name: 'Display Large', + style: textTheme.displayLarge!, + ), + TextStyleExample( + name: 'Display Medium', + style: textTheme.displayMedium!, + ), + TextStyleExample( + name: 'Display Small', + style: textTheme.displaySmall!, + ), + TextStyleExample( + name: 'Headline Large', + style: textTheme.headlineLarge!, + ), + TextStyleExample( + name: 'Headline Medium', + style: textTheme.headlineMedium!, + ), + TextStyleExample( + name: 'Headline Small', + style: textTheme.headlineSmall!, + ), + TextStyleExample( + name: 'Title Large', + style: textTheme.titleLarge!, + ), + TextStyleExample( + name: 'Title Medium', + style: textTheme.titleMedium!, + ), + TextStyleExample( + name: 'Title Small', + style: textTheme.titleSmall!, + ), + TextStyleExample( + name: 'Label Large', + style: textTheme.labelLarge!, + ), + TextStyleExample( + name: 'Label Medium', + style: textTheme.labelMedium!, + ), + TextStyleExample( + name: 'Label Small', + style: textTheme.labelSmall!, + ), + TextStyleExample( + name: 'Body Large', + style: textTheme.bodyLarge!, + ), + TextStyleExample( + name: 'Body Medium', + style: textTheme.bodyMedium!, + ), + TextStyleExample( + name: 'Body Small', + style: textTheme.bodySmall!, + ), + ], + ), + ); + } +} + +class TextStyleExample extends StatelessWidget { + const TextStyleExample({ + super.key, + required this.name, + required this.style, + }); + + final String name; + final TextStyle style; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Text(name, style: style), + ); + } +} diff --git a/material_3_demo/linux/.gitignore b/material_3_demo/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/material_3_demo/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/material_3_demo/linux/CMakeLists.txt b/material_3_demo/linux/CMakeLists.txt new file mode 100644 index 0000000..4d83984 --- /dev/null +++ b/material_3_demo/linux/CMakeLists.txt @@ -0,0 +1,139 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "material_3_demo") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.material_3_demo") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/material_3_demo/linux/flutter/CMakeLists.txt b/material_3_demo/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/material_3_demo/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/material_3_demo/linux/flutter/generated_plugin_registrant.cc b/material_3_demo/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..f6f23bf --- /dev/null +++ b/material_3_demo/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/material_3_demo/linux/flutter/generated_plugin_registrant.h b/material_3_demo/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/material_3_demo/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/material_3_demo/linux/flutter/generated_plugins.cmake b/material_3_demo/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..f16b4c3 --- /dev/null +++ b/material_3_demo/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/material_3_demo/linux/main.cc b/material_3_demo/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/material_3_demo/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/material_3_demo/linux/my_application.cc b/material_3_demo/linux/my_application.cc new file mode 100644 index 0000000..0eb2ec9 --- /dev/null +++ b/material_3_demo/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "material_3_demo"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "material_3_demo"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/material_3_demo/linux/my_application.h b/material_3_demo/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/material_3_demo/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/material_3_demo/macos/.gitignore b/material_3_demo/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/material_3_demo/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/material_3_demo/macos/Flutter/Flutter-Debug.xcconfig b/material_3_demo/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/material_3_demo/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/material_3_demo/macos/Flutter/Flutter-Release.xcconfig b/material_3_demo/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/material_3_demo/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/material_3_demo/macos/Flutter/GeneratedPluginRegistrant.swift b/material_3_demo/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..8236f57 --- /dev/null +++ b/material_3_demo/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import url_launcher_macos + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) +} diff --git a/material_3_demo/macos/Podfile b/material_3_demo/macos/Podfile new file mode 100644 index 0000000..c795730 --- /dev/null +++ b/material_3_demo/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/material_3_demo/macos/Runner.xcodeproj/project.pbxproj b/material_3_demo/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..ddd81a1 --- /dev/null +++ b/material_3_demo/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,695 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* material_3_demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "material_3_demo.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* material_3_demo.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* material_3_demo.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.material3Demo.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/material_3_demo.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/material_3_demo"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.material3Demo.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/material_3_demo.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/material_3_demo"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.material3Demo.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/material_3_demo.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/material_3_demo"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/material_3_demo/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/material_3_demo/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/material_3_demo/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/material_3_demo/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/material_3_demo/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..7bf6335 --- /dev/null +++ b/material_3_demo/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/material_3_demo/macos/Runner.xcworkspace/contents.xcworkspacedata b/material_3_demo/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/material_3_demo/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/material_3_demo/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/material_3_demo/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/material_3_demo/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/material_3_demo/macos/Runner/AppDelegate.swift b/material_3_demo/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..d53ef64 --- /dev/null +++ b/material_3_demo/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/material_3_demo/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/material_3_demo/macos/Runner/Base.lproj/MainMenu.xib b/material_3_demo/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/material_3_demo/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/material_3_demo/macos/Runner/Configs/AppInfo.xcconfig b/material_3_demo/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..76173be --- /dev/null +++ b/material_3_demo/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = material_3_demo + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.material3Demo + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright Β© 2023 com.example. All rights reserved. diff --git a/material_3_demo/macos/Runner/Configs/Debug.xcconfig b/material_3_demo/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/material_3_demo/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/material_3_demo/macos/Runner/Configs/Release.xcconfig b/material_3_demo/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/material_3_demo/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/material_3_demo/macos/Runner/Configs/Warnings.xcconfig b/material_3_demo/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/material_3_demo/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/material_3_demo/macos/Runner/DebugProfile.entitlements b/material_3_demo/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..08c3ab1 --- /dev/null +++ b/material_3_demo/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + com.apple.security.network.client + + + diff --git a/material_3_demo/macos/Runner/Info.plist b/material_3_demo/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/material_3_demo/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/material_3_demo/macos/Runner/MainFlutterWindow.swift b/material_3_demo/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..2722837 --- /dev/null +++ b/material_3_demo/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/material_3_demo/macos/Runner/Release.entitlements b/material_3_demo/macos/Runner/Release.entitlements new file mode 100644 index 0000000..ee95ab7 --- /dev/null +++ b/material_3_demo/macos/Runner/Release.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/material_3_demo/macos/RunnerTests/RunnerTests.swift b/material_3_demo/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..5418c9f --- /dev/null +++ b/material_3_demo/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import FlutterMacOS +import Cocoa +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/material_3_demo/pubspec.yaml b/material_3_demo/pubspec.yaml new file mode 100644 index 0000000..06e39a5 --- /dev/null +++ b/material_3_demo/pubspec.yaml @@ -0,0 +1,26 @@ +name: material_3_demo +description: + A Flutter project showcasing supported Material 3 components, typography, color system and elevation. + Supports different light/dark mode, color seed, and comparison to Material 2. +publish_to: "none" +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^1.0.2 + url_launcher: ^6.1.8 + +dev_dependencies: + analysis_defaults: + path: ../analysis_defaults + flutter_test: + sdk: flutter + integration_test: + sdk: flutter +flutter: + uses-material-design: true diff --git a/material_3_demo/test/color_screen_test.dart b/material_3_demo/test/color_screen_test.dart new file mode 100644 index 0000000..5fd534e --- /dev/null +++ b/material_3_demo/test/color_screen_test.dart @@ -0,0 +1,162 @@ +// Copyright 2021 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. + +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:material_3_demo/main.dart'; +import 'package:material_3_demo/src/color_box.dart'; +import 'package:material_3_demo/src/color_palettes_screen.dart'; +import 'package:material_3_demo/src/scheme.dart'; + +import 'component_screen_test.dart'; + +void main() { + testWidgets( + 'Color palettes screen shows correctly when color icon is clicked ' + 'on NavigationBar', + (tester) async { + widgetSetup(tester, 449); + addTearDown(tester.view.resetPhysicalSize); + await tester.pumpWidget(const App()); + + expect(find.text('Light ColorScheme'), findsNothing); + expect(find.text('Dark ColorScheme'), findsNothing); + expect(find.byType(NavigationBar), findsOneWidget); + Finder colorIconOnBar = find.descendant( + of: find.byType(NavigationBar), + matching: find.widgetWithIcon( + NavigationDestination, + Icons.format_paint_outlined, + ), + ); + expect(colorIconOnBar, findsOneWidget); + await tester.tap(colorIconOnBar); + await tester.pumpAndSettle(const Duration(microseconds: 500)); + expect(colorIconOnBar, findsNothing); + + Finder selectedColorIconOnBar = find.descendant( + of: find.byType(NavigationBar), + matching: find.widgetWithIcon( + NavigationDestination, + Icons.format_paint, + ), + ); + expect(selectedColorIconOnBar, findsOneWidget); + expect(find.text('Light ColorScheme'), findsOneWidget); + expect(find.text('Dark ColorScheme'), findsOneWidget); + }, + ); + + testWidgets( + 'Color palettes screen shows correctly when color icon is clicked ' + 'on NavigationRail', + (tester) async { + widgetSetup( + tester, + 1200, + ); // NavigationRail shows only when width is > 1000. + addTearDown(tester.view.resetPhysicalSize); + await tester.pumpWidget(const App()); + await tester.pumpAndSettle(); + expect(find.text('Light ColorScheme'), findsNothing); + expect(find.text('Dark ColorScheme'), findsNothing); + Finder colorIconOnRail = find.descendant( + of: find.byType(NavigationRail), + matching: find.byIcon(Icons.format_paint_outlined), + ); + expect(colorIconOnRail, findsOneWidget); + await tester.tap(colorIconOnRail); + await tester.pumpAndSettle(const Duration(microseconds: 500)); + expect(colorIconOnRail, findsNothing); + Finder selectedColorIconOnRail = find.descendant( + of: find.byType(NavigationRail), + matching: find.byIcon(Icons.format_paint), + ); + expect(selectedColorIconOnRail, findsOneWidget); + expect(find.text('Light ColorScheme'), findsOneWidget); + expect(find.text('Dark ColorScheme'), findsOneWidget); + }, + ); + + testWidgets('Color screen shows correct content', (tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Scaffold(body: Row(children: [ColorPalettesScreen()])), + ), + ); + expect(find.text('Light ColorScheme'), findsOneWidget); + expect(find.text('Dark ColorScheme'), findsOneWidget); + expect( + find.byType(SchemePreview, skipOffstage: false), + findsNWidgets(2), + ); + }); + + testWidgets( + 'ColorBox displays correct info and copies hex color on button tap', + (tester) async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMessageHandler('flutter/platform', (_) async { + // To intercept method calls to 'Clipboard.setData' + return const JSONMethodCodec().encodeSuccessEnvelope(null); + }); + const hexColor = 0xFF3d3d8d; + const testColor = Color(hexColor); + const onTestColor = Colors.white; + const testLabel = 'Test Label'; + const testTone = '50'; + + final gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + + // Wrap in MaterialApp + Scaffold so we can show SnackBars + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ColorBox( + label: testLabel, + tone: testTone, + color: testColor, + onColor: onTestColor, + height: 100, + width: 100, + displayPaletteInfo: true, + ), + ), + ), + ); + + expect(find.text(testLabel), findsOneWidget); + expect(find.text(testTone), findsOneWidget); + + // The copy icon should NOT be there initially (only appears on hover). + expect(find.byIcon(Icons.copy), findsNothing); + + await gesture.addPointer(location: Offset.zero); + addTearDown(gesture.removePointer); + await tester.pump(); + await gesture.moveTo(tester.getCenter(find.byType(ColorBox))); + await tester.pumpAndSettle(); + + expect(find.byIcon(Icons.copy), findsOneWidget); + + // Tap the copy icon, which copies the hex to clipboard and shows a SnackBar. + await tester.tap(find.byIcon(Icons.copy)); + await tester.pumpAndSettle(); + + expect(find.byType(SnackBar), findsOneWidget); + + expect( + find.text( + 'Copied #${hexColor.toRadixString(16).substring(2)} to clipboard', + ), + findsOneWidget, + ); + }, + ); +} diff --git a/material_3_demo/test/component_screen_test.dart b/material_3_demo/test/component_screen_test.dart new file mode 100644 index 0000000..16169f5 --- /dev/null +++ b/material_3_demo/test/component_screen_test.dart @@ -0,0 +1,275 @@ +// Copyright 2021 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. + +// ignore_for_file: avoid_types_on_closure_parameters +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:material_3_demo/main.dart'; +import 'package:material_3_demo/src/component_screen.dart'; + +void main() { + testWidgets('Default main page shows all M3 components', (tester) async { + widgetSetup(tester, 800, windowHeight: 7500); + await tester.pumpWidget(const App()); + + // Elements on the app bar + expect(find.text('Material 3'), findsOneWidget); + expect( + find.widgetWithIcon(AppBar, Icons.dark_mode_outlined), + findsOneWidget, + ); + expect(find.widgetWithIcon(AppBar, Icons.filter_2), findsOneWidget); + expect( + find.widgetWithIcon(AppBar, Icons.palette_outlined), + findsOneWidget, + ); + + // Elements on the component screen + // Common buttons + expect( + find.widgetWithText(ElevatedButton, 'Elevated'), + findsNWidgets(2), + ); + expect(find.widgetWithText(FilledButton, 'Filled'), findsNWidgets(2)); + expect( + find.widgetWithText(FilledButton, 'Filled tonal'), + findsNWidgets(2), + ); + expect( + find.widgetWithText(OutlinedButton, 'Outlined'), + findsNWidgets(2), + ); + expect(find.widgetWithText(TextButton, 'Text'), findsNWidgets(2)); + expect(find.widgetWithText(Buttons, 'Icon'), findsNWidgets(5)); + + // FABs + expect( + find.byType(FloatingActionButton), + findsNWidgets(6), + ); // 2 more show up in the bottom app bar. 1 more in the navigation rail + expect( + find.widgetWithText(FloatingActionButton, 'Create'), + findsOneWidget, + ); + + // Icon buttons + expect( + find.widgetWithIcon(IconButton, Icons.settings_outlined), + findsNWidgets(8), + ); + + // Segmented buttons + expect(find.byType(SegmentedButton), findsOneWidget); + expect(find.byType(SegmentedButton), findsOneWidget); + + // Badges + expect(find.byType(Badge), findsNWidgets(4)); + + // Progress indicators + Finder circularProgressIndicator = find.byType( + CircularProgressIndicator, + ); + expect(circularProgressIndicator, findsOneWidget); + Finder linearProgressIndicator = find.byType(LinearProgressIndicator); + expect(linearProgressIndicator, findsOneWidget); + + // Snackbar + expect( + find.widgetWithText(TextButton, 'Show snackbar'), + findsOneWidget, + ); + + // Bottom sheet + expect( + find.widgetWithText(TextButton, 'Show modal bottom sheet'), + findsOneWidget, + ); + expect( + find.widgetWithText(TextButton, 'Show bottom sheet'), + findsOneWidget, + ); + + // Cards + expect(find.widgetWithText(Cards, 'Elevated'), findsOneWidget); + expect(find.widgetWithText(Cards, 'Filled'), findsOneWidget); + expect(find.widgetWithText(Cards, 'Outlined'), findsOneWidget); + + // Carousels + expect(find.byType(CarouselView), findsNWidgets(2)); + + // Dialogs + expect(find.widgetWithText(TextButton, 'Show dialog'), findsOneWidget); + expect( + find.widgetWithText(TextButton, 'Show full-screen dialog'), + findsOneWidget, + ); + + // Dividers + expect(find.byKey(const Key('divider')), findsOneWidget); + + // Bottom app bar + expect(find.byType(BottomAppBar), findsOneWidget); + + // Navigation bar + // Third one is off screen in the scaffold + expect(find.byType(NavigationBar), findsNWidgets(3)); + + // Navigation drawer + expect(find.byType(Drawer), findsOneWidget); + expect( + find.widgetWithText(TextButton, 'Show modal navigation drawer'), + findsOneWidget, + ); + + // Navigation rail + // Second one is off screen in the scaffold + expect(find.byType(NavigationRail), findsNWidgets(2)); + + // Tabs + expect(find.byType(TabBar), findsOneWidget); + + // Search + expect(find.byType(SearchBar), findsOneWidget); + + // Top app bars + expect(find.byType(AppBar), findsNWidgets(6)); + + // Checkboxes + Finder checkboxExample = find.byType(CheckboxListTile); + expect(checkboxExample, findsNWidgets(4)); + + // Chips + expect( + find.byType(ActionChip), + findsNWidgets(4), + ); // includes Assist and Suggestion chip. + expect(find.byType(FilterChip), findsNWidgets(2)); + expect(find.byType(InputChip), findsNWidgets(2)); + + // Date and time pickers + expect( + find.widgetWithText(DatePicker, 'Show date picker'), + findsOneWidget, + ); + expect( + find.widgetWithText(TimePicker, 'Show time picker'), + findsOneWidget, + ); + + // Menus + expect(find.byType(MenuAnchor), findsNWidgets(5)); + expect(find.byType(DropdownMenu), findsOneWidget); + expect(find.byType(DropdownMenu), findsOneWidget); + + // Radios + Finder radioExample = find.byType(RadioListTile); + expect(radioExample, findsNWidgets(3)); + + // Sliders + expect(find.byType(Slider), findsNWidgets(2)); + + // Switches + expect(find.byType(Switch), findsNWidgets(4)); + + // TextFields + expect(find.widgetWithText(TextField, 'Disabled'), findsNWidgets(2)); + expect(find.widgetWithText(TextField, 'Filled'), findsNWidgets(2)); + expect(find.widgetWithText(TextField, 'Outlined'), findsNWidgets(2)); + }); + + testWidgets( + 'NavigationRail doesn\'t show when width value is small than 1000 ' + '(in Portrait mode or narrow screen)', + (tester) async { + widgetSetup(tester, 999, windowHeight: 7000); + await tester.pumpWidget(const App()); + await tester.pumpAndSettle(); + + // When screen width is less than 1000, NavigationBar will show. At the same + // time, the NavigationBar example still show up in the navigation group. + expect( + find.byType(NavigationBars), + findsNWidgets(3), + ); // The real navBar, badges example and navBar example + expect( + find.widgetWithText(NavigationBar, 'Components'), + findsOneWidget, + ); + expect(find.widgetWithText(NavigationBar, 'Color'), findsOneWidget); + expect( + find.widgetWithText(NavigationBar, 'Typography'), + findsOneWidget, + ); + expect( + find.widgetWithText(NavigationBar, 'Elevation'), + findsOneWidget, + ); + + expect( + find.widgetWithText(NavigationBar, 'Explore'), + findsOneWidget, + ); + expect(find.widgetWithText(NavigationBar, 'Pets'), findsOneWidget); + expect( + find.widgetWithText(NavigationBar, 'Account'), + findsOneWidget, + ); + }, + ); + + testWidgets( + 'NavigationRail shows when width value is greater than or equal ' + 'to 1000 (in Landscape mode or wider screen)', + (tester) async { + widgetSetup(tester, 1001, windowHeight: 3000); + await tester.pumpWidget(const App()); + await tester.pumpAndSettle(); + + // When screen width is greater than or equal to 1000, NavigationRail will show. + // At the same time, the NavigationBar will NOT show. + expect(find.byType(NavigationRail), findsNWidgets(2)); + expect(find.byType(Tooltip, skipOffstage: false), findsWidgets); + expect( + find.widgetWithText(NavigationRail, 'Components'), + findsOneWidget, + ); + expect(find.widgetWithText(NavigationRail, 'Color'), findsOneWidget); + expect( + find.widgetWithText(NavigationRail, 'Typography'), + findsOneWidget, + ); + expect( + find.widgetWithText(NavigationRail, 'Elevation'), + findsOneWidget, + ); + + expect( + find.widgetWithText(NavigationBar, 'Explore'), + findsOneWidget, + ); + expect(find.widgetWithText(NavigationBar, 'Pets'), findsOneWidget); + expect( + find.widgetWithText(NavigationBar, 'Account'), + findsOneWidget, + ); + + // the Navigation bar should be out of screen. + final RenderBox box = tester.renderObject( + find.widgetWithText(NavigationBar, 'Components'), + ); + expect(box.localToGlobal(Offset.zero), const Offset(0.0, 3080.0)); + }, + ); +} + +void widgetSetup( + WidgetTester tester, + double windowWidth, { + double? windowHeight, +}) { + final height = windowHeight ?? 846; + tester.view.devicePixelRatio = 2; + final dpi = tester.view.devicePixelRatio; + tester.view.physicalSize = Size(windowWidth * dpi, height * dpi); +} diff --git a/material_3_demo/test/elevation_screen_test.dart b/material_3_demo/test/elevation_screen_test.dart new file mode 100644 index 0000000..a9f3d12 --- /dev/null +++ b/material_3_demo/test/elevation_screen_test.dart @@ -0,0 +1,92 @@ +// Copyright 2021 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. + +// ignore_for_file: avoid_types_on_closure_parameters +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:material_3_demo/main.dart'; +import 'package:material_3_demo/src/elevation_screen.dart'; + +import 'component_screen_test.dart'; + +void main() { + testWidgets( + 'Surface Tones screen shows correctly when the corresponding icon is ' + 'selected on NavigationBar', + (tester) async { + widgetSetup(tester, 449); + addTearDown(tester.view.resetPhysicalSize); + await tester.pumpWidget(const App()); + + expect(find.text('Surface Tint Color Only'), findsNothing); + expect(find.byType(NavigationBar), findsOneWidget); + Finder tintIconOnBar = find.descendant( + of: find.byType(NavigationBar), + matching: find.widgetWithIcon( + NavigationDestination, + Icons.invert_colors_on_outlined, + ), + ); + expect(tintIconOnBar, findsOneWidget); + await tester.tap(tintIconOnBar); + await tester.pumpAndSettle(const Duration(microseconds: 500)); + expect(tintIconOnBar, findsNothing); + Finder selectedTintIconOnBar = find.descendant( + of: find.byType(NavigationBar), + matching: find.widgetWithIcon( + NavigationDestination, + Icons.opacity, + ), + ); + expect(selectedTintIconOnBar, findsOneWidget); + expect(find.text('Surface Tint Color Only'), findsOneWidget); + }, + ); + + testWidgets( + 'Surface Tones screen shows correctly when the corresponding icon is ' + 'selected on NavigationRail', + (tester) async { + widgetSetup( + tester, + 1200, + ); // NavigationRail shows only when width is > 1000. + addTearDown(tester.view.resetPhysicalSize); + await tester.pumpWidget(const App()); + expect(find.text('Surface Tint Color Only'), findsNothing); + Finder tintIconOnRail = find.descendant( + of: find.byType(NavigationRail), + matching: find.byIcon(Icons.invert_colors_on_outlined), + ); + expect(tintIconOnRail, findsOneWidget); + await tester.tap(tintIconOnRail); + await tester.pumpAndSettle(const Duration(microseconds: 500)); + expect(tintIconOnRail, findsNothing); + Finder selectedTintIconOnRail = find.descendant( + of: find.byType(NavigationRail), + matching: find.byIcon(Icons.opacity), + ); + expect(selectedTintIconOnRail, findsOneWidget); + expect(find.text('Surface Tint Color Only'), findsOneWidget); + }, + ); + + testWidgets('Surface Tones screen shows correct content', ( + tester, + ) async { + await tester.pumpWidget( + const MaterialApp( + home: Scaffold(body: Row(children: [ElevationScreen()])), + ), + ); + expect(find.text('Surface Tint Color Only'), findsOneWidget); + expect( + find.text('Surface Tint Color and Shadow Color'), + findsOneWidget, + ); + expect(find.text('Shadow Color Only'), findsOneWidget); + expect(find.byType(ElevationGrid), findsNWidgets(3)); + expect(find.byType(ElevationCard), findsNWidgets(18)); + }); +} diff --git a/material_3_demo/test/typography_screen_test.dart b/material_3_demo/test/typography_screen_test.dart new file mode 100644 index 0000000..bb7bfd7 --- /dev/null +++ b/material_3_demo/test/typography_screen_test.dart @@ -0,0 +1,94 @@ +// Copyright 2021 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. + +// ignore_for_file: avoid_types_on_closure_parameters +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:material_3_demo/main.dart'; +import 'package:material_3_demo/src/typography_screen.dart'; + +import 'component_screen_test.dart'; + +void main() { + testWidgets( + 'Typography screen shows correctly when the corresponding icon is ' + 'selected on NavigationBar', + (tester) async { + widgetSetup(tester, 449); + addTearDown(tester.view.resetPhysicalSize); + await tester.pumpWidget(const App()); + + expect(find.text('Display Large'), findsNothing); + expect(find.byType(NavigationBar), findsOneWidget); + Finder textIconOnBar = find.descendant( + of: find.byType(NavigationBar), + matching: find.byIcon(Icons.text_snippet_outlined), + ); + expect(textIconOnBar, findsOneWidget); + await tester.tap(textIconOnBar); + await tester.pumpAndSettle(const Duration(microseconds: 500)); + expect(textIconOnBar, findsNothing); + Finder selectedTextIconOnBar = find.descendant( + of: find.byType(NavigationBar), + matching: find.byIcon(Icons.text_snippet), + ); + expect(selectedTextIconOnBar, findsOneWidget); + expect(find.text('Display Large'), findsOneWidget); + }, + ); + + testWidgets( + 'Typography screen shows correctly when the corresponding icon is ' + 'selected on NavigationRail', + (tester) async { + widgetSetup( + tester, + 1200, + ); // NavigationRail shows only when width is > 1000. + addTearDown(tester.view.resetPhysicalSize); + await tester.pumpWidget(const App()); + expect(find.text('Display Large'), findsNothing); + Finder textIconOnRail = find.descendant( + of: find.byType(NavigationRail), + matching: find.byIcon(Icons.text_snippet_outlined), + ); + expect(textIconOnRail, findsOneWidget); + await tester.tap(textIconOnRail); + await tester.pumpAndSettle(const Duration(microseconds: 500)); + expect(textIconOnRail, findsNothing); + Finder selectedTextIconOnRail = find.descendant( + of: find.byType(NavigationRail), + matching: find.byIcon(Icons.text_snippet), + ); + expect(selectedTextIconOnRail, findsOneWidget); + expect(find.text('Display Large'), findsOneWidget); + }, + ); + + testWidgets('Typography screen shows correct content', (tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Scaffold(body: Row(children: [TypographyScreen()])), + ), + ); + expect(find.text('Display Large'), findsOneWidget); + expect(find.text('Display Medium'), findsOneWidget); + expect(find.text('Display Small'), findsOneWidget); + expect(find.text('Headline Large'), findsOneWidget); + expect(find.text('Headline Medium'), findsOneWidget); + expect(find.text('Headline Small'), findsOneWidget); + expect(find.text('Title Large'), findsOneWidget); + expect(find.text('Title Medium'), findsOneWidget); + expect(find.text('Title Small'), findsOneWidget); + await tester.scrollUntilVisible(find.text('Body Small'), 500.0); + expect(find.text('Label Large'), findsOneWidget); + expect(find.text('Label Medium'), findsOneWidget); + expect(find.text('Label Small'), findsOneWidget); + expect(find.text('Body Large'), findsOneWidget); + expect(find.text('Body Medium'), findsOneWidget); + expect(find.text('Body Small'), findsOneWidget); + + expect(find.byType(TextStyleExample), findsNWidgets(15)); + }); +} diff --git a/material_3_demo/web/favicon.png b/material_3_demo/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/material_3_demo/web/favicon.png differ diff --git a/material_3_demo/web/icons/Icon-192.png b/material_3_demo/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/material_3_demo/web/icons/Icon-192.png differ diff --git a/material_3_demo/web/icons/Icon-512.png b/material_3_demo/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/material_3_demo/web/icons/Icon-512.png differ diff --git a/material_3_demo/web/icons/Icon-maskable-192.png b/material_3_demo/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/material_3_demo/web/icons/Icon-maskable-192.png differ diff --git a/material_3_demo/web/icons/Icon-maskable-512.png b/material_3_demo/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/material_3_demo/web/icons/Icon-maskable-512.png differ diff --git a/material_3_demo/web/index.html b/material_3_demo/web/index.html new file mode 100644 index 0000000..41d0620 --- /dev/null +++ b/material_3_demo/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + material_3_demo + + + + + + diff --git a/material_3_demo/web/manifest.json b/material_3_demo/web/manifest.json new file mode 100644 index 0000000..0d59af8 --- /dev/null +++ b/material_3_demo/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "material_3_demo", + "short_name": "material_3_demo", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/material_3_demo/windows/.gitignore b/material_3_demo/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/material_3_demo/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/material_3_demo/windows/CMakeLists.txt b/material_3_demo/windows/CMakeLists.txt new file mode 100644 index 0000000..dec9d0b --- /dev/null +++ b/material_3_demo/windows/CMakeLists.txt @@ -0,0 +1,102 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(material_3_demo LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "material_3_demo") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/material_3_demo/windows/flutter/CMakeLists.txt b/material_3_demo/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..930d207 --- /dev/null +++ b/material_3_demo/windows/flutter/CMakeLists.txt @@ -0,0 +1,104 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/material_3_demo/windows/flutter/generated_plugin_registrant.cc b/material_3_demo/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..4f78848 --- /dev/null +++ b/material_3_demo/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/material_3_demo/windows/flutter/generated_plugin_registrant.h b/material_3_demo/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/material_3_demo/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/material_3_demo/windows/flutter/generated_plugins.cmake b/material_3_demo/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..88b22e5 --- /dev/null +++ b/material_3_demo/windows/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/material_3_demo/windows/runner/CMakeLists.txt b/material_3_demo/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/material_3_demo/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/material_3_demo/windows/runner/Runner.rc b/material_3_demo/windows/runner/Runner.rc new file mode 100644 index 0000000..0a24f5f --- /dev/null +++ b/material_3_demo/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "material_3_demo" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "material_3_demo" "\0" + VALUE "LegalCopyright", "Copyright (C) 2023 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "material_3_demo.exe" "\0" + VALUE "ProductName", "material_3_demo" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/material_3_demo/windows/runner/flutter_window.cpp b/material_3_demo/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..b25e363 --- /dev/null +++ b/material_3_demo/windows/runner/flutter_window.cpp @@ -0,0 +1,66 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/material_3_demo/windows/runner/flutter_window.h b/material_3_demo/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/material_3_demo/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/material_3_demo/windows/runner/main.cpp b/material_3_demo/windows/runner/main.cpp new file mode 100644 index 0000000..8585dc2 --- /dev/null +++ b/material_3_demo/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"material_3_demo", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/material_3_demo/windows/runner/resource.h b/material_3_demo/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/material_3_demo/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/material_3_demo/windows/runner/resources/app_icon.ico b/material_3_demo/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/material_3_demo/windows/runner/resources/app_icon.ico differ diff --git a/material_3_demo/windows/runner/runner.exe.manifest b/material_3_demo/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..a42ea76 --- /dev/null +++ b/material_3_demo/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/material_3_demo/windows/runner/utils.cpp b/material_3_demo/windows/runner/utils.cpp new file mode 100644 index 0000000..b2b0873 --- /dev/null +++ b/material_3_demo/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/material_3_demo/windows/runner/utils.h b/material_3_demo/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/material_3_demo/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/material_3_demo/windows/runner/win32_window.cpp b/material_3_demo/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/material_3_demo/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/material_3_demo/windows/runner/win32_window.h b/material_3_demo/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/material_3_demo/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/navigation_and_routing/.gitignore b/navigation_and_routing/.gitignore new file mode 100644 index 0000000..69ef9b0 --- /dev/null +++ b/navigation_and_routing/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/navigation_and_routing/.metadata b/navigation_and_routing/.metadata new file mode 100644 index 0000000..d22992e --- /dev/null +++ b/navigation_and_routing/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "db7ef5bf9f59442b0e200a90587e8fa5e0c6336a" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: android + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: ios + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: linux + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: macos + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: web + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: windows + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/navigation_and_routing/README.md b/navigation_and_routing/README.md new file mode 100644 index 0000000..72a1e7d --- /dev/null +++ b/navigation_and_routing/README.md @@ -0,0 +1,12 @@ +# Navigation and Routing +A sample that shows how to use [go_router](https://pub.dev/packages/go_router) +API to handle common navigation scenarios. + +## Goals +- Demonstrate common navigation scenarios: + - Parsing path parameters (`/user/:id`) + - Sign in (redirection) + - Nested navigation using ShellRoute +- Demonstrate how [deep linking](https://docs.flutter.dev/ui/navigation/deep-linking#get-started) is configured on iOS and Android +- Demonstrate how to use the Link widget from `package:url_Launcher` with the + Router API. diff --git a/navigation_and_routing/analysis_options.yaml b/navigation_and_routing/analysis_options.yaml new file mode 100644 index 0000000..5e2133e --- /dev/null +++ b/navigation_and_routing/analysis_options.yaml @@ -0,0 +1 @@ +include: ../analysis_options.yaml diff --git a/navigation_and_routing/android/.gitignore b/navigation_and_routing/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/navigation_and_routing/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/navigation_and_routing/android/app/build.gradle b/navigation_and_routing/android/app/build.gradle new file mode 100644 index 0000000..ad0c905 --- /dev/null +++ b/navigation_and_routing/android/app/build.gradle @@ -0,0 +1,67 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +android { + namespace "dev.flutter.navigation_and_routing" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "dev.flutter.navigation_and_routing" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies {} diff --git a/navigation_and_routing/android/app/src/debug/AndroidManifest.xml b/navigation_and_routing/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/navigation_and_routing/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/navigation_and_routing/android/app/src/main/AndroidManifest.xml b/navigation_and_routing/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..eb496d7 --- /dev/null +++ b/navigation_and_routing/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/navigation_and_routing/android/app/src/main/kotlin/dev/flutter/navigation_and_routing/MainActivity.kt b/navigation_and_routing/android/app/src/main/kotlin/dev/flutter/navigation_and_routing/MainActivity.kt new file mode 100644 index 0000000..86b0945 --- /dev/null +++ b/navigation_and_routing/android/app/src/main/kotlin/dev/flutter/navigation_and_routing/MainActivity.kt @@ -0,0 +1,6 @@ +package dev.flutter.navigation_and_routing + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/navigation_and_routing/android/app/src/main/res/drawable-v21/launch_background.xml b/navigation_and_routing/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/navigation_and_routing/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/navigation_and_routing/android/app/src/main/res/drawable/launch_background.xml b/navigation_and_routing/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/navigation_and_routing/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/navigation_and_routing/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/navigation_and_routing/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/navigation_and_routing/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/navigation_and_routing/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/navigation_and_routing/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/navigation_and_routing/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/navigation_and_routing/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/navigation_and_routing/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/navigation_and_routing/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/navigation_and_routing/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/navigation_and_routing/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/navigation_and_routing/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/navigation_and_routing/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/navigation_and_routing/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/navigation_and_routing/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/navigation_and_routing/android/app/src/main/res/values-night/styles.xml b/navigation_and_routing/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/navigation_and_routing/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/navigation_and_routing/android/app/src/main/res/values/styles.xml b/navigation_and_routing/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/navigation_and_routing/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/navigation_and_routing/android/app/src/profile/AndroidManifest.xml b/navigation_and_routing/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/navigation_and_routing/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/navigation_and_routing/android/build.gradle b/navigation_and_routing/android/build.gradle new file mode 100644 index 0000000..e83fb5d --- /dev/null +++ b/navigation_and_routing/android/build.gradle @@ -0,0 +1,30 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/navigation_and_routing/android/gradle.properties b/navigation_and_routing/android/gradle.properties new file mode 100644 index 0000000..598d13f --- /dev/null +++ b/navigation_and_routing/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G +android.useAndroidX=true +android.enableJetifier=true diff --git a/navigation_and_routing/android/gradle/wrapper/gradle-wrapper.properties b/navigation_and_routing/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/navigation_and_routing/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/navigation_and_routing/android/settings.gradle b/navigation_and_routing/android/settings.gradle new file mode 100644 index 0000000..7cd7128 --- /dev/null +++ b/navigation_and_routing/android/settings.gradle @@ -0,0 +1,29 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + + plugins { + id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false +} + +include ":app" diff --git a/navigation_and_routing/codelab_rebuild.yaml b/navigation_and_routing/codelab_rebuild.yaml new file mode 100644 index 0000000..d9c4dc4 --- /dev/null +++ b/navigation_and_routing/codelab_rebuild.yaml @@ -0,0 +1,43 @@ +# Run with tooling from https://github.com/flutter/codelabs/tree/main/tooling/codelab_rebuild +name: Navigation and Routing rebuild script +steps: + - name: Remove runners + rmdirs: + - android + - ios + - linux + - macos + - web + - windows + - name: Flutter recreate + flutter: create --org dev.flutter . + - name: Patch web/index.html + path: web/index.html + patch-u: | + --- b/navigation_and_routing/web/index.html + +++ a/navigation_and_routing/web/index.html + @@ -18,7 +18,7 @@ + + + + - + + + + + + - name: Patch web/manifest.json + path: web/manifest.json + patch-u: | + --- b/navigation_and_routing/web/manifest.json + +++ a/navigation_and_routing/web/manifest.json + @@ -5,7 +5,7 @@ + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + - "description": "A new Flutter project.", + + "description": "Navigation and routing sample app", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + - name: Update dependencies + flutter: pub upgrade --major-versions diff --git a/navigation_and_routing/ios/.gitignore b/navigation_and_routing/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/navigation_and_routing/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/navigation_and_routing/ios/Flutter/AppFrameworkInfo.plist b/navigation_and_routing/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/navigation_and_routing/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/navigation_and_routing/ios/Flutter/Debug.xcconfig b/navigation_and_routing/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/navigation_and_routing/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/navigation_and_routing/ios/Flutter/Release.xcconfig b/navigation_and_routing/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/navigation_and_routing/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/navigation_and_routing/ios/Podfile b/navigation_and_routing/ios/Podfile new file mode 100644 index 0000000..fdcc671 --- /dev/null +++ b/navigation_and_routing/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '11.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/navigation_and_routing/ios/Runner.xcodeproj/project.pbxproj b/navigation_and_routing/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..2482460 --- /dev/null +++ b/navigation_and_routing/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,614 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807E294A63A400263BE5 /* Frameworks */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.navigationAndRouting; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.navigationAndRouting.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.navigationAndRouting.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.navigationAndRouting.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.navigationAndRouting; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.navigationAndRouting; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/navigation_and_routing/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/navigation_and_routing/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/navigation_and_routing/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/navigation_and_routing/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/navigation_and_routing/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/navigation_and_routing/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/navigation_and_routing/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/navigation_and_routing/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/navigation_and_routing/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/navigation_and_routing/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/navigation_and_routing/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..87131a0 --- /dev/null +++ b/navigation_and_routing/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/navigation_and_routing/ios/Runner.xcworkspace/contents.xcworkspacedata b/navigation_and_routing/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/navigation_and_routing/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/navigation_and_routing/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/navigation_and_routing/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/navigation_and_routing/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/navigation_and_routing/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/navigation_and_routing/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/navigation_and_routing/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/navigation_and_routing/ios/Runner/AppDelegate.swift b/navigation_and_routing/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/navigation_and_routing/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/navigation_and_routing/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/navigation_and_routing/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/navigation_and_routing/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/navigation_and_routing/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/navigation_and_routing/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/navigation_and_routing/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/navigation_and_routing/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/navigation_and_routing/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/navigation_and_routing/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/navigation_and_routing/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/navigation_and_routing/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/navigation_and_routing/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/navigation_and_routing/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/navigation_and_routing/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/navigation_and_routing/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/navigation_and_routing/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/navigation_and_routing/ios/Runner/Base.lproj/LaunchScreen.storyboard b/navigation_and_routing/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/navigation_and_routing/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/navigation_and_routing/ios/Runner/Base.lproj/Main.storyboard b/navigation_and_routing/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/navigation_and_routing/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/navigation_and_routing/ios/Runner/Info.plist b/navigation_and_routing/ios/Runner/Info.plist new file mode 100644 index 0000000..9a04f33 --- /dev/null +++ b/navigation_and_routing/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Navigation And Routing + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + navigation_and_routing + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/navigation_and_routing/ios/Runner/Runner-Bridging-Header.h b/navigation_and_routing/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/navigation_and_routing/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/navigation_and_routing/ios/RunnerTests/RunnerTests.swift b/navigation_and_routing/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/navigation_and_routing/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/navigation_and_routing/lib/main.dart b/navigation_and_routing/lib/main.dart new file mode 100644 index 0000000..2ce6440 --- /dev/null +++ b/navigation_and_routing/lib/main.dart @@ -0,0 +1,14 @@ +// Copyright 2021, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +import 'package:flutter/material.dart'; + +import 'src/app.dart'; + +void main() { + // Removed setupWindow() call + runApp(const Bookstore()); +} + +const double windowWidth = 480; +const double windowHeight = 854; diff --git a/navigation_and_routing/lib/src/app.dart b/navigation_and_routing/lib/src/app.dart new file mode 100644 index 0000000..884b10a --- /dev/null +++ b/navigation_and_routing/lib/src/app.dart @@ -0,0 +1,279 @@ +// Copyright 2021, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import 'auth.dart'; +import 'data.dart'; +import 'screens/author_details.dart'; +import 'screens/authors.dart'; +import 'screens/book_details.dart'; +import 'screens/books.dart'; +import 'screens/scaffold.dart'; +import 'screens/settings.dart'; +import 'screens/sign_in.dart'; +import 'widgets/book_list.dart'; +import 'widgets/fade_transition_page.dart'; + +final appShellNavigatorKey = GlobalKey( + debugLabel: 'app shell', +); +final booksNavigatorKey = GlobalKey( + debugLabel: 'books shell', +); + +class Bookstore extends StatefulWidget { + const Bookstore({super.key}); + + @override + State createState() => _BookstoreState(); +} + +class _BookstoreState extends State { + final BookstoreAuth auth = BookstoreAuth(); + + @override + Widget build(BuildContext context) { + return MaterialApp.router( + builder: (context, child) { + if (child == null) { + throw ('No child in .router constructor builder'); + } + return BookstoreAuthScope(notifier: auth, child: child); + }, + routerConfig: GoRouter( + refreshListenable: auth, + debugLogDiagnostics: true, + initialLocation: '/books/popular', + redirect: (context, state) { + final signedIn = BookstoreAuth.of(context).signedIn; + if (state.uri.toString() != '/sign-in' && !signedIn) { + return '/sign-in'; + } + return null; + }, + routes: [ + ShellRoute( + navigatorKey: appShellNavigatorKey, + builder: (context, state, child) { + return BookstoreScaffold( + selectedIndex: switch (state.uri.path) { + var p when p.startsWith('/books') => 0, + var p when p.startsWith('/authors') => 1, + var p when p.startsWith('/settings') => 2, + _ => 0, + }, + child: child, + ); + }, + routes: [ + ShellRoute( + pageBuilder: (context, state, child) { + return FadeTransitionPage( + key: state.pageKey, + child: Builder( + builder: (context) { + return BooksScreen( + onTap: (idx) { + GoRouter.of(context).go(switch (idx) { + 0 => '/books/popular', + 1 => '/books/new', + 2 => '/books/all', + _ => '/books/popular', + }); + }, + selectedIndex: switch (state.uri.path) { + var p when p.startsWith('/books/popular') => 0, + var p when p.startsWith('/books/new') => 1, + var p when p.startsWith('/books/all') => 2, + _ => 0, + }, + child: child, + ); + }, + ), + ); + }, + routes: [ + GoRoute( + path: '/books/popular', + pageBuilder: (context, state) { + return FadeTransitionPage( + key: state.pageKey, + child: Builder( + builder: (context) { + return BookList( + books: libraryInstance.popularBooks, + onTap: (book) { + GoRouter.of( + context, + ).go('/books/popular/book/${book.id}'); + }, + ); + }, + ), + ); + }, + routes: [ + GoRoute( + path: 'book/:bookId', + parentNavigatorKey: appShellNavigatorKey, + builder: (context, state) { + return BookDetailsScreen( + book: libraryInstance.getBook( + state.pathParameters['bookId'] ?? '', + ), + ); + }, + ), + ], + ), + GoRoute( + path: '/books/new', + pageBuilder: (context, state) { + return FadeTransitionPage( + key: state.pageKey, + child: Builder( + builder: (context) { + return BookList( + books: libraryInstance.newBooks, + onTap: (book) { + GoRouter.of( + context, + ).go('/books/new/book/${book.id}'); + }, + ); + }, + ), + ); + }, + routes: [ + GoRoute( + path: 'book/:bookId', + parentNavigatorKey: appShellNavigatorKey, + builder: (context, state) { + return BookDetailsScreen( + book: libraryInstance.getBook( + state.pathParameters['bookId'] ?? '', + ), + ); + }, + ), + ], + ), + GoRoute( + path: '/books/all', + pageBuilder: (context, state) { + return FadeTransitionPage( + key: state.pageKey, + child: Builder( + builder: (context) { + return BookList( + books: libraryInstance.allBooks, + onTap: (book) { + GoRouter.of( + context, + ).go('/books/all/book/${book.id}'); + }, + ); + }, + ), + ); + }, + routes: [ + GoRoute( + path: 'book/:bookId', + parentNavigatorKey: appShellNavigatorKey, + builder: (context, state) { + return BookDetailsScreen( + book: libraryInstance.getBook( + state.pathParameters['bookId'] ?? '', + ), + ); + }, + ), + ], + ), + ], + ), + GoRoute( + path: '/authors', + pageBuilder: (context, state) { + return FadeTransitionPage( + key: state.pageKey, + child: Builder( + builder: (context) { + return AuthorsScreen( + onTap: (author) { + GoRouter.of( + context, + ).go('/authors/author/${author.id}'); + }, + ); + }, + ), + ); + }, + routes: [ + GoRoute( + path: 'author/:authorId', + builder: (context, state) { + final author = libraryInstance.allAuthors.firstWhere( + (author) => + author.id == + int.parse(state.pathParameters['authorId']!), + ); + // Use a builder to get the correct BuildContext + return Builder( + builder: (context) { + return AuthorDetailsScreen( + author: author, + onBookTapped: (book) { + GoRouter.of( + context, + ).go('/books/all/book/${book.id}'); + }, + ); + }, + ); + }, + ), + ], + ), + GoRoute( + path: '/settings', + pageBuilder: (context, state) { + return FadeTransitionPage( + key: state.pageKey, + child: const SettingsScreen(), + ); + }, + ), + ], + ), + GoRoute( + path: '/sign-in', + builder: (context, state) { + // Use a builder to get the correct BuildContext + return Builder( + builder: (context) { + return SignInScreen( + onSignIn: (value) async { + final router = GoRouter.of(context); + await BookstoreAuth.of( + context, + ).signIn(value.username, value.password); + router.go('/books/popular'); + }, + ); + }, + ); + }, + ), + ], + ), + ); + } +} diff --git a/navigation_and_routing/lib/src/auth.dart b/navigation_and_routing/lib/src/auth.dart new file mode 100644 index 0000000..9804207 --- /dev/null +++ b/navigation_and_routing/lib/src/auth.dart @@ -0,0 +1,47 @@ +// Copyright 2021, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter/widgets.dart'; + +/// A mock authentication service +class BookstoreAuth extends ChangeNotifier { + bool _signedIn = false; + + bool get signedIn => _signedIn; + + Future signOut() async { + await Future.delayed(const Duration(milliseconds: 200)); + // Sign out. + _signedIn = false; + notifyListeners(); + } + + Future signIn(String username, String password) async { + await Future.delayed(const Duration(milliseconds: 200)); + + // Sign in. Allow any password. + _signedIn = true; + notifyListeners(); + return _signedIn; + } + + @override + bool operator ==(Object other) => + other is BookstoreAuth && other._signedIn == _signedIn; + + @override + int get hashCode => _signedIn.hashCode; + + static BookstoreAuth of(BuildContext context) => context + .dependOnInheritedWidgetOfExactType()! + .notifier!; +} + +class BookstoreAuthScope extends InheritedNotifier { + const BookstoreAuthScope({ + required super.notifier, + required super.child, + super.key, + }); +} diff --git a/navigation_and_routing/lib/src/data.dart b/navigation_and_routing/lib/src/data.dart new file mode 100644 index 0000000..f82a31d --- /dev/null +++ b/navigation_and_routing/lib/src/data.dart @@ -0,0 +1,7 @@ +// Copyright 2021, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +export 'data/author.dart'; +export 'data/book.dart'; +export 'data/library.dart'; diff --git a/navigation_and_routing/lib/src/data/author.dart b/navigation_and_routing/lib/src/data/author.dart new file mode 100644 index 0000000..affd9a2 --- /dev/null +++ b/navigation_and_routing/lib/src/data/author.dart @@ -0,0 +1,13 @@ +// Copyright 2021, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'book.dart'; + +class Author { + final int id; + final String name; + final books = []; + + Author(this.id, this.name); +} diff --git a/navigation_and_routing/lib/src/data/book.dart b/navigation_and_routing/lib/src/data/book.dart new file mode 100644 index 0000000..f91f038 --- /dev/null +++ b/navigation_and_routing/lib/src/data/book.dart @@ -0,0 +1,15 @@ +// Copyright 2021, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'author.dart'; + +class Book { + final int id; + final String title; + final Author author; + final bool isPopular; + final bool isNew; + + Book(this.id, this.title, this.isPopular, this.isNew, this.author); +} diff --git a/navigation_and_routing/lib/src/data/library.dart b/navigation_and_routing/lib/src/data/library.dart new file mode 100644 index 0000000..0afdb59 --- /dev/null +++ b/navigation_and_routing/lib/src/data/library.dart @@ -0,0 +1,67 @@ +// Copyright 2021, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'author.dart'; +import 'book.dart'; + +final libraryInstance = Library() + ..addBook( + title: 'Left Hand of Darkness', + authorName: 'Ursula K. Le Guin', + isPopular: true, + isNew: true, + ) + ..addBook( + title: 'Too Like the Lightning', + authorName: 'Ada Palmer', + isPopular: false, + isNew: true, + ) + ..addBook( + title: 'Kindred', + authorName: 'Octavia E. Butler', + isPopular: true, + isNew: false, + ) + ..addBook( + title: 'The Lathe of Heaven', + authorName: 'Ursula K. Le Guin', + isPopular: false, + isNew: false, + ); + +class Library { + final List allBooks = []; + final List allAuthors = []; + + void addBook({ + required String title, + required String authorName, + required bool isPopular, + required bool isNew, + }) { + var author = allAuthors.firstWhere( + (author) => author.name == authorName, + orElse: () { + final value = Author(allAuthors.length, authorName); + allAuthors.add(value); + return value; + }, + ); + var book = Book(allBooks.length, title, isPopular, isNew, author); + + author.books.add(book); + allBooks.add(book); + } + + Book getBook(String id) { + return allBooks[int.parse(id)]; + } + + List get popularBooks => [ + ...allBooks.where((book) => book.isPopular), + ]; + + List get newBooks => [...allBooks.where((book) => book.isNew)]; +} diff --git a/navigation_and_routing/lib/src/screens/author_details.dart b/navigation_and_routing/lib/src/screens/author_details.dart new file mode 100644 index 0000000..2f12c53 --- /dev/null +++ b/navigation_and_routing/lib/src/screens/author_details.dart @@ -0,0 +1,38 @@ +// Copyright 2021, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter/material.dart'; + +import '../data.dart'; +import '../widgets/book_list.dart'; + +class AuthorDetailsScreen extends StatelessWidget { + final Author author; + final ValueChanged onBookTapped; + + const AuthorDetailsScreen({ + super.key, + required this.author, + required this.onBookTapped, + }); + + @override + Widget build(BuildContext context) => Scaffold( + appBar: AppBar(title: Text(author.name)), + body: Center( + child: Column( + children: [ + Expanded( + child: BookList( + books: author.books, + onTap: (book) { + onBookTapped(book); + }, + ), + ), + ], + ), + ), + ); +} diff --git a/navigation_and_routing/lib/src/screens/authors.dart b/navigation_and_routing/lib/src/screens/authors.dart new file mode 100644 index 0000000..3aeb4b5 --- /dev/null +++ b/navigation_and_routing/lib/src/screens/authors.dart @@ -0,0 +1,26 @@ +// Copyright 2021, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter/material.dart'; + +import '../data/author.dart'; +import '../data/library.dart'; +import '../widgets/author_list.dart'; + +class AuthorsScreen extends StatelessWidget { + final String title; + final ValueChanged onTap; + + const AuthorsScreen({ + required this.onTap, + this.title = 'Authors', + super.key, + }); + + @override + Widget build(BuildContext context) => Scaffold( + appBar: AppBar(title: Text(title)), + body: AuthorList(authors: libraryInstance.allAuthors, onTap: onTap), + ); +} diff --git a/navigation_and_routing/lib/src/screens/book_details.dart b/navigation_and_routing/lib/src/screens/book_details.dart new file mode 100644 index 0000000..4de2017 --- /dev/null +++ b/navigation_and_routing/lib/src/screens/book_details.dart @@ -0,0 +1,64 @@ +// Copyright 2021, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:url_launcher/link.dart'; + +import '../data.dart'; +import 'author_details.dart'; + +class BookDetailsScreen extends StatelessWidget { + final Book? book; + + const BookDetailsScreen({super.key, this.book}); + + @override + Widget build(BuildContext context) { + if (book == null) { + return const Scaffold(body: Center(child: Text('No book found.'))); + } + return Scaffold( + appBar: AppBar(title: Text(book!.title)), + body: Center( + child: Column( + children: [ + Text( + book!.title, + style: Theme.of(context).textTheme.headlineMedium, + ), + Text( + book!.author.name, + style: Theme.of(context).textTheme.titleMedium, + ), + TextButton( + child: const Text('View author (Push)'), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => AuthorDetailsScreen( + author: book!.author, + onBookTapped: (book) { + GoRouter.of( + context, + ).go('/books/all/book/${book.id}'); + }, + ), + ), + ); + }, + ), + Link( + uri: Uri.parse('/authors/author/${book!.author.id}'), + builder: (context, followLink) => TextButton( + onPressed: followLink, + child: const Text('View author (Link)'), + ), + ), + ], + ), + ), + ); + } +} diff --git a/navigation_and_routing/lib/src/screens/books.dart b/navigation_and_routing/lib/src/screens/books.dart new file mode 100644 index 0000000..cc2241b --- /dev/null +++ b/navigation_and_routing/lib/src/screens/books.dart @@ -0,0 +1,69 @@ +// Copyright 2021, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter/material.dart'; + +class BooksScreen extends StatefulWidget { + final Widget child; + final ValueChanged onTap; + final int selectedIndex; + + const BooksScreen({ + required this.child, + required this.onTap, + required this.selectedIndex, + super.key, + }); + + @override + State createState() => _BooksScreenState(); +} + +class _BooksScreenState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 3, vsync: this) + ..addListener(_handleTabIndexChanged); + } + + @override + void didUpdateWidget(covariant BooksScreen oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.selectedIndex != widget.selectedIndex) { + _tabController.index = widget.selectedIndex; + } + } + + @override + void dispose() { + _tabController.removeListener(_handleTabIndexChanged); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Books'), + bottom: TabBar( + controller: _tabController, + tabs: const [ + Tab(text: 'Popular', icon: Icon(Icons.people)), + Tab(text: 'New', icon: Icon(Icons.new_releases)), + Tab(text: 'All', icon: Icon(Icons.list)), + ], + ), + ), + body: widget.child, + ); + } + + void _handleTabIndexChanged() { + widget.onTap(_tabController.index); + } +} diff --git a/navigation_and_routing/lib/src/screens/scaffold.dart b/navigation_and_routing/lib/src/screens/scaffold.dart new file mode 100644 index 0000000..2d72973 --- /dev/null +++ b/navigation_and_routing/lib/src/screens/scaffold.dart @@ -0,0 +1,46 @@ +// Copyright 2021, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:adaptive_navigation/adaptive_navigation.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class BookstoreScaffold extends StatelessWidget { + final Widget child; + final int selectedIndex; + + const BookstoreScaffold({ + required this.child, + required this.selectedIndex, + super.key, + }); + + @override + Widget build(BuildContext context) { + final goRouter = GoRouter.of(context); + + return Scaffold( + body: AdaptiveNavigationScaffold( + selectedIndex: selectedIndex, + body: child, + onDestinationSelected: (idx) { + if (idx == 0) goRouter.go('/books/popular'); + if (idx == 1) goRouter.go('/authors'); + if (idx == 2) goRouter.go('/settings'); + }, + destinations: const [ + AdaptiveScaffoldDestination(title: 'Books', icon: Icons.book), + AdaptiveScaffoldDestination( + title: 'Authors', + icon: Icons.person, + ), + AdaptiveScaffoldDestination( + title: 'Settings', + icon: Icons.settings, + ), + ], + ), + ); + } +} diff --git a/navigation_and_routing/lib/src/screens/settings.dart b/navigation_and_routing/lib/src/screens/settings.dart new file mode 100644 index 0000000..b915e6a --- /dev/null +++ b/navigation_and_routing/lib/src/screens/settings.dart @@ -0,0 +1,99 @@ +// Copyright 2021, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:url_launcher/link.dart'; + +import '../auth.dart'; + +class SettingsScreen extends StatefulWidget { + const SettingsScreen({super.key}); + + @override + State createState() => _SettingsScreenState(); +} + +class _SettingsScreenState extends State { + @override + Widget build(BuildContext context) => Scaffold( + body: SafeArea( + child: SingleChildScrollView( + child: Align( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 400), + child: const Card( + child: Padding( + padding: EdgeInsets.symmetric( + vertical: 18, + horizontal: 12, + ), + child: SettingsContent(), + ), + ), + ), + ), + ), + ), + ); +} + +class SettingsContent extends StatelessWidget { + const SettingsContent({super.key}); + + @override + Widget build(BuildContext context) => Column( + children: [ + ...[ + Text( + 'Settings', + style: Theme.of(context).textTheme.headlineMedium, + ), + FilledButton( + onPressed: () { + BookstoreAuth.of(context).signOut(); + }, + child: const Text('Sign out'), + ), + const Text('Example using the Link widget:'), + Link( + uri: Uri.parse('/books/all/book/0'), + builder: (context, followLink) => TextButton( + onPressed: followLink, + child: const Text('/books/all/book/0'), + ), + ), + const Text('Example using GoRouter.of(context).go():'), + TextButton( + child: const Text('/books/all/book/0'), + onPressed: () { + GoRouter.of(context).go('/books/all/book/0'); + }, + ), + ].map((w) => Padding(padding: const EdgeInsets.all(8), child: w)), + const Text('Displays a dialog on the root Navigator:'), + TextButton( + onPressed: () => showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Alert!'), + content: const Text('The alert description goes here.'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, 'Cancel'), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () => Navigator.pop(context, 'OK'), + child: const Text('OK'), + ), + ], + ), + ), + child: const Text('Show Dialog'), + ), + ], + ); +} diff --git a/navigation_and_routing/lib/src/screens/sign_in.dart b/navigation_and_routing/lib/src/screens/sign_in.dart new file mode 100644 index 0000000..78abb2b --- /dev/null +++ b/navigation_and_routing/lib/src/screens/sign_in.dart @@ -0,0 +1,71 @@ +// Copyright 2021, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter/material.dart'; + +class Credentials { + final String username; + final String password; + + Credentials(this.username, this.password); +} + +class SignInScreen extends StatefulWidget { + final ValueChanged onSignIn; + + const SignInScreen({required this.onSignIn, super.key}); + + @override + State createState() => _SignInScreenState(); +} + +class _SignInScreenState extends State { + final _usernameController = TextEditingController(); + final _passwordController = TextEditingController(); + + @override + Widget build(BuildContext context) => Scaffold( + body: Center( + child: Card( + child: Container( + constraints: BoxConstraints.loose(const Size(600, 600)), + padding: const EdgeInsets.all(8), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Sign in', + style: Theme.of(context).textTheme.headlineMedium, + ), + TextField( + decoration: const InputDecoration(labelText: 'Username'), + controller: _usernameController, + ), + TextField( + decoration: const InputDecoration(labelText: 'Password'), + obscureText: true, + controller: _passwordController, + ), + Padding( + padding: const EdgeInsets.all(16), + child: TextButton( + onPressed: () async { + widget.onSignIn( + Credentials( + _usernameController.value.text, + _passwordController.value.text, + ), + ); + }, + child: const Text('Sign in'), + ), + ), + ], + ), + ), + ), + ), + ); +} diff --git a/navigation_and_routing/lib/src/widgets/author_list.dart b/navigation_and_routing/lib/src/widgets/author_list.dart new file mode 100644 index 0000000..e9c3a99 --- /dev/null +++ b/navigation_and_routing/lib/src/widgets/author_list.dart @@ -0,0 +1,24 @@ +// Copyright 2021, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter/material.dart'; + +import '../data.dart'; + +class AuthorList extends StatelessWidget { + final List authors; + final ValueChanged? onTap; + + const AuthorList({required this.authors, this.onTap, super.key}); + + @override + Widget build(BuildContext context) => ListView.builder( + itemCount: authors.length, + itemBuilder: (context, index) => ListTile( + title: Text(authors[index].name), + subtitle: Text('${authors[index].books.length} books'), + onTap: onTap != null ? () => onTap!(authors[index]) : null, + ), + ); +} diff --git a/navigation_and_routing/lib/src/widgets/book_list.dart b/navigation_and_routing/lib/src/widgets/book_list.dart new file mode 100644 index 0000000..9c5da89 --- /dev/null +++ b/navigation_and_routing/lib/src/widgets/book_list.dart @@ -0,0 +1,24 @@ +// Copyright 2021, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter/material.dart'; + +import '../data.dart'; + +class BookList extends StatelessWidget { + final List books; + final ValueChanged? onTap; + + const BookList({required this.books, this.onTap, super.key}); + + @override + Widget build(BuildContext context) => ListView.builder( + itemCount: books.length, + itemBuilder: (context, index) => ListTile( + title: Text(books[index].title), + subtitle: Text(books[index].author.name), + onTap: onTap != null ? () => onTap!(books[index]) : null, + ), + ); +} diff --git a/navigation_and_routing/lib/src/widgets/fade_transition_page.dart b/navigation_and_routing/lib/src/widgets/fade_transition_page.dart new file mode 100644 index 0000000..f175063 --- /dev/null +++ b/navigation_and_routing/lib/src/widgets/fade_transition_page.dart @@ -0,0 +1,59 @@ +// Copyright 2021, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter/material.dart'; + +class FadeTransitionPage extends Page { + final Widget child; + final Duration duration; + + const FadeTransitionPage({ + super.key, + required this.child, + this.duration = const Duration(milliseconds: 300), + }); + + @override + Route createRoute(BuildContext context) => + PageBasedFadeTransitionRoute(this); +} + +class PageBasedFadeTransitionRoute extends PageRoute { + final FadeTransitionPage _page; + + PageBasedFadeTransitionRoute(this._page) : super(settings: _page); + + @override + Color? get barrierColor => null; + + @override + String? get barrierLabel => null; + + @override + Duration get transitionDuration => _page.duration; + + @override + bool get maintainState => true; + + @override + Widget buildPage( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + var curveTween = CurveTween(curve: Curves.easeIn); + return FadeTransition( + opacity: animation.drive(curveTween), + child: (settings as FadeTransitionPage).child, + ); + } + + @override + Widget buildTransitions( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) => child; +} diff --git a/navigation_and_routing/linux/.gitignore b/navigation_and_routing/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/navigation_and_routing/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/navigation_and_routing/linux/CMakeLists.txt b/navigation_and_routing/linux/CMakeLists.txt new file mode 100644 index 0000000..c444a31 --- /dev/null +++ b/navigation_and_routing/linux/CMakeLists.txt @@ -0,0 +1,145 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "navigation_and_routing") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "dev.flutter.navigation_and_routing") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/navigation_and_routing/linux/flutter/CMakeLists.txt b/navigation_and_routing/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/navigation_and_routing/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/navigation_and_routing/linux/flutter/generated_plugin_registrant.cc b/navigation_and_routing/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..f6f23bf --- /dev/null +++ b/navigation_and_routing/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/navigation_and_routing/linux/flutter/generated_plugin_registrant.h b/navigation_and_routing/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/navigation_and_routing/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/navigation_and_routing/linux/flutter/generated_plugins.cmake b/navigation_and_routing/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..f16b4c3 --- /dev/null +++ b/navigation_and_routing/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/navigation_and_routing/linux/main.cc b/navigation_and_routing/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/navigation_and_routing/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/navigation_and_routing/linux/my_application.cc b/navigation_and_routing/linux/my_application.cc new file mode 100644 index 0000000..85d0835 --- /dev/null +++ b/navigation_and_routing/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "navigation_and_routing"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "navigation_and_routing"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/navigation_and_routing/linux/my_application.h b/navigation_and_routing/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/navigation_and_routing/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/navigation_and_routing/macos/.gitignore b/navigation_and_routing/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/navigation_and_routing/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/navigation_and_routing/macos/Flutter/Flutter-Debug.xcconfig b/navigation_and_routing/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/navigation_and_routing/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/navigation_and_routing/macos/Flutter/Flutter-Release.xcconfig b/navigation_and_routing/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/navigation_and_routing/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/navigation_and_routing/macos/Flutter/GeneratedPluginRegistrant.swift b/navigation_and_routing/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..8236f57 --- /dev/null +++ b/navigation_and_routing/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import url_launcher_macos + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) +} diff --git a/navigation_and_routing/macos/Podfile b/navigation_and_routing/macos/Podfile new file mode 100644 index 0000000..c795730 --- /dev/null +++ b/navigation_and_routing/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/navigation_and_routing/macos/Runner.xcodeproj/project.pbxproj b/navigation_and_routing/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..1220eab --- /dev/null +++ b/navigation_and_routing/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,695 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* navigation_and_routing.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "navigation_and_routing.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* navigation_and_routing.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* navigation_and_routing.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.navigationAndRouting.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/navigation_and_routing.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/navigation_and_routing"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.navigationAndRouting.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/navigation_and_routing.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/navigation_and_routing"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.navigationAndRouting.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/navigation_and_routing.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/navigation_and_routing"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/navigation_and_routing/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/navigation_and_routing/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/navigation_and_routing/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/navigation_and_routing/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/navigation_and_routing/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..025d0ce --- /dev/null +++ b/navigation_and_routing/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/navigation_and_routing/macos/Runner.xcworkspace/contents.xcworkspacedata b/navigation_and_routing/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/navigation_and_routing/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/navigation_and_routing/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/navigation_and_routing/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/navigation_and_routing/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/navigation_and_routing/macos/Runner/AppDelegate.swift b/navigation_and_routing/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..d53ef64 --- /dev/null +++ b/navigation_and_routing/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/navigation_and_routing/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/navigation_and_routing/macos/Runner/Base.lproj/MainMenu.xib b/navigation_and_routing/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/navigation_and_routing/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/navigation_and_routing/macos/Runner/Configs/AppInfo.xcconfig b/navigation_and_routing/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..57da72e --- /dev/null +++ b/navigation_and_routing/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = navigation_and_routing + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.navigationAndRouting + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright Β© 2023 dev.flutter. All rights reserved. diff --git a/navigation_and_routing/macos/Runner/Configs/Debug.xcconfig b/navigation_and_routing/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/navigation_and_routing/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/navigation_and_routing/macos/Runner/Configs/Release.xcconfig b/navigation_and_routing/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/navigation_and_routing/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/navigation_and_routing/macos/Runner/Configs/Warnings.xcconfig b/navigation_and_routing/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/navigation_and_routing/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/navigation_and_routing/macos/Runner/DebugProfile.entitlements b/navigation_and_routing/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/navigation_and_routing/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/navigation_and_routing/macos/Runner/Info.plist b/navigation_and_routing/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/navigation_and_routing/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/navigation_and_routing/macos/Runner/MainFlutterWindow.swift b/navigation_and_routing/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/navigation_and_routing/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/navigation_and_routing/macos/Runner/Release.entitlements b/navigation_and_routing/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/navigation_and_routing/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/navigation_and_routing/macos/RunnerTests/RunnerTests.swift b/navigation_and_routing/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..5418c9f --- /dev/null +++ b/navigation_and_routing/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import FlutterMacOS +import Cocoa +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/navigation_and_routing/pubspec.yaml b/navigation_and_routing/pubspec.yaml new file mode 100644 index 0000000..401e6a1 --- /dev/null +++ b/navigation_and_routing/pubspec.yaml @@ -0,0 +1,26 @@ +name: bookstore +description: Navigation and routing sample app +publish_to: "none" # Remove this line if you wish to publish to pub.dev +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + adaptive_navigation: ^0.0.3 + cupertino_icons: ^1.0.2 + flutter: + sdk: flutter + go_router: ^16.0.0 + url_launcher: ^6.1.1 + +dev_dependencies: + analysis_defaults: + path: ../analysis_defaults + flutter_test: + sdk: flutter + test: ^1.24.0 + +flutter: + uses-material-design: true diff --git a/navigation_and_routing/test/library_test.dart b/navigation_and_routing/test/library_test.dart new file mode 100644 index 0000000..788867b --- /dev/null +++ b/navigation_and_routing/test/library_test.dart @@ -0,0 +1,42 @@ +// Copyright 2021, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:bookstore/src/data/library.dart'; +import 'package:test/test.dart'; + +void main() { + group('Library', () { + test('addBook', () { + final library = Library(); + library.addBook( + title: 'Left Hand of Darkness', + authorName: 'Ursula K. Le Guin', + isPopular: true, + isNew: true, + ); + library.addBook( + title: 'Too Like the Lightning', + authorName: 'Ada Palmer', + isPopular: false, + isNew: true, + ); + library.addBook( + title: 'Kindred', + authorName: 'Octavia E. Butler', + isPopular: true, + isNew: false, + ); + library.addBook( + title: 'The Lathe of Heaven', + authorName: 'Ursula K. Le Guin', + isPopular: false, + isNew: false, + ); + expect(library.allAuthors.length, 3); + expect(library.allAuthors.first.books.length, 2); + expect(library.allBooks.length, 4); + expect(library.allBooks.first.author.name, startsWith('Ursula')); + }); + }); +} diff --git a/navigation_and_routing/test/widget_test.dart b/navigation_and_routing/test/widget_test.dart new file mode 100644 index 0000000..47a8fca --- /dev/null +++ b/navigation_and_routing/test/widget_test.dart @@ -0,0 +1,9 @@ +// Copyright 2021, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('empty test', (tester) async {}); +} diff --git a/navigation_and_routing/web/favicon.png b/navigation_and_routing/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/navigation_and_routing/web/favicon.png differ diff --git a/navigation_and_routing/web/icons/Icon-192.png b/navigation_and_routing/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/navigation_and_routing/web/icons/Icon-192.png differ diff --git a/navigation_and_routing/web/icons/Icon-512.png b/navigation_and_routing/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/navigation_and_routing/web/icons/Icon-512.png differ diff --git a/navigation_and_routing/web/icons/Icon-maskable-192.png b/navigation_and_routing/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/navigation_and_routing/web/icons/Icon-maskable-192.png differ diff --git a/navigation_and_routing/web/icons/Icon-maskable-512.png b/navigation_and_routing/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/navigation_and_routing/web/icons/Icon-maskable-512.png differ diff --git a/navigation_and_routing/web/index.html b/navigation_and_routing/web/index.html new file mode 100644 index 0000000..e1ff8cc --- /dev/null +++ b/navigation_and_routing/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + navigation_and_routing + + + + + + diff --git a/navigation_and_routing/web/manifest.json b/navigation_and_routing/web/manifest.json new file mode 100644 index 0000000..7631ed2 --- /dev/null +++ b/navigation_and_routing/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "navigation_and_routing", + "short_name": "navigation_and_routing", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "Navigation and routing sample app", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/navigation_and_routing/windows/.gitignore b/navigation_and_routing/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/navigation_and_routing/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/navigation_and_routing/windows/CMakeLists.txt b/navigation_and_routing/windows/CMakeLists.txt new file mode 100644 index 0000000..7569048 --- /dev/null +++ b/navigation_and_routing/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(navigation_and_routing LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "navigation_and_routing") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/navigation_and_routing/windows/flutter/CMakeLists.txt b/navigation_and_routing/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/navigation_and_routing/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/navigation_and_routing/windows/flutter/generated_plugin_registrant.cc b/navigation_and_routing/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..4f78848 --- /dev/null +++ b/navigation_and_routing/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/navigation_and_routing/windows/flutter/generated_plugin_registrant.h b/navigation_and_routing/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/navigation_and_routing/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/navigation_and_routing/windows/flutter/generated_plugins.cmake b/navigation_and_routing/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..88b22e5 --- /dev/null +++ b/navigation_and_routing/windows/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/navigation_and_routing/windows/runner/CMakeLists.txt b/navigation_and_routing/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/navigation_and_routing/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/navigation_and_routing/windows/runner/Runner.rc b/navigation_and_routing/windows/runner/Runner.rc new file mode 100644 index 0000000..d97574e --- /dev/null +++ b/navigation_and_routing/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "dev.flutter" "\0" + VALUE "FileDescription", "navigation_and_routing" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "navigation_and_routing" "\0" + VALUE "LegalCopyright", "Copyright (C) 2023 dev.flutter. All rights reserved." "\0" + VALUE "OriginalFilename", "navigation_and_routing.exe" "\0" + VALUE "ProductName", "navigation_and_routing" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/navigation_and_routing/windows/runner/flutter_window.cpp b/navigation_and_routing/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/navigation_and_routing/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/navigation_and_routing/windows/runner/flutter_window.h b/navigation_and_routing/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/navigation_and_routing/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/navigation_and_routing/windows/runner/main.cpp b/navigation_and_routing/windows/runner/main.cpp new file mode 100644 index 0000000..3911285 --- /dev/null +++ b/navigation_and_routing/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"navigation_and_routing", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/navigation_and_routing/windows/runner/resource.h b/navigation_and_routing/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/navigation_and_routing/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/navigation_and_routing/windows/runner/resources/app_icon.ico b/navigation_and_routing/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/navigation_and_routing/windows/runner/resources/app_icon.ico differ diff --git a/navigation_and_routing/windows/runner/runner.exe.manifest b/navigation_and_routing/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..a42ea76 --- /dev/null +++ b/navigation_and_routing/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/navigation_and_routing/windows/runner/utils.cpp b/navigation_and_routing/windows/runner/utils.cpp new file mode 100644 index 0000000..b2b0873 --- /dev/null +++ b/navigation_and_routing/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/navigation_and_routing/windows/runner/utils.h b/navigation_and_routing/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/navigation_and_routing/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/navigation_and_routing/windows/runner/win32_window.cpp b/navigation_and_routing/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/navigation_and_routing/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/navigation_and_routing/windows/runner/win32_window.h b/navigation_and_routing/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/navigation_and_routing/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/pedometer/.gitignore b/pedometer/.gitignore new file mode 100644 index 0000000..96486fd --- /dev/null +++ b/pedometer/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/pedometer/.metadata b/pedometer/.metadata new file mode 100644 index 0000000..795ad52 --- /dev/null +++ b/pedometer/.metadata @@ -0,0 +1,33 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: 098aac7ffeef2a1846eb3a7f14788520c8400a14 + channel: master + +project_type: plugin_ffi + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 098aac7ffeef2a1846eb3a7f14788520c8400a14 + base_revision: 098aac7ffeef2a1846eb3a7f14788520c8400a14 + - platform: android + create_revision: 098aac7ffeef2a1846eb3a7f14788520c8400a14 + base_revision: 098aac7ffeef2a1846eb3a7f14788520c8400a14 + - platform: ios + create_revision: 098aac7ffeef2a1846eb3a7f14788520c8400a14 + base_revision: 098aac7ffeef2a1846eb3a7f14788520c8400a14 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/pedometer/CHANGELOG.md b/pedometer/CHANGELOG.md new file mode 100644 index 0000000..41cc7d8 --- /dev/null +++ b/pedometer/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/pedometer/LICENSE b/pedometer/LICENSE new file mode 100644 index 0000000..ba75c69 --- /dev/null +++ b/pedometer/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/pedometer/README.md b/pedometer/README.md new file mode 100644 index 0000000..3cee83f --- /dev/null +++ b/pedometer/README.md @@ -0,0 +1,93 @@ +# FFIgen + JNIgen pedometer + +This is a demo for some of our tooling around +calling platform APIs directly from dart code. +This repository represents a demo of a plugin that leverages FFIgen & JNIgen. +There is also an example pedometer app that +uses the bindings generated from these tools. + +- [FFIgen](https://pub.dev/packages/ffigen) is used to generate + bindings for C, Objective-C and Swift APIs. +- [JNIgen](https://pub.dev/packages/jnigen) is used to generate + bindings for Java and Kotlin APIs. + +**These tools are both experimental and are currently a work in progress.** +If you find any issues or have feedback, +please file it on the corresponding GitHub repositories. + +## Re-generating bindings + +The bindings that allow the Dart code to call the platform code have +already been generated in the [`\lib` folder](./lib). +You can regenerate them by following the steps below: + +### FFIgen + +Configuration of FFIgen for the +[CoreMotion framework](https://developer.apple.com/documentation/coremotion) +is in the [`ffigen.yaml` file](./ffigen.yaml). +FFIgen currently does not support autogenerating code to handle callbacks. +So, there are a few extra steps needed to +appropriately handle callbacks in Objective-C. +You can read more about this limitation on +[dart.dev](https://dart.dev/interop/objective-c-interop#callbacks-and-multithreading-limitations). + +```bash +dart run ffigen --config ffigen.yaml +``` + + +### JNIgen + +Configuration of JNIgen for the +[HealthConnect API](https://developer.android.com/guide/health-and-fitness/health-connect) +is in the [`jnigen.yaml` file](./jnigen.yaml). + +1. Build an Android APK file from the example app. + Currently, JNIgen requires at least one APK build + to obtain the classpaths of Android Gradle libraries. + + ```bash + cd example && flutter build apk + ``` + +2. Return to the `/pedometer` directory and run `jnigen`: + + ```bash + cd .. && dart run jnigen --config jnigen.yaml + ``` + +## Running the example app + +The example app is located in the [`/example`](./example) directory, +and the following commands assume they are being run from that location. + +Note that step counting is only available on physical devices. + +### iOS + +- Run `flutter run` and choose your physical device. +- Allow the *pedometer* app access to step counting. + +### Android + +- Make sure that [Google Fit](https://play.google.com/store/apps/details?id=com.google.android.apps.fitness) + is installed (to ensure that steps are being counted). +- Run `flutter run` and choose your physical device. +- Install [Health Connect](https://play.google.com/store/apps/details?id=com.google.android.apps.healthdata) + and grant access to Google Fit and the *jni_demo* app. + + +## Project structure + +* `src`: Contains the native source code, and a `CMakeLists.txt` file for + building that source code into a dynamic library. + +* `lib`: Contains the Dart code that defines the API of the plugin and + calls into the native code using `dart:ffi`. + +* platform folders (`ios` etc.): Contain the build files for + building and bundling the native code library with the platform application. + +* `example`: Contains the native source code for building + that source code into a dynamic library. diff --git a/pedometer/analysis_options.yaml b/pedometer/analysis_options.yaml new file mode 100644 index 0000000..7887423 --- /dev/null +++ b/pedometer/analysis_options.yaml @@ -0,0 +1,11 @@ +include: ../analysis_options.yaml + +analyzer: + exclude: + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** diff --git a/pedometer/android/.gitignore b/pedometer/android/.gitignore new file mode 100644 index 0000000..161bdcd --- /dev/null +++ b/pedometer/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/pedometer/android/build.gradle b/pedometer/android/build.gradle new file mode 100644 index 0000000..de4a69e --- /dev/null +++ b/pedometer/android/build.gradle @@ -0,0 +1,59 @@ +// The Android Gradle Plugin builds the native code with the Android NDK. + +group 'dev.flutter.pedometer' +version '1.0' + +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + // The Android Gradle Plugin knows how to build native code with the NDK. + classpath 'com.android.tools.build:gradle:7.3.0' + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' + +android { + // Bumping the plugin compileSdkVersion requires all clients of this plugin + // to bump the version in their app. + compileSdkVersion 34 + + // Bumping the plugin ndkVersion requires all clients of this plugin to bump + // the version in their app and to download a newer version of the NDK. + ndkVersion "23.1.7779620" + + // Invoke the shared CMake build with the Android Gradle Plugin. + externalNativeBuild { + cmake { + path "../src/health_connect/CMakeLists.txt" + + // The default CMake version for the Android Gradle Plugin is 3.10.2. + // https://developer.android.com/studio/projects/install-ndk#vanilla_cmake + // + // The Flutter tooling requires that developers have CMake 3.10 or later + // installed. You should not increase this version, as doing so will cause + // the plugin to fail to compile for some customers of the plugin. + // version "3.10.2" + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion 21 + } +} diff --git a/pedometer/android/settings.gradle b/pedometer/android/settings.gradle new file mode 100644 index 0000000..f295a58 --- /dev/null +++ b/pedometer/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'pedometer' diff --git a/pedometer/android/src/main/AndroidManifest.xml b/pedometer/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1a19daf --- /dev/null +++ b/pedometer/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/pedometer/example/.gitignore b/pedometer/example/.gitignore new file mode 100644 index 0000000..24476c5 --- /dev/null +++ b/pedometer/example/.gitignore @@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/pedometer/example/README.md b/pedometer/example/README.md new file mode 100644 index 0000000..c752364 --- /dev/null +++ b/pedometer/example/README.md @@ -0,0 +1,5 @@ +# pedometer_example + +Demonstrates how to use the pedometer plugin. + +Visit the primary [pedometer README](../README.md) for more information. diff --git a/pedometer/example/analysis_options.yaml b/pedometer/example/analysis_options.yaml new file mode 100644 index 0000000..f04c6cf --- /dev/null +++ b/pedometer/example/analysis_options.yaml @@ -0,0 +1 @@ +include: ../../analysis_options.yaml diff --git a/pedometer/example/android/.gitignore b/pedometer/example/android/.gitignore new file mode 100644 index 0000000..d945a87 --- /dev/null +++ b/pedometer/example/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/app/.cxx/ +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/pedometer/example/android/app/build.gradle b/pedometer/example/android/app/build.gradle new file mode 100644 index 0000000..9dc4c0d --- /dev/null +++ b/pedometer/example/android/app/build.gradle @@ -0,0 +1,77 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + ndkVersion '25.1.8937393' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.jni_demo" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. + compileSdkVersion 34 + minSdkVersion 30 + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation "androidx.health.connect:connect-client:1.0.0-alpha06" +} diff --git a/pedometer/example/android/app/proguard-rules.pro b/pedometer/example/android/app/proguard-rules.pro new file mode 100644 index 0000000..c917f56 --- /dev/null +++ b/pedometer/example/android/app/proguard-rules.pro @@ -0,0 +1,2 @@ +-keep class androidx.health.connect.client.** { *; } +-keep class kotlin.coroutines.** { *; } diff --git a/pedometer/example/android/app/src/debug/AndroidManifest.xml b/pedometer/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..dfafb1a --- /dev/null +++ b/pedometer/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/pedometer/example/android/app/src/main/AndroidManifest.xml b/pedometer/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0b7b616 --- /dev/null +++ b/pedometer/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pedometer/example/android/app/src/main/kotlin/com/example/jni_demo/MainActivity.kt b/pedometer/example/android/app/src/main/kotlin/com/example/jni_demo/MainActivity.kt new file mode 100644 index 0000000..7d34f80 --- /dev/null +++ b/pedometer/example/android/app/src/main/kotlin/com/example/jni_demo/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.jni_demo + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() {} diff --git a/pedometer/example/android/app/src/main/res/drawable-v21/launch_background.xml b/pedometer/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/pedometer/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/pedometer/example/android/app/src/main/res/drawable/launch_background.xml b/pedometer/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/pedometer/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/pedometer/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/pedometer/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/pedometer/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/pedometer/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/pedometer/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/pedometer/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/pedometer/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/pedometer/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/pedometer/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/pedometer/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/pedometer/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/pedometer/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/pedometer/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/pedometer/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/pedometer/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/pedometer/example/android/app/src/main/res/values-night/styles.xml b/pedometer/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/pedometer/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/pedometer/example/android/app/src/main/res/values/health-permissions.xml b/pedometer/example/android/app/src/main/res/values/health-permissions.xml new file mode 100644 index 0000000..bd3b656 --- /dev/null +++ b/pedometer/example/android/app/src/main/res/values/health-permissions.xml @@ -0,0 +1,8 @@ + + + androidx.health.permission.HeartRate.READ + androidx.health.permission.HeartRate.WRITE + androidx.health.permission.Steps.READ + androidx.health.permission.Steps.WRITE + + \ No newline at end of file diff --git a/pedometer/example/android/app/src/main/res/values/styles.xml b/pedometer/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/pedometer/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/pedometer/example/android/app/src/profile/AndroidManifest.xml b/pedometer/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..dfafb1a --- /dev/null +++ b/pedometer/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/pedometer/example/android/build.gradle b/pedometer/example/android/build.gradle new file mode 100644 index 0000000..4c47d69 --- /dev/null +++ b/pedometer/example/android/build.gradle @@ -0,0 +1,32 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.3.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} + diff --git a/pedometer/example/android/gradle.properties b/pedometer/example/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/pedometer/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/pedometer/example/android/gradle/wrapper/gradle-wrapper.properties b/pedometer/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..dcf0f19 --- /dev/null +++ b/pedometer/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip diff --git a/pedometer/example/android/settings.gradle b/pedometer/example/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/pedometer/example/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/pedometer/example/ios/.gitignore b/pedometer/example/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/pedometer/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/pedometer/example/ios/Flutter/AppFrameworkInfo.plist b/pedometer/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/pedometer/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/pedometer/example/ios/Flutter/Debug.xcconfig b/pedometer/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/pedometer/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/pedometer/example/ios/Flutter/Release.xcconfig b/pedometer/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/pedometer/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/pedometer/example/ios/Podfile b/pedometer/example/ios/Podfile new file mode 100644 index 0000000..88359b2 --- /dev/null +++ b/pedometer/example/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '11.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/pedometer/example/ios/Runner.xcodeproj/project.pbxproj b/pedometer/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..84e7ee7 --- /dev/null +++ b/pedometer/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,559 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + CD8D487B2DE298847620CE27 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD546C851461740D88EB9EA1 /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0E73CBA5419BEF015A90306B /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3838EA3C4ACB62ED755A9FF6 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 6FC09200CFFED4F1241C2B25 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + CD546C851461740D88EB9EA1 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CD8D487B2DE298847620CE27 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 2E033E4F7934F793372AF7BD /* Frameworks */ = { + isa = PBXGroup; + children = ( + CD546C851461740D88EB9EA1 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 3CD8DEE842AFA53B3E88ECBE /* Pods */ = { + isa = PBXGroup; + children = ( + 6FC09200CFFED4F1241C2B25 /* Pods-Runner.debug.xcconfig */, + 3838EA3C4ACB62ED755A9FF6 /* Pods-Runner.release.xcconfig */, + 0E73CBA5419BEF015A90306B /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 3CD8DEE842AFA53B3E88ECBE /* Pods */, + 2E033E4F7934F793372AF7BD /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + CF8D8954002953B19E29F0D1 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + C8366BDABC1428824E3EC64F /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + C8366BDABC1428824E3EC64F /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + CF8D8954002953B19E29F0D1 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = S8QB4VV633; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.leighatest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = S8QB4VV633; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.leighatest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = S8QB4VV633; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.leighatest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/pedometer/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/pedometer/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/pedometer/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/pedometer/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/pedometer/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/pedometer/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/pedometer/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/pedometer/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/pedometer/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/pedometer/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/pedometer/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..c87d15a --- /dev/null +++ b/pedometer/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pedometer/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/pedometer/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/pedometer/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/pedometer/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/pedometer/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/pedometer/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/pedometer/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/pedometer/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/pedometer/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/pedometer/example/ios/Runner/AppDelegate.swift b/pedometer/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/pedometer/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/pedometer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/pedometer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/pedometer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/pedometer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/pedometer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/pedometer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/pedometer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/pedometer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/pedometer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/pedometer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/pedometer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/pedometer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/pedometer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/pedometer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/pedometer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/pedometer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/pedometer/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/pedometer/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/pedometer/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pedometer/example/ios/Runner/Base.lproj/Main.storyboard b/pedometer/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/pedometer/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pedometer/example/ios/Runner/Info.plist b/pedometer/example/ios/Runner/Info.plist new file mode 100644 index 0000000..15dfe78 --- /dev/null +++ b/pedometer/example/ios/Runner/Info.plist @@ -0,0 +1,53 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Pedometer + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + pedometer_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + NSMotionUsageDescription + Live stream pedometer + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/pedometer/example/ios/Runner/Runner-Bridging-Header.h b/pedometer/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/pedometer/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/pedometer/example/lib/main.dart b/pedometer/example/lib/main.dart new file mode 100644 index 0000000..6e18433 --- /dev/null +++ b/pedometer/example/lib/main.dart @@ -0,0 +1,206 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +import 'steps_repo.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData(primarySwatch: Colors.blue), + home: const Home(), + ); + } +} + +class RoundClipper extends CustomClipper { + @override + Path getClip(Size size) { + final diameter = size.shortestSide * 1.5; + final x = -(diameter - size.width) / 2; + final y = size.height - diameter; + final rect = Offset(x, y) & Size(diameter, diameter); + return Path()..addOval(rect); + } + + @override + bool shouldReclip(CustomClipper oldClipper) { + return false; + } +} + +class Home extends StatefulWidget { + const Home({super.key}); + + @override + State createState() => _HomeState(); +} + +class _HomeState extends State { + var hourlySteps = []; + DateTime? lastUpdated; + + @override + void initState() { + runPedometer(); + super.initState(); + } + + Future runPedometer() async { + final now = DateTime.now(); + hourlySteps = await StepsRepo.instance.getSteps(); + lastUpdated = now; + setState(() {}); + } + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + + final barGroups = hourlySteps + .map( + (e) => BarChartGroupData( + x: int.parse(e.startHour), + barRods: [ + BarChartRodData( + color: Colors.blue[900], + toY: e.steps.toDouble() / 100, + ), + ], + ), + ) + .toList(); + + return Scaffold( + body: Stack( + children: [ + ClipPath( + clipper: RoundClipper(), + child: FractionallySizedBox( + heightFactor: 0.55, + widthFactor: 1, + child: Container(color: Colors.blue[300]), + ), + ), + Align( + alignment: Alignment.topCenter, + child: Padding( + padding: const EdgeInsets.all(80.0), + child: Column( + children: [ + lastUpdated != null + ? Padding( + padding: const EdgeInsets.symmetric(vertical: 50.0), + child: Text( + DateFormat.yMMMMd('en_US').format(lastUpdated!), + style: textTheme.titleLarge!.copyWith( + color: Colors.blue[900], + ), + ), + ) + : const SizedBox(height: 0), + Text( + hourlySteps.fold(0, (t, e) => t + e.steps).toString(), + style: textTheme.displayMedium!.copyWith( + color: Colors.white, + ), + ), + Text( + 'steps', + style: textTheme.titleLarge!.copyWith(color: Colors.white), + ), + ], + ), + ), + ), + Align( + alignment: Alignment.centerRight, + child: GestureDetector( + onTap: runPedometer, + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Container( + decoration: BoxDecoration( + color: Colors.blue[900], + shape: BoxShape.circle, + ), + child: const Padding( + padding: EdgeInsets.all(8.0), + child: Icon(Icons.refresh, color: Colors.white, size: 50), + ), + ), + ), + ), + ), + Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 30.0, + vertical: 50.0, + ), + child: AspectRatio( + aspectRatio: 1.2, + child: BarChart( + BarChartData( + titlesData: const FlTitlesData( + show: true, + // Top titles are null + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + rightTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 30, + getTitlesWidget: getBottomTitles, + ), + ), + ), + borderData: FlBorderData(show: false), + barGroups: barGroups, + gridData: const FlGridData(show: false), + alignment: BarChartAlignment.spaceAround, + ), + ), + ), + ), + ), + ], + ), + ); + } +} + +// Axis labels for bottom of chart. +Widget getBottomTitles(double value, TitleMeta meta) { + final timeText = switch (value.toInt()) { + 0 => '12AM', + 6 => '6AM', + 12 => '12PM', + 18 => '6PM', + _ => '', + }; + return SideTitleWidget( + space: 4, + meta: meta, + child: Text( + timeText, + style: TextStyle(fontSize: 14, color: Colors.blue[900]), + ), + ); +} diff --git a/pedometer/example/lib/steps_repo.dart b/pedometer/example/lib/steps_repo.dart new file mode 100644 index 0000000..fcbaa8a --- /dev/null +++ b/pedometer/example/lib/steps_repo.dart @@ -0,0 +1,183 @@ +import 'dart:async'; +import 'dart:collection'; +import 'dart:ffi' as ffi; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:intl/intl.dart'; +import 'package:jni/jni.dart' as jni; +import 'package:pedometer/health_connect.dart' as hc; +import 'package:pedometer/pedometer_bindings_generated.dart' as pd; + +/// Class to hold the information needed for the chart +class Steps { + final String startHour; + final int steps; + + Steps(this.startHour, this.steps); +} + +abstract class StepsRepo { + static const _formatString = "yyyy-MM-dd HH:mm:ss"; + + static StepsRepo? _instance; + static StepsRepo get instance => + _instance ??= Platform.isAndroid ? _AndroidStepsRepo() : _IOSStepsRepo(); + + Future> getSteps(); +} + +class _IOSStepsRepo implements StepsRepo { + static const _dylibPath = + '/System/Library/Frameworks/CoreMotion.framework/CoreMotion'; + + // Bindings for the CMPedometer class. + final lib = pd.PedometerBindings(ffi.DynamicLibrary.open(_dylibPath)); + // Bindings for the helper function. + final helpLib = pd.PedometerBindings(ffi.DynamicLibrary.process()); + + late final pd.CMPedometer client; + late final pd.NSDateFormatter formatter; + late final pd.NSDateFormatter hourFormatter; + + _IOSStepsRepo() { + // Contains the Dart API helper functions + final dylib = ffi.DynamicLibrary.open("pedometer.framework/pedometer"); + + // Initialize the Dart API + final initializeApi = dylib + .lookupFunction< + ffi.IntPtr Function(ffi.Pointer), + int Function(ffi.Pointer) + >('Dart_InitializeApiDL'); + + final initializeResult = initializeApi(ffi.NativeApi.initializeApiDLData); + if (initializeResult != 0) { + throw StateError('failed to init API.'); + } + + // Create a new CMPedometer instance. + client = pd.CMPedometer.new1(lib); + + // Setting the formatter for date strings. + formatter = pd.NSDateFormatter.castFrom( + pd.NSDateFormatter.alloc(lib).init(), + ); + formatter.dateFormat = pd.NSString(lib, "${StepsRepo._formatString} zzz"); + hourFormatter = pd.NSDateFormatter.castFrom( + pd.NSDateFormatter.alloc(lib).init(), + ); + hourFormatter.dateFormat = pd.NSString(lib, "HH"); + } + + pd.NSDate dateConverter(DateTime dartDate) { + // Format dart date to string. + final formattedDate = DateFormat(StepsRepo._formatString).format(dartDate); + // Get current timezone. + // If eastern african change to AST to follow with NSDate. + final tz = dartDate.timeZoneName == "EAT" ? "AST" : dartDate.timeZoneName; + + // Create a new NSString with the formatted date and timezone. + final nString = pd.NSString(lib, "$formattedDate $tz"); + // Convert the NSString to NSDate. + return formatter.dateFromString_(nString)!; + } + + @override + Future> getSteps() async { + if (!pd.CMPedometer.isStepCountingAvailable(lib)) { + debugPrint("Step counting is not available."); + return []; + } + + final handlers = []; + final futures = >[]; + final now = DateTime.now(); + + for (var h = 0; h <= now.hour; h++) { + final start = dateConverter(DateTime(now.year, now.month, now.day, h)); + final end = dateConverter(DateTime(now.year, now.month, now.day, h + 1)); + final completer = Completer(); + futures.add(completer.future); + + final handler = helpLib.wrapCallback( + pd.ObjCBlock_ffiVoid_CMPedometerData_NSError.listener(lib, ( + result, + error, + ) { + if (result != null) { + final stepCount = result.numberOfSteps.intValue; + final startHour = hourFormatter + .stringFromDate_(result.startDate) + .toString(); + completer.complete(Steps(startHour, stepCount)); + } else { + debugPrint("Query error: ${error?.localizedDescription}"); + completer.complete(null); + } + }), + ); + handlers.add(handler); + client.queryPedometerDataFromDate_toDate_withHandler_( + start, + end, + handler, + ); + } + + return (await futures.wait).nonNulls.toList(); + } +} + +class _AndroidStepsRepo implements StepsRepo { + late final hc.Activity activity; + late final hc.Context applicationContext; + late final hc.HealthConnectClient client; + + _AndroidStepsRepo() { + // ignore: invalid_use_of_internal_member + activity = hc.Activity.fromReference(jni.Jni.getCurrentActivity()); + applicationContext = + // ignore: invalid_use_of_internal_member + hc.Context.fromReference(jni.Jni.getCachedApplicationContext()); + client = hc.HealthConnectClient.getOrCreate$1(applicationContext); + } + + @override + Future> getSteps() async { + final futures = >[]; + final now = DateTime.now(); + + for (var h = 0; h <= now.hour; h++) { + final start = DateTime( + now.year, + now.month, + now.day, + h, + ).millisecondsSinceEpoch; + final end = DateTime( + now.year, + now.month, + now.day, + h + 1, + ).millisecondsSinceEpoch; + final request = hc.AggregateRequest( + { + hc.StepsRecord.COUNT_TOTAL, + }.toJSet(hc.AggregateMetric.type(jni.JLong.type)), + hc.TimeRangeFilter.between( + hc.Instant.ofEpochMilli(start)!, + hc.Instant.ofEpochMilli(end)!, + ), + jni.JSet.hash(jni.JObject.type), + ); + futures.add(client.aggregate(request)); + } + final data = await Future.wait(futures); + return data.asMap().entries.map((entry) { + final stepsLong = entry.value.get(hc.StepsRecord.COUNT_TOTAL); + final steps = stepsLong?.intValue() ?? 0; + return Steps(entry.key.toString().padLeft(2, '0'), steps); + }).toList(); + } +} diff --git a/pedometer/example/pubspec.yaml b/pedometer/example/pubspec.yaml new file mode 100644 index 0000000..b9157f0 --- /dev/null +++ b/pedometer/example/pubspec.yaml @@ -0,0 +1,103 @@ +name: pedometer_example +description: Demonstrates how to use the pedometer plugin. +version: 1.0.0+1 +resolution: workspace + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. + + +environment: + sdk: ^3.9.0-0 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + pedometer: + # When depending on this package from a real application you should use: + # pedometer: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + ffi: ^2.1.2 + intl: ^0.20.0 + jni: ^0.14.2 + fl_chart: ^1.0.0 + +dev_dependencies: + analysis_defaults: + path: ../../analysis_defaults + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^6.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/pedometer/ffigen.yaml b/pedometer/ffigen.yaml new file mode 100644 index 0000000..b55ca65 --- /dev/null +++ b/pedometer/ffigen.yaml @@ -0,0 +1,24 @@ +# Run with `dart run ffigen --config ffigen.yaml`. +name: PedometerBindings +description: "Bindings for CM pedometers" +language: objc +output: "lib/pedometer_bindings_generated.dart" +compiler-opts: + - "-F/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks" + - "-mios-version-min=13.0" +exclude-all-by-default: true +functions: + include: + - "wrapCallback" +objc-interfaces: + include: + - "CMPedometer" + - "NSDate" + - "NSDateFormatter" +headers: + entry-points: + - "src/pedometerHelper.h" + +# To use this API, you must include the NSMotionUsageDescription key in your app’s Info.plist file +# and provide a usage description string for this key. +# The usage description appears in the prompt that the user must accept the first time the system asks the user to access motion data for your app. diff --git a/pedometer/ios/Classes/dart_api_dl.c b/pedometer/ios/Classes/dart_api_dl.c new file mode 100644 index 0000000..20b9bf2 --- /dev/null +++ b/pedometer/ios/Classes/dart_api_dl.c @@ -0,0 +1,2 @@ +#include "../../src/dart-sdk/include/dart_api_dl.c" + diff --git a/pedometer/ios/Classes/pedometerHelper.m b/pedometer/ios/Classes/pedometerHelper.m new file mode 100644 index 0000000..ac82ca0 --- /dev/null +++ b/pedometer/ios/Classes/pedometerHelper.m @@ -0,0 +1,3 @@ +// Relative import to be able to reuse the C sources. +// See the comment in ../{projectName}}.podspec for more information. + #include "../../src/pedometerHelper.m" diff --git a/pedometer/ios/pedometer.podspec b/pedometer/ios/pedometer.podspec new file mode 100644 index 0000000..989f141 --- /dev/null +++ b/pedometer/ios/pedometer.podspec @@ -0,0 +1,29 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint pedometer.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'pedometer' + s.version = '0.0.1' + s.summary = 'A new Flutter FFI plugin project.' + s.description = <<-DESC +A new Flutter FFI plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + + # This will ensure the source files in Classes/ are included in the native + # builds of apps using this FFI plugin. Podspec does not support relative + # paths, so Classes contains a forwarder C file that relatively imports + # `../src/*` so that the C sources can be shared among all target platforms. + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'Flutter' + s.platform = :ios, '12.0' + s.requires_arc = [] + + # Flutter.framework does not contain a i386 slice. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } + s.swift_version = '5.0' +end diff --git a/pedometer/jnigen.yaml b/pedometer/jnigen.yaml new file mode 100644 index 0000000..3d7bc15 --- /dev/null +++ b/pedometer/jnigen.yaml @@ -0,0 +1,25 @@ +android_sdk_config: + add_gradle_deps: true + add_gradle_sources: true + android_example: 'example/' + +output: + c: + library_name: health_connect + path: src/health_connect/ + dart: + path: lib/health_connect.dart + structure: single_file + +classes: + - 'androidx.health.connect.client.HealthConnectClient' + - 'androidx.health.connect.client.PermissionController' + - 'androidx.health.connect.client.records.StepsRecord' + - 'androidx.health.connect.client.time' + - 'android.content.Context' + - 'android.content.Intent' + - 'android.app.Activity' + - 'java.time.Instant' + - 'androidx.health.connect.client.request' + - 'androidx.health.connect.client.aggregate.AggregationResult' + - 'androidx.health.connect.client.aggregate.AggregateMetric' diff --git a/pedometer/pubspec.yaml b/pedometer/pubspec.yaml new file mode 100644 index 0000000..5dfa80d --- /dev/null +++ b/pedometer/pubspec.yaml @@ -0,0 +1,79 @@ +name: pedometer +description: A new Flutter FFI plugin project. +version: 0.0.1 +publish_to: none +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.1.8 + jni: ^0.14.2 + ffi: ^2.1.2 + +dev_dependencies: + analysis_defaults: + path: ../analysis_defaults + ffigen: ^19.1.0 + jnigen: ^0.14.2 + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. + # + # Please refer to README.md for a detailed explanation. + plugin: + platforms: + ios: + ffiPlugin: true + android: + ffiPlugin: true + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/pedometer/src/dart-sdk/include/dart_api_dl.c b/pedometer/src/dart-sdk/include/dart_api_dl.c new file mode 100644 index 0000000..c4a68f4 --- /dev/null +++ b/pedometer/src/dart-sdk/include/dart_api_dl.c @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +#include "dart_api_dl.h" /* NOLINT */ +#include "dart_version.h" /* NOLINT */ +#include "internal/dart_api_dl_impl.h" /* NOLINT */ + +#include + +#define DART_API_DL_DEFINITIONS(name, R, A) name##_Type name##_DL = NULL; + +DART_API_ALL_DL_SYMBOLS(DART_API_DL_DEFINITIONS) + +#undef DART_API_DL_DEFINITIONS + +typedef void* DartApiEntry_function; + +DartApiEntry_function FindFunctionPointer(const DartApiEntry* entries, + const char* name) { + while (entries->name != NULL) { + if (strcmp(entries->name, name) == 0) return entries->function; + entries++; + } + return NULL; +} + +intptr_t Dart_InitializeApiDL(void* data) { + DartApi* dart_api_data = (DartApi*)data; + + if (dart_api_data->major != DART_API_DL_MAJOR_VERSION) { + // If the DartVM we're running on does not have the same version as this + // file was compiled against, refuse to initialize. The symbols are not + // compatible. + return -1; + } + // Minor versions are allowed to be different. + // If the DartVM has a higher minor version, it will provide more symbols + // than we initialize here. + // If the DartVM has a lower minor version, it will not provide all symbols. + // In that case, we leave the missing symbols un-initialized. Those symbols + // should not be used by the Dart and native code. The client is responsible + // for checking the minor version number himself based on which symbols it + // is using. + // (If we would error out on this case, recompiling native code against a + // newer SDK would break all uses on older SDKs, which is too strict.) + + const DartApiEntry* dart_api_function_pointers = dart_api_data->functions; + +#define DART_API_DL_INIT(name, R, A) \ + name##_DL = \ + (name##_Type)(FindFunctionPointer(dart_api_function_pointers, #name)); + DART_API_ALL_DL_SYMBOLS(DART_API_DL_INIT) +#undef DART_API_DL_INIT + + return 0; +} diff --git a/pedometer/src/dart-sdk/include/dart_api_dl.h b/pedometer/src/dart-sdk/include/dart_api_dl.h new file mode 100644 index 0000000..62f48b6 --- /dev/null +++ b/pedometer/src/dart-sdk/include/dart_api_dl.h @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +#ifndef RUNTIME_INCLUDE_DART_API_DL_H_ +#define RUNTIME_INCLUDE_DART_API_DL_H_ + +#include "dart_api.h" /* NOLINT */ +#include "dart_native_api.h" /* NOLINT */ + +/** \mainpage Dynamically Linked Dart API + * + * This exposes a subset of symbols from dart_api.h and dart_native_api.h + * available in every Dart embedder through dynamic linking. + * + * All symbols are postfixed with _DL to indicate that they are dynamically + * linked and to prevent conflicts with the original symbol. + * + * Link `dart_api_dl.c` file into your library and invoke + * `Dart_InitializeApiDL` with `NativeApi.initializeApiDLData`. + */ + +DART_EXPORT intptr_t Dart_InitializeApiDL(void* data); + +// ============================================================================ +// IMPORTANT! Never update these signatures without properly updating +// DART_API_DL_MAJOR_VERSION and DART_API_DL_MINOR_VERSION. +// +// Verbatim copy of `dart_native_api.h` and `dart_api.h` symbol names and types +// to trigger compile-time errors if the sybols in those files are updated +// without updating these. +// +// Function return and argument types, and typedefs are carbon copied. Structs +// are typechecked nominally in C/C++, so they are not copied, instead a +// comment is added to their definition. +typedef int64_t Dart_Port_DL; + +typedef void (*Dart_NativeMessageHandler_DL)(Dart_Port_DL dest_port_id, + Dart_CObject* message); + +// dart_native_api.h symbols can be called on any thread. +#define DART_NATIVE_API_DL_SYMBOLS(F) \ + /***** dart_native_api.h *****/ \ + /* Dart_Port */ \ + F(Dart_PostCObject, bool, (Dart_Port_DL port_id, Dart_CObject * message)) \ + F(Dart_PostInteger, bool, (Dart_Port_DL port_id, int64_t message)) \ + F(Dart_NewNativePort, Dart_Port_DL, \ + (const char* name, Dart_NativeMessageHandler_DL handler, \ + bool handle_concurrently)) \ + F(Dart_CloseNativePort, bool, (Dart_Port_DL native_port_id)) + +// dart_api.h symbols can only be called on Dart threads. +#define DART_API_DL_SYMBOLS(F) \ + /***** dart_api.h *****/ \ + /* Errors */ \ + F(Dart_IsError, bool, (Dart_Handle handle)) \ + F(Dart_IsApiError, bool, (Dart_Handle handle)) \ + F(Dart_IsUnhandledExceptionError, bool, (Dart_Handle handle)) \ + F(Dart_IsCompilationError, bool, (Dart_Handle handle)) \ + F(Dart_IsFatalError, bool, (Dart_Handle handle)) \ + F(Dart_GetError, const char*, (Dart_Handle handle)) \ + F(Dart_ErrorHasException, bool, (Dart_Handle handle)) \ + F(Dart_ErrorGetException, Dart_Handle, (Dart_Handle handle)) \ + F(Dart_ErrorGetStackTrace, Dart_Handle, (Dart_Handle handle)) \ + F(Dart_NewApiError, Dart_Handle, (const char* error)) \ + F(Dart_NewCompilationError, Dart_Handle, (const char* error)) \ + F(Dart_NewUnhandledExceptionError, Dart_Handle, (Dart_Handle exception)) \ + F(Dart_PropagateError, void, (Dart_Handle handle)) \ + /* Dart_Handle, Dart_PersistentHandle, Dart_WeakPersistentHandle */ \ + F(Dart_HandleFromPersistent, Dart_Handle, (Dart_PersistentHandle object)) \ + F(Dart_HandleFromWeakPersistent, Dart_Handle, \ + (Dart_WeakPersistentHandle object)) \ + F(Dart_NewPersistentHandle, Dart_PersistentHandle, (Dart_Handle object)) \ + F(Dart_SetPersistentHandle, void, \ + (Dart_PersistentHandle obj1, Dart_Handle obj2)) \ + F(Dart_DeletePersistentHandle, void, (Dart_PersistentHandle object)) \ + F(Dart_NewWeakPersistentHandle, Dart_WeakPersistentHandle, \ + (Dart_Handle object, void* peer, intptr_t external_allocation_size, \ + Dart_HandleFinalizer callback)) \ + F(Dart_DeleteWeakPersistentHandle, void, (Dart_WeakPersistentHandle object)) \ + F(Dart_UpdateExternalSize, void, \ + (Dart_WeakPersistentHandle object, intptr_t external_allocation_size)) \ + F(Dart_NewFinalizableHandle, Dart_FinalizableHandle, \ + (Dart_Handle object, void* peer, intptr_t external_allocation_size, \ + Dart_HandleFinalizer callback)) \ + F(Dart_DeleteFinalizableHandle, void, \ + (Dart_FinalizableHandle object, Dart_Handle strong_ref_to_object)) \ + F(Dart_UpdateFinalizableExternalSize, void, \ + (Dart_FinalizableHandle object, Dart_Handle strong_ref_to_object, \ + intptr_t external_allocation_size)) \ + /* Dart_Port */ \ + F(Dart_Post, bool, (Dart_Port_DL port_id, Dart_Handle object)) \ + F(Dart_NewSendPort, Dart_Handle, (Dart_Port_DL port_id)) \ + F(Dart_SendPortGetId, Dart_Handle, \ + (Dart_Handle port, Dart_Port_DL * port_id)) \ + /* Scopes */ \ + F(Dart_EnterScope, void, ()) \ + F(Dart_ExitScope, void, ()) + +#define DART_API_ALL_DL_SYMBOLS(F) \ + DART_NATIVE_API_DL_SYMBOLS(F) \ + DART_API_DL_SYMBOLS(F) +// IMPORTANT! Never update these signatures without properly updating +// DART_API_DL_MAJOR_VERSION and DART_API_DL_MINOR_VERSION. +// +// End of verbatim copy. +// ============================================================================ + +// Copy of definition of DART_EXPORT without 'used' attribute. +// +// The 'used' attribute cannot be used with DART_API_ALL_DL_SYMBOLS because +// they are not function declarations, but variable declarations with a +// function pointer type. +// +// The function pointer variables are initialized with the addresses of the +// functions in the VM. If we were to use function declarations instead, we +// would need to forward the call to the VM adding indirection. +#if defined(__CYGWIN__) +#error Tool chain and platform not supported. +#elif defined(_WIN32) +#if defined(DART_SHARED_LIB) +#define DART_EXPORT_DL DART_EXTERN_C __declspec(dllexport) +#else +#define DART_EXPORT_DL DART_EXTERN_C +#endif +#else +#if __GNUC__ >= 4 +#if defined(DART_SHARED_LIB) +#define DART_EXPORT_DL DART_EXTERN_C __attribute__((visibility("default"))) +#else +#define DART_EXPORT_DL DART_EXTERN_C +#endif +#else +#error Tool chain not supported. +#endif +#endif + +#define DART_API_DL_DECLARATIONS(name, R, A) \ + typedef R(*name##_Type) A; \ + DART_EXPORT_DL name##_Type name##_DL; + +DART_API_ALL_DL_SYMBOLS(DART_API_DL_DECLARATIONS) + +#undef DART_API_DL_DECLARATIONS + +#undef DART_EXPORT_DL + +#endif /* RUNTIME_INCLUDE_DART_API_DL_H_ */ /* NOLINT */ \ No newline at end of file diff --git a/pedometer/src/dart-sdk/include/dart_native_api.h b/pedometer/src/dart-sdk/include/dart_native_api.h new file mode 100644 index 0000000..f99fff1 --- /dev/null +++ b/pedometer/src/dart-sdk/include/dart_native_api.h @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +#ifndef RUNTIME_INCLUDE_DART_NATIVE_API_H_ +#define RUNTIME_INCLUDE_DART_NATIVE_API_H_ + +#include "dart_api.h" /* NOLINT */ + +/* + * ========================================== + * Message sending/receiving from native code + * ========================================== + */ + +/** + * A Dart_CObject is used for representing Dart objects as native C + * data outside the Dart heap. These objects are totally detached from + * the Dart heap. Only a subset of the Dart objects have a + * representation as a Dart_CObject. + * + * The string encoding in the 'value.as_string' is UTF-8. + * + * All the different types from dart:typed_data are exposed as type + * kTypedData. The specific type from dart:typed_data is in the type + * field of the as_typed_data structure. The length in the + * as_typed_data structure is always in bytes. + * + * The data for kTypedData is copied on message send and ownership remains with + * the caller. The ownership of data for kExternalTyped is passed to the VM on + * message send and returned when the VM invokes the + * Dart_HandleFinalizer callback; a non-NULL callback must be provided. + */ +typedef enum { + Dart_CObject_kNull = 0, + Dart_CObject_kBool, + Dart_CObject_kInt32, + Dart_CObject_kInt64, + Dart_CObject_kDouble, + Dart_CObject_kString, + Dart_CObject_kArray, + Dart_CObject_kTypedData, + Dart_CObject_kExternalTypedData, + Dart_CObject_kSendPort, + Dart_CObject_kCapability, + Dart_CObject_kNativePointer, + Dart_CObject_kUnsupported, + Dart_CObject_kNumberOfTypes +} Dart_CObject_Type; + +typedef struct _Dart_CObject { + Dart_CObject_Type type; + union { + bool as_bool; + int32_t as_int32; + int64_t as_int64; + double as_double; + char* as_string; + struct { + Dart_Port id; + Dart_Port origin_id; + } as_send_port; + struct { + int64_t id; + } as_capability; + struct { + intptr_t length; + struct _Dart_CObject** values; + } as_array; + struct { + Dart_TypedData_Type type; + intptr_t length; /* in elements, not bytes */ + uint8_t* values; + } as_typed_data; + struct { + Dart_TypedData_Type type; + intptr_t length; /* in elements, not bytes */ + uint8_t* data; + void* peer; + Dart_HandleFinalizer callback; + } as_external_typed_data; + struct { + intptr_t ptr; + intptr_t size; + Dart_HandleFinalizer callback; + } as_native_pointer; + } value; +} Dart_CObject; +// This struct is versioned by DART_API_DL_MAJOR_VERSION, bump the version when +// changing this struct. + +/** + * Posts a message on some port. The message will contain the Dart_CObject + * object graph rooted in 'message'. + * + * While the message is being sent the state of the graph of Dart_CObject + * structures rooted in 'message' should not be accessed, as the message + * generation will make temporary modifications to the data. When the message + * has been sent the graph will be fully restored. + * + * If true is returned, the message was enqueued, and finalizers for external + * typed data will eventually run, even if the receiving isolate shuts down + * before processing the message. If false is returned, the message was not + * enqueued and ownership of external typed data in the message remains with the + * caller. + * + * This function may be called on any thread when the VM is running (that is, + * after Dart_Initialize has returned and before Dart_Cleanup has been called). + * + * \param port_id The destination port. + * \param message The message to send. + * + * \return True if the message was posted. + */ +DART_EXPORT bool Dart_PostCObject(Dart_Port port_id, Dart_CObject* message); + +/** + * Posts a message on some port. The message will contain the integer 'message'. + * + * \param port_id The destination port. + * \param message The message to send. + * + * \return True if the message was posted. + */ +DART_EXPORT bool Dart_PostInteger(Dart_Port port_id, int64_t message); + +/** + * A native message handler. + * + * This handler is associated with a native port by calling + * Dart_NewNativePort. + * + * The message received is decoded into the message structure. The + * lifetime of the message data is controlled by the caller. All the + * data references from the message are allocated by the caller and + * will be reclaimed when returning to it. + */ +typedef void (*Dart_NativeMessageHandler)(Dart_Port dest_port_id, + Dart_CObject* message); + +/** + * Creates a new native port. When messages are received on this + * native port, then they will be dispatched to the provided native + * message handler. + * + * \param name The name of this port in debugging messages. + * \param handler The C handler to run when messages arrive on the port. + * \param handle_concurrently Is it okay to process requests on this + * native port concurrently? + * + * \return If successful, returns the port id for the native port. In + * case of error, returns ILLEGAL_PORT. + */ +DART_EXPORT Dart_Port Dart_NewNativePort(const char* name, + Dart_NativeMessageHandler handler, + bool handle_concurrently); +/* TODO(turnidge): Currently handle_concurrently is ignored. */ + +/** + * Closes the native port with the given id. + * + * The port must have been allocated by a call to Dart_NewNativePort. + * + * \param native_port_id The id of the native port to close. + * + * \return Returns true if the port was closed successfully. + */ +DART_EXPORT bool Dart_CloseNativePort(Dart_Port native_port_id); + +/* + * ================== + * Verification Tools + * ================== + */ + +/** + * Forces all loaded classes and functions to be compiled eagerly in + * the current isolate.. + * + * TODO(turnidge): Document. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_CompileAll(); + +/** + * Finalizes all classes. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT Dart_Handle Dart_FinalizeAllClasses(); + +/* This function is intentionally undocumented. + * + * It should not be used outside internal tests. + */ +DART_EXPORT void* Dart_ExecuteInternalCommand(const char* command, void* arg); + +#endif /* INCLUDE_DART_NATIVE_API_H_ */ /* NOLINT */ diff --git a/pedometer/src/dart-sdk/include/dart_tools_api.h b/pedometer/src/dart-sdk/include/dart_tools_api.h new file mode 100644 index 0000000..f36ec6b --- /dev/null +++ b/pedometer/src/dart-sdk/include/dart_tools_api.h @@ -0,0 +1,526 @@ +// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#ifndef RUNTIME_INCLUDE_DART_TOOLS_API_H_ +#define RUNTIME_INCLUDE_DART_TOOLS_API_H_ + +#include "dart_api.h" /* NOLINT */ + +/** \mainpage Dart Tools Embedding API Reference + * + * This reference describes the Dart embedding API for tools. Tools include + * a debugger, service protocol, and timeline. + * + * NOTE: The APIs described in this file are unstable and subject to change. + * + * This reference is generated from the header include/dart_tools_api.h. + */ + +/* + * ======== + * Debugger + * ======== + */ + +/** + * ILLEGAL_ISOLATE_ID is a number guaranteed never to be associated with a + * valid isolate. + */ +#define ILLEGAL_ISOLATE_ID ILLEGAL_PORT + + +/* + * ======= + * Service + * ======= + */ + +/** + * A service request callback function. + * + * These callbacks, registered by the embedder, are called when the VM receives + * a service request it can't handle and the service request command name + * matches one of the embedder registered handlers. + * + * The return value of the callback indicates whether the response + * should be used as a regular result or an error result. + * Specifically, if the callback returns true, a regular JSON-RPC + * response is built in the following way: + * + * { + * "jsonrpc": "2.0", + * "result": , + * "id": , + * } + * + * If the callback returns false, a JSON-RPC error is built like this: + * + * { + * "jsonrpc": "2.0", + * "error": , + * "id": , + * } + * + * \param method The rpc method name. + * \param param_keys Service requests can have key-value pair parameters. The + * keys and values are flattened and stored in arrays. + * \param param_values The values associated with the keys. + * \param num_params The length of the param_keys and param_values arrays. + * \param user_data The user_data pointer registered with this handler. + * \param result A C string containing a valid JSON object. The returned + * pointer will be freed by the VM by calling free. + * + * \return True if the result is a regular JSON-RPC response, false if the + * result is a JSON-RPC error. + */ +typedef bool (*Dart_ServiceRequestCallback)(const char* method, + const char** param_keys, + const char** param_values, + intptr_t num_params, + void* user_data, + const char** json_object); + +/** + * Register a Dart_ServiceRequestCallback to be called to handle + * requests for the named rpc on a specific isolate. The callback will + * be invoked with the current isolate set to the request target. + * + * \param method The name of the method that this callback is responsible for. + * \param callback The callback to invoke. + * \param user_data The user data passed to the callback. + * + * NOTE: If multiple callbacks with the same name are registered, only + * the last callback registered will be remembered. + */ +DART_EXPORT void Dart_RegisterIsolateServiceRequestCallback( + const char* method, + Dart_ServiceRequestCallback callback, + void* user_data); + +/** + * Register a Dart_ServiceRequestCallback to be called to handle + * requests for the named rpc. The callback will be invoked without a + * current isolate. + * + * \param method The name of the command that this callback is responsible for. + * \param callback The callback to invoke. + * \param user_data The user data passed to the callback. + * + * NOTE: If multiple callbacks with the same name are registered, only + * the last callback registered will be remembered. + */ +DART_EXPORT void Dart_RegisterRootServiceRequestCallback( + const char* method, + Dart_ServiceRequestCallback callback, + void* user_data); + +/** + * Embedder information which can be requested by the VM for internal or + * reporting purposes. + * + * The pointers in this structure are not going to be cached or freed by the VM. + */ + + #define DART_EMBEDDER_INFORMATION_CURRENT_VERSION (0x00000001) + +typedef struct { + int32_t version; + const char* name; // [optional] The name of the embedder + int64_t current_rss; // [optional] the current RSS of the embedder + int64_t max_rss; // [optional] the maximum RSS of the embedder +} Dart_EmbedderInformation; + +/** + * Callback provided by the embedder that is used by the vm to request + * information. + * + * \return Returns a pointer to a Dart_EmbedderInformation structure. + * The embedder keeps the ownership of the structure and any field in it. + * The embedder must ensure that the structure will remain valid until the + * next invokation of the callback. + */ +typedef void (*Dart_EmbedderInformationCallback)( + Dart_EmbedderInformation* info); + +/** + * Register a Dart_ServiceRequestCallback to be called to handle + * requests for the named rpc. The callback will be invoked without a + * current isolate. + * + * \param method The name of the command that this callback is responsible for. + * \param callback The callback to invoke. + * \param user_data The user data passed to the callback. + * + * NOTE: If multiple callbacks with the same name are registered, only + * the last callback registered will be remembered. + */ +DART_EXPORT void Dart_SetEmbedderInformationCallback( + Dart_EmbedderInformationCallback callback); + +/** + * Invoke a vm-service method and wait for its result. + * + * \param request_json The utf8-encoded json-rpc request. + * \param request_json_length The length of the json-rpc request. + * + * \param response_json The returned utf8-encoded json response, must be + * free()ed by caller. + * \param response_json_length The length of the returned json response. + * \param error An optional error, must be free()ed by caller. + * + * \return Whether the call was sucessfully performed. + * + * NOTE: This method does not need a current isolate and must not have the + * vm-isolate being the current isolate. It must be called after + * Dart_Initialize() and before Dart_Cleanup(). + */ +DART_EXPORT bool Dart_InvokeVMServiceMethod(uint8_t* request_json, + intptr_t request_json_length, + uint8_t** response_json, + intptr_t* response_json_length, + char** error); + +/* + * ======== + * Event Streams + * ======== + */ + +/** + * A callback invoked when the VM service gets a request to listen to + * some stream. + * + * \return Returns true iff the embedder supports the named stream id. + */ +typedef bool (*Dart_ServiceStreamListenCallback)(const char* stream_id); + +/** + * A callback invoked when the VM service gets a request to cancel + * some stream. + */ +typedef void (*Dart_ServiceStreamCancelCallback)(const char* stream_id); + +/** + * Adds VM service stream callbacks. + * + * \param listen_callback A function pointer to a listen callback function. + * A listen callback function should not be already set when this function + * is called. A NULL value removes the existing listen callback function + * if any. + * + * \param cancel_callback A function pointer to a cancel callback function. + * A cancel callback function should not be already set when this function + * is called. A NULL value removes the existing cancel callback function + * if any. + * + * \return Success if the callbacks were added. Otherwise, returns an + * error handle. + */ +DART_EXPORT char* Dart_SetServiceStreamCallbacks( + Dart_ServiceStreamListenCallback listen_callback, + Dart_ServiceStreamCancelCallback cancel_callback); + +/** + * A callback invoked when the VM service receives an event. + */ +typedef void (*Dart_NativeStreamConsumer)(const uint8_t* event_json, + intptr_t event_json_length); + +/** + * Sets the native VM service stream callbacks for a particular stream. + * Note: The function may be called on multiple threads concurrently. + * + * \param consumer A function pointer to an event handler callback function. + * A NULL value removes the existing listen callback function if any. + * + * \param stream_id The ID of the stream on which to set the callback. + */ +DART_EXPORT void Dart_SetNativeServiceStreamCallback( + Dart_NativeStreamConsumer consumer, + const char* stream_id); + +/** + * Sends a data event to clients of the VM Service. + * + * A data event is used to pass an array of bytes to subscribed VM + * Service clients. For example, in the standalone embedder, this is + * function used to provide WriteEvents on the Stdout and Stderr + * streams. + * + * If the embedder passes in a stream id for which no client is + * subscribed, then the event is ignored. + * + * \param stream_id The id of the stream on which to post the event. + * + * \param event_kind A string identifying what kind of event this is. + * For example, 'WriteEvent'. + * + * \param bytes A pointer to an array of bytes. + * + * \param bytes_length The length of the byte array. + * + * \return NULL if the arguments are well formed. Otherwise, returns an + * error string. The caller is responsible for freeing the error message. + */ +DART_EXPORT char* Dart_ServiceSendDataEvent(const char* stream_id, + const char* event_kind, + const uint8_t* bytes, + intptr_t bytes_length); + +/** + * Usage statistics for a space/generation at a particular moment in time. + * + * \param used Amount of memory used, in bytes. + * + * \param capacity Memory capacity, in bytes. + * + * \param external External memory, in bytes. + * + * \param collections How many times the garbage collector has run in this + * space. + * + * \param time Cumulative time spent collecting garbage in this space, in + * seconds. + * + * \param avg_collection_period Average time between garbage collector running + * in this space, in milliseconds. + */ +typedef struct { + intptr_t used; + intptr_t capacity; + intptr_t external; + intptr_t collections; + double time; + double avg_collection_period; +} Dart_GCStats; + +/** + * A Garbage Collection event with memory usage statistics. + * + * \param type The event type. Static lifetime. + * + * \param reason The reason for the GC event. Static lifetime. + * + * \param new_space Data for New Space. + * + * \param old_space Data for Old Space. + */ +typedef struct { + const char* type; + const char* reason; + const char* isolate_id; + + Dart_GCStats new_space; + Dart_GCStats old_space; +} Dart_GCEvent; + +/** + * A callback invoked when the VM emits a GC event. + * + * \param event The GC event data. Pointer only valid for the duration of the + * callback. + */ +typedef void (*Dart_GCEventCallback)(Dart_GCEvent* event); + +/** + * Sets the native GC event callback. + * + * \param callback A function pointer to an event handler callback function. + * A NULL value removes the existing listen callback function if any. + */ +DART_EXPORT void Dart_SetGCEventCallback(Dart_GCEventCallback callback); + +/* + * ======== + * Reload support + * ======== + * + * These functions are used to implement reloading in the Dart VM. + * This is an experimental feature, so embedders should be prepared + * for these functions to change. + */ + +/** + * A callback which determines whether the file at some url has been + * modified since some time. If the file cannot be found, true should + * be returned. + */ +typedef bool (*Dart_FileModifiedCallback)(const char* url, int64_t since); + +DART_EXPORT char* Dart_SetFileModifiedCallback( + Dart_FileModifiedCallback file_modified_callback); + +/** + * Returns true if isolate is currently reloading. + */ +DART_EXPORT bool Dart_IsReloading(); + +/* + * ======== + * Timeline + * ======== + */ + +/** + * Returns a timestamp in microseconds. This timestamp is suitable for + * passing into the timeline system, and uses the same monotonic clock + * as dart:developer's Timeline.now. + * + * \return A timestamp that can be passed to the timeline system. + */ +DART_EXPORT int64_t Dart_TimelineGetMicros(); + +/** + * Returns a raw timestamp in from the monotonic clock. + * + * \return A raw timestamp from the monotonic clock. + */ +DART_EXPORT int64_t Dart_TimelineGetTicks(); + +/** + * Returns the frequency of the monotonic clock. + * + * \return The frequency of the monotonic clock. + */ +DART_EXPORT int64_t Dart_TimelineGetTicksFrequency(); + +typedef enum { + Dart_Timeline_Event_Begin, // Phase = 'B'. + Dart_Timeline_Event_End, // Phase = 'E'. + Dart_Timeline_Event_Instant, // Phase = 'i'. + Dart_Timeline_Event_Duration, // Phase = 'X'. + Dart_Timeline_Event_Async_Begin, // Phase = 'b'. + Dart_Timeline_Event_Async_End, // Phase = 'e'. + Dart_Timeline_Event_Async_Instant, // Phase = 'n'. + Dart_Timeline_Event_Counter, // Phase = 'C'. + Dart_Timeline_Event_Flow_Begin, // Phase = 's'. + Dart_Timeline_Event_Flow_Step, // Phase = 't'. + Dart_Timeline_Event_Flow_End, // Phase = 'f'. +} Dart_Timeline_Event_Type; + +/** + * Add a timeline event to the embedder stream. + * + * \param label The name of the event. Its lifetime must extend at least until + * Dart_Cleanup. + * \param timestamp0 The first timestamp of the event. + * \param timestamp1_or_async_id The second timestamp of the event or + * the async id. + * \param argument_count The number of argument names and values. + * \param argument_names An array of names of the arguments. The lifetime of the + * names must extend at least until Dart_Cleanup. The array may be reclaimed + * when this call returns. + * \param argument_values An array of values of the arguments. The values and + * the array may be reclaimed when this call returns. + */ +DART_EXPORT void Dart_TimelineEvent(const char* label, + int64_t timestamp0, + int64_t timestamp1_or_async_id, + Dart_Timeline_Event_Type type, + intptr_t argument_count, + const char** argument_names, + const char** argument_values); + +/** + * Associates a name with the current thread. This name will be used to name + * threads in the timeline. Can only be called after a call to Dart_Initialize. + * + * \param name The name of the thread. + */ +DART_EXPORT void Dart_SetThreadName(const char* name); + +/* + * ======= + * Metrics + * ======= + */ + +/** + * Return metrics gathered for the VM and individual isolates. + * + * NOTE: Non-heap metrics are not available in PRODUCT builds of Dart. + * Calling the non-heap metric functions on a PRODUCT build might return invalid metrics. + */ +DART_EXPORT int64_t Dart_VMIsolateCountMetric(); // Counter +DART_EXPORT int64_t Dart_VMCurrentRSSMetric(); // Byte +DART_EXPORT int64_t Dart_VMPeakRSSMetric(); // Byte +DART_EXPORT int64_t +Dart_IsolateHeapOldUsedMetric(Dart_Isolate isolate); // Byte +DART_EXPORT int64_t +Dart_IsolateHeapOldUsedMaxMetric(Dart_Isolate isolate); // Byte +DART_EXPORT int64_t +Dart_IsolateHeapOldCapacityMetric(Dart_Isolate isolate); // Byte +DART_EXPORT int64_t +Dart_IsolateHeapOldCapacityMaxMetric(Dart_Isolate isolate); // Byte +DART_EXPORT int64_t +Dart_IsolateHeapOldExternalMetric(Dart_Isolate isolate); // Byte +DART_EXPORT int64_t +Dart_IsolateHeapNewUsedMetric(Dart_Isolate isolate); // Byte +DART_EXPORT int64_t +Dart_IsolateHeapNewUsedMaxMetric(Dart_Isolate isolate); // Byte +DART_EXPORT int64_t +Dart_IsolateHeapNewCapacityMetric(Dart_Isolate isolate); // Byte +DART_EXPORT int64_t +Dart_IsolateHeapNewCapacityMaxMetric(Dart_Isolate isolate); // Byte +DART_EXPORT int64_t +Dart_IsolateHeapNewExternalMetric(Dart_Isolate isolate); // Byte +DART_EXPORT int64_t +Dart_IsolateHeapGlobalUsedMetric(Dart_Isolate isolate); // Byte +DART_EXPORT int64_t +Dart_IsolateHeapGlobalUsedMaxMetric(Dart_Isolate isolate); // Byte +DART_EXPORT int64_t +Dart_IsolateRunnableLatencyMetric(Dart_Isolate isolate); // Microsecond +DART_EXPORT int64_t +Dart_IsolateRunnableHeapSizeMetric(Dart_Isolate isolate); // Byte + +/* + * ======== + * UserTags + * ======== + */ + +/* + * Gets the current isolate's currently set UserTag instance. + * + * \return The currently set UserTag instance. + */ +DART_EXPORT Dart_Handle Dart_GetCurrentUserTag(); + +/* + * Gets the current isolate's default UserTag instance. + * + * \return The default UserTag with label 'Default' + */ +DART_EXPORT Dart_Handle Dart_GetDefaultUserTag(); + +/* + * Creates a new UserTag instance. + * + * \param label The name of the new UserTag. + * + * \return The newly created UserTag instance or an error handle. + */ +DART_EXPORT Dart_Handle Dart_NewUserTag(const char* label); + +/* + * Updates the current isolate's UserTag to a new value. + * + * \param user_tag The UserTag to be set as the current UserTag. + * + * \return The previously set UserTag instance or an error handle. + */ +DART_EXPORT Dart_Handle Dart_SetCurrentUserTag(Dart_Handle user_tag); + +/* + * Returns the label of a given UserTag instance. + * + * \param user_tag The UserTag from which the label will be retrieved. + * + * \return The UserTag's label. NULL if the user_tag is invalid. The caller is + * responsible for freeing the returned label. + */ +DART_EXPORT DART_WARN_UNUSED_RESULT char* Dart_GetUserTagLabel( + Dart_Handle user_tag); + +#endif // RUNTIME_INCLUDE_DART_TOOLS_API_H_ diff --git a/pedometer/src/dart-sdk/include/dart_version.h b/pedometer/src/dart-sdk/include/dart_version.h new file mode 100644 index 0000000..b3b4924 --- /dev/null +++ b/pedometer/src/dart-sdk/include/dart_version.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +#ifndef RUNTIME_INCLUDE_DART_VERSION_H_ +#define RUNTIME_INCLUDE_DART_VERSION_H_ + +// On breaking changes the major version is increased. +// On backwards compatible changes the minor version is increased. +// The versioning covers the symbols exposed in dart_api_dl.h +#define DART_API_DL_MAJOR_VERSION 2 +#define DART_API_DL_MINOR_VERSION 0 + +#endif /* RUNTIME_INCLUDE_DART_VERSION_H_ */ /* NOLINT */ diff --git a/pedometer/src/dart-sdk/include/internal/dart_api_dl_impl.h b/pedometer/src/dart-sdk/include/internal/dart_api_dl_impl.h new file mode 100644 index 0000000..ad13a4b --- /dev/null +++ b/pedometer/src/dart-sdk/include/internal/dart_api_dl_impl.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file + * for details. All rights reserved. Use of this source code is governed by a + * BSD-style license that can be found in the LICENSE file. + */ + +#ifndef RUNTIME_INCLUDE_INTERNAL_DART_API_DL_IMPL_H_ +#define RUNTIME_INCLUDE_INTERNAL_DART_API_DL_IMPL_H_ + +typedef struct { + const char* name; + void (*function)(); +} DartApiEntry; + +typedef struct { + const int major; + const int minor; + const DartApiEntry* const functions; +} DartApi; + +#endif /* RUNTIME_INCLUDE_INTERNAL_DART_API_DL_IMPL_H_ */ /* NOLINT */ diff --git a/pedometer/src/health_connect/.clang-format b/pedometer/src/health_connect/.clang-format new file mode 100644 index 0000000..a256c2f --- /dev/null +++ b/pedometer/src/health_connect/.clang-format @@ -0,0 +1,15 @@ +# From dart SDK: https://github.com/dart-lang/sdk/blob/main/.clang-format + +# Defines the Chromium style for automatic reformatting. +# http://clang.llvm.org/docs/ClangFormatStyleOptions.html +BasedOnStyle: Chromium + +# clang-format doesn't seem to do a good job of this for longer comments. +ReflowComments: 'false' + +# We have lots of these. Though we need to put them all in curly braces, +# clang-format can't do that. +AllowShortIfStatementsOnASingleLine: 'true' + +# Put escaped newlines into the rightmost column. +AlignEscapedNewlinesLeft: false diff --git a/pedometer/src/health_connect/CMakeLists.txt b/pedometer/src/health_connect/CMakeLists.txt new file mode 100644 index 0000000..4877658 --- /dev/null +++ b/pedometer/src/health_connect/CMakeLists.txt @@ -0,0 +1,32 @@ +# jni_native_build (Build with jni:setup. Do not delete this line.) + +# The Flutter tooling requires that developers have CMake 3.10 or later +# installed. You should not increase this version, as doing so will cause +# the plugin to fail to compile for some customers of the plugin. +cmake_minimum_required(VERSION 3.10) + +project(health_connect VERSION 0.0.1 LANGUAGES C) + +add_library(health_connect SHARED + "./health_connect.c" +) + +set_target_properties(health_connect PROPERTIES + OUTPUT_NAME "health_connect" +) + +target_compile_definitions(health_connect PUBLIC DART_SHARED_LIB) + +if(WIN32) + set_target_properties(${TARGET_NAME} PROPERTIES + LINK_FLAGS "/DELAYLOAD:jvm.dll") +endif() + +if (ANDROID) + target_link_libraries(health_connect log) +else() + find_package(Java REQUIRED) + find_package(JNI REQUIRED) + include_directories(${JNI_INCLUDE_DIRS}) + target_link_libraries(health_connect ${JNI_LIBRARIES}) +endif() diff --git a/pedometer/src/health_connect/dartjni.h b/pedometer/src/health_connect/dartjni.h new file mode 100644 index 0000000..f834d15 --- /dev/null +++ b/pedometer/src/health_connect/dartjni.h @@ -0,0 +1,383 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#pragma once + +// Note: include appropriate system jni.h as found by CMake, not third_party/jni.h. +#include +#include +#include +#include + +#if _WIN32 +#include +#else +#include +#include +#endif + +#if _WIN32 +#define FFI_PLUGIN_EXPORT __declspec(dllexport) +#else +#define FFI_PLUGIN_EXPORT +#endif + +#if defined _WIN32 +#define thread_local __declspec(thread) +#else +#define thread_local __thread +#endif + +#ifdef __ANDROID__ +#include +#endif + +#ifdef __ANDROID__ +#define __ENVP_CAST (JNIEnv**) +#else +#define __ENVP_CAST (void**) +#endif + +/// Locking functions for windows and pthread. + +#if defined _WIN32 +#include + +typedef CRITICAL_SECTION MutexLock; +typedef CONDITION_VARIABLE ConditionVariable; + +static inline void init_lock(MutexLock* lock) { + InitializeCriticalSection(lock); +} + +static inline void acquire_lock(MutexLock* lock) { + EnterCriticalSection(lock); +} + +static inline void release_lock(MutexLock* lock) { + LeaveCriticalSection(lock); +} + +static inline void destroy_lock(MutexLock* lock) { + DeleteCriticalSection(lock); +} + +static inline void init_cond(ConditionVariable* cond) { + InitializeConditionVariable(cond); +} + +static inline void signal_cond(ConditionVariable* cond) { + WakeConditionVariable(cond); +} + +static inline void wait_for(ConditionVariable* cond, MutexLock* lock) { + SleepConditionVariableCS(cond, lock, INFINITE); +} + +static inline void destroy_cond(ConditionVariable* cond) { + // Not available. +} + +static inline void free_mem(void* mem) { + CoTaskMemFree(mem); +} + +#elif defined __APPLE__ || defined __LINUX__ || defined __ANDROID__ || \ + defined __GNUC__ +#include + +typedef pthread_mutex_t MutexLock; +typedef pthread_cond_t ConditionVariable; + +static inline void init_lock(MutexLock* lock) { + pthread_mutex_init(lock, NULL); +} + +static inline void acquire_lock(MutexLock* lock) { + pthread_mutex_lock(lock); +} + +static inline void release_lock(MutexLock* lock) { + pthread_mutex_unlock(lock); +} + +static inline void destroy_lock(MutexLock* lock) { + pthread_mutex_destroy(lock); +} + +static inline void init_cond(ConditionVariable* cond) { + pthread_cond_init(cond, NULL); +} + +static inline void signal_cond(ConditionVariable* cond) { + pthread_cond_signal(cond); +} + +static inline void wait_for(ConditionVariable* cond, MutexLock* lock) { + pthread_cond_wait(cond, lock); +} + +static inline void destroy_cond(ConditionVariable* cond) { + pthread_cond_destroy(cond); +} + +static inline void free_mem(void* mem) { + free(mem); +} + +#else + +#error "No locking/condition variable support; Possibly unsupported platform" + +#endif + +typedef struct CallbackResult { + MutexLock lock; + ConditionVariable cond; + int ready; + jobject object; +} CallbackResult; + +typedef struct JniLocks { + MutexLock classLoadingLock; +} JniLocks; + +/// Represents the error when dart-jni layer has already spawned singleton VM. +#define DART_JNI_SINGLETON_EXISTS (-99); + +/// Stores the global state of the JNI. +typedef struct JniContext { + JavaVM* jvm; + jobject classLoader; + jmethodID loadClassMethod; + jobject currentActivity; + jobject appContext; + JniLocks locks; +} JniContext; + +// jniEnv for this thread, used by inline functions in this header, +// therefore declared as extern. +extern thread_local JNIEnv* jniEnv; + +extern JniContext* jni; + +/// Types used by JNI API to distinguish between primitive types. +enum JniType { + booleanType = 0, + byteType = 1, + shortType = 2, + charType = 3, + intType = 4, + longType = 5, + floatType = 6, + doubleType = 7, + objectType = 8, + voidType = 9, +}; + +/// Result type for use by JNI. +/// +/// If [exception] is null, it means the result is valid. +/// It's assumed that the caller knows the expected type in [result]. +typedef struct JniResult { + jvalue value; + jthrowable exception; +} JniResult; + +/// Similar to [JniResult] but for class lookups. +typedef struct JniClassLookupResult { + jclass value; + jthrowable exception; +} JniClassLookupResult; + +/// Similar to [JniResult] but for method/field ID lookups. +typedef struct JniPointerResult { + const void* value; + jthrowable exception; +} JniPointerResult; + +/// JniExceptionDetails holds 2 jstring objects, one is the result of +/// calling `toString` on exception object, other is stack trace; +typedef struct JniExceptionDetails { + jstring message; + jstring stacktrace; +} JniExceptionDetails; + +/// This struct contains functions which wrap method call / field access conveniently along with +/// exception checking. +/// +/// Flutter embedding checks for pending JNI exceptions before an FFI transition, which requires us +/// to check for and clear the exception before returning to dart code, which requires these functions +/// to return result types. +typedef struct JniAccessorsStruct { + JniClassLookupResult (*getClass)(char* internalName); + JniPointerResult (*getFieldID)(jclass cls, char* fieldName, char* signature); + JniPointerResult (*getStaticFieldID)(jclass cls, + char* fieldName, + char* signature); + JniPointerResult (*getMethodID)(jclass cls, + char* methodName, + char* signature); + JniPointerResult (*getStaticMethodID)(jclass cls, + char* methodName, + char* signature); + JniResult (*newObject)(jclass cls, jmethodID ctor, jvalue* args); + JniResult (*newPrimitiveArray)(jsize length, int type); + JniResult (*newObjectArray)(jsize length, + jclass elementClass, + jobject initialElement); + JniResult (*getArrayElement)(jarray array, int index, int type); + JniResult (*callMethod)(jobject obj, + jmethodID methodID, + int callType, + jvalue* args); + JniResult (*callStaticMethod)(jclass cls, + jmethodID methodID, + int callType, + jvalue* args); + JniResult (*getField)(jobject obj, jfieldID fieldID, int callType); + JniResult (*getStaticField)(jclass cls, jfieldID fieldID, int callType); + JniExceptionDetails (*getExceptionDetails)(jthrowable exception); +} JniAccessorsStruct; + +FFI_PLUGIN_EXPORT JniAccessorsStruct* GetAccessors(); + +FFI_PLUGIN_EXPORT JavaVM* GetJavaVM(void); + +FFI_PLUGIN_EXPORT JNIEnv* GetJniEnv(void); + +/// Spawn a JVM with given arguments. +/// +/// Returns JNI_OK on success, and one of the documented JNI error codes on +/// failure. It returns DART_JNI_SINGLETON_EXISTS if an attempt to spawn multiple +/// JVMs is made, even if the underlying API potentially supports multiple VMs. +FFI_PLUGIN_EXPORT int SpawnJvm(JavaVMInitArgs* args); + +/// Returns Application classLoader (on Android), +/// which can be used to load application and platform classes. +/// +/// On other platforms, NULL is returned. +FFI_PLUGIN_EXPORT jobject GetClassLoader(void); + +/// Returns application context on Android. +/// +/// On other platforms, NULL is returned. +FFI_PLUGIN_EXPORT jobject GetApplicationContext(void); + +/// Returns current activity of the app on Android. +FFI_PLUGIN_EXPORT jobject GetCurrentActivity(void); + +static inline void attach_thread() { + if (jniEnv == NULL) { + (*jni->jvm)->AttachCurrentThread(jni->jvm, __ENVP_CAST & jniEnv, NULL); + } +} + +/// Load class into [cls] using platform specific mechanism +static inline void load_class_platform(jclass* cls, const char* name) { +#ifdef __ANDROID__ + jstring className = (*jniEnv)->NewStringUTF(jniEnv, name); + *cls = (*jniEnv)->CallObjectMethod(jniEnv, jni->classLoader, + jni->loadClassMethod, className); + (*jniEnv)->DeleteLocalRef(jniEnv, className); +#else + *cls = (*jniEnv)->FindClass(jniEnv, name); +#endif +} + +static inline void load_class_global_ref(jclass* cls, const char* name) { + if (*cls == NULL) { + jclass tmp = NULL; + acquire_lock(&jni->locks.classLoadingLock); + if (*cls == NULL) { + load_class_platform(&tmp, name); + if (!(*jniEnv)->ExceptionCheck(jniEnv)) { + *cls = (*jniEnv)->NewGlobalRef(jniEnv, tmp); + (*jniEnv)->DeleteLocalRef(jniEnv, tmp); + } + } + release_lock(&jni->locks.classLoadingLock); + } +} + +static inline void load_method(jclass cls, + jmethodID* res, + const char* name, + const char* sig) { + if (*res == NULL) { + *res = (*jniEnv)->GetMethodID(jniEnv, cls, name, sig); + } +} + +static inline void load_static_method(jclass cls, + jmethodID* res, + const char* name, + const char* sig) { + if (*res == NULL) { + *res = (*jniEnv)->GetStaticMethodID(jniEnv, cls, name, sig); + } +} + +static inline void load_field(jclass cls, + jfieldID* res, + const char* name, + const char* sig) { + if (*res == NULL) { + *res = (*jniEnv)->GetFieldID(jniEnv, cls, name, sig); + } +} + +static inline void load_static_field(jclass cls, + jfieldID* res, + const char* name, + const char* sig) { + if (*res == NULL) { + *res = (*jniEnv)->GetStaticFieldID(jniEnv, cls, name, sig); + } +} + +static inline jobject to_global_ref(jobject ref) { + jobject g = (*jniEnv)->NewGlobalRef(jniEnv, ref); + (*jniEnv)->DeleteLocalRef(jniEnv, ref); + return g; +} + +// These functions are useful for C+Dart bindings, and not required for pure dart bindings. + +FFI_PLUGIN_EXPORT JniContext* GetJniContextPtr(); + +/// For use by jni_gen's generated code +/// don't use these. + +// these 2 fn ptr vars will be defined by generated code library +extern JniContext* (*context_getter)(void); +extern JNIEnv* (*env_getter)(void); + +// this function will be exported by generated code library +// it will set above 2 variables. +FFI_PLUGIN_EXPORT void setJniGetters(struct JniContext* (*cg)(void), + JNIEnv* (*eg)(void)); + +static inline void load_env() { + if (jniEnv == NULL) { + jni = context_getter(); + jniEnv = env_getter(); + } +} + +static inline jthrowable check_exception() { + jthrowable exception = (*jniEnv)->ExceptionOccurred(jniEnv); + if (exception != NULL) (*jniEnv)->ExceptionClear(jniEnv); + if (exception == NULL) return NULL; + return to_global_ref(exception); +} + +static inline JniResult to_global_ref_result(jobject ref) { + JniResult result; + result.exception = check_exception(); + if (result.exception == NULL) { + result.value.l = to_global_ref(ref); + } + return result; +} diff --git a/pedometer/src/pedometerHelper.h b/pedometer/src/pedometerHelper.h new file mode 100644 index 0000000..c2b0225 --- /dev/null +++ b/pedometer/src/pedometerHelper.h @@ -0,0 +1,7 @@ +#import +#import +#import + +// TODO(https://github.com/dart-lang/native/issues/835): Generate this wrapper +// automatically. +CMPedometerHandler wrapCallback(CMPedometerHandler callback); diff --git a/pedometer/src/pedometerHelper.m b/pedometer/src/pedometerHelper.m new file mode 100644 index 0000000..fe76a56 --- /dev/null +++ b/pedometer/src/pedometerHelper.m @@ -0,0 +1,12 @@ +#import "pedometerHelper.h" +#import +#import +#import + +// TODO(https://github.com/dart-lang/native/issues/835): Generate this wrapper +// automatically. +CMPedometerHandler wrapCallback(CMPedometerHandler callback) { + return [^(CMPedometerData *data, NSError *error) { + return callback([data retain], [error retain]); + } copy]; +} diff --git a/plan.md b/plan.md new file mode 100644 index 0000000..8ce1cd0 --- /dev/null +++ b/plan.md @@ -0,0 +1,19 @@ +# Galleries, demos, quickstarts + +A plan to migrate all samples to a unified, sane + +## Quickstarts + +Samples that are designed to be cloned and give a starting point to work with specific features. + * add_to_app + +## Demonstrations + +Samples that are designed to show complex problems solved in specific ways. + +* asset_transformation + +## Galleries + +Samples that are designed to + diff --git a/platform_channels/.gitignore b/platform_channels/.gitignore new file mode 100644 index 0000000..1ba9c33 --- /dev/null +++ b/platform_channels/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Exceptions to above rules. +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/platform_channels/.metadata b/platform_channels/.metadata new file mode 100644 index 0000000..6c1bf2f --- /dev/null +++ b/platform_channels/.metadata @@ -0,0 +1,33 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "db7ef5bf9f59442b0e200a90587e8fa5e0c6336a" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: android + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: ios + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/platform_channels/README.md b/platform_channels/README.md new file mode 100644 index 0000000..f621479 --- /dev/null +++ b/platform_channels/README.md @@ -0,0 +1,42 @@ +# Platform Channel Samples + +A sample app which demonstrates how to use `MethodChannel`, `EventChannel`, `BasicMessageChannel` and `MessageCodec` in Flutter. + +## Goals + +* Demonstrate how to use `MethodChannel` to invoke platform methods. +* Demonstrate how to use `EventChannel` to listen continuous value changes from the platform. +* Demonstrate how to use `BasicMessageChannel` and `MessageCodec` to send messages of different types across the platform. + +## The important bits + +### [Method Channel demo](./lib/src/method_channel_demo.dart) + +Demonstrates how to implement a `MethodChannel` to increment and decrement a +counter. + +### [Event Channel demo](./lib/src/event_channel_demo.dart) + +Demonstrates how to implement an `EventChannel` to listen to value changes from +the Accelerometer sensor from native side. + +### [Platform Image demo](./lib/src/platform_image_demo.dart) + +Demonstrates how to implement a `BasicMessageChannel` using +`StandardMessageCodec` to load an image from native asset. + +### [Basic Message Channel demo](./lib/src/pet_list_screen.dart) + +Demonstrates how to implement `BasicMessageChannel` using `JSONMessageCodec`, +`BinaryCodec` and `StringCodec` to send and receive data about pets. + +## Questions/issues + +If you have a general question about Platform Channels in Flutter, the +best places to go are: + +* [The FlutterDev Google Group](https://groups.google.com/forum/#!forum/flutter-dev) +* [The Flutter Gitter channel](https://gitter.im/flutter/flutter) +* [StackOverflow](https://stackoverflow.com/questions/tagged/flutter) + +If you run into an issue with the sample itself, please file an issue [here](https://github.com/flutter/samples/issues). diff --git a/platform_channels/analysis_options.yaml b/platform_channels/analysis_options.yaml new file mode 100644 index 0000000..5e2133e --- /dev/null +++ b/platform_channels/analysis_options.yaml @@ -0,0 +1 @@ +include: ../analysis_options.yaml diff --git a/platform_channels/android/.gitignore b/platform_channels/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/platform_channels/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/platform_channels/android/app/build.gradle b/platform_channels/android/app/build.gradle new file mode 100644 index 0000000..bebc96d --- /dev/null +++ b/platform_channels/android/app/build.gradle @@ -0,0 +1,69 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +android { + namespace "dev.flutter.platform_channels" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "dev.flutter.platform_channels" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation 'com.google.code.gson:gson:2.8.6' +} diff --git a/platform_channels/android/app/src/debug/AndroidManifest.xml b/platform_channels/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/platform_channels/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/platform_channels/android/app/src/main/AndroidManifest.xml b/platform_channels/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..97c7040 --- /dev/null +++ b/platform_channels/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/platform_channels/android/app/src/main/assets/eat_new_orleans.jpg b/platform_channels/android/app/src/main/assets/eat_new_orleans.jpg new file mode 100644 index 0000000..517759e Binary files /dev/null and b/platform_channels/android/app/src/main/assets/eat_new_orleans.jpg differ diff --git a/platform_channels/android/app/src/main/kotlin/dev/flutter/platform_channels/AccelerometerStreamHandler.kt b/platform_channels/android/app/src/main/kotlin/dev/flutter/platform_channels/AccelerometerStreamHandler.kt new file mode 100644 index 0000000..845c7ad --- /dev/null +++ b/platform_channels/android/app/src/main/kotlin/dev/flutter/platform_channels/AccelerometerStreamHandler.kt @@ -0,0 +1,39 @@ +// Copyright 2020 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.platform_channels + +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager +import io.flutter.plugin.common.EventChannel + +class AccelerometerStreamHandler(sManager: SensorManager, s: Sensor) : EventChannel.StreamHandler, SensorEventListener { + private val sensorManager: SensorManager = sManager + private val accelerometerSensor: Sensor = s + private lateinit var eventSink: EventChannel.EventSink + + override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { + if (events != null) { + eventSink = events + sensorManager.registerListener(this, accelerometerSensor, SensorManager.SENSOR_DELAY_UI) + } + } + + override fun onCancel(arguments: Any?) { + sensorManager.unregisterListener(this) + } + + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {} + + override fun onSensorChanged(sensorEvent: SensorEvent?) { + if (sensorEvent != null) { + val axisValues = listOf(sensorEvent.values[0], sensorEvent.values[1], sensorEvent.values[2]) + eventSink.success(axisValues) + } else { + eventSink.error("DATA_UNAVAILABLE", "Cannot get accelerometer data", null) + } + } +} diff --git a/platform_channels/android/app/src/main/kotlin/dev/flutter/platform_channels/MainActivity.kt b/platform_channels/android/app/src/main/kotlin/dev/flutter/platform_channels/MainActivity.kt new file mode 100644 index 0000000..50ba805 --- /dev/null +++ b/platform_channels/android/app/src/main/kotlin/dev/flutter/platform_channels/MainActivity.kt @@ -0,0 +1,88 @@ +// Copyright 2020 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.platform_channels + +import android.content.Context +import android.hardware.Sensor +import android.hardware.SensorManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.* +import java.io.InputStream +import java.nio.ByteBuffer + +class MainActivity : FlutterActivity() { + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + // Creates a MethodChannel as soon as the FlutterEngine is attached to + // the Activity, and registers a MethodCallHandler. The Method.setMethodCallHandler + // is responsible to register a MethodCallHandler to handle the incoming calls. + + // The call parameter of MethodCallHandler has information about the incoming call, + // like method name, and arguments. The result parameter of MethodCallHandler is + // responsible to send the results of the call. + MethodChannel(flutterEngine.dartExecutor, "methodChannelDemo") + .setMethodCallHandler { call, result -> + val count: Int? = call.argument("count") + + if (count == null) { + result.error("INVALID ARGUMENT", "Value of count cannot be null", null) + } else { + when (call.method) { + "increment" -> result.success(count + 1) + "decrement" -> result.success(count - 1) + else -> result.notImplemented() + } + } + } + + val sensorManger: SensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager + val accelerometerSensor: Sensor = sensorManger.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) + EventChannel(flutterEngine.dartExecutor, "eventChannelDemo") + .setStreamHandler(AccelerometerStreamHandler(sensorManger, accelerometerSensor)) + + // Registers a MessageHandler for BasicMessageChannel to receive a message from Dart and send + // image data in reply. + BasicMessageChannel(flutterEngine.dartExecutor, "platformImageDemo", StandardMessageCodec()) + .setMessageHandler { message, reply -> + if (message == "getImage") { + val inputStream: InputStream = assets.open("eat_new_orleans.jpg") + reply.reply(inputStream.readBytes()) + } + } + + val petList = mutableListOf>() + val gson = Gson() + + // A BasicMessageChannel for sending petList to Dart. + val stringCodecChannel = BasicMessageChannel(flutterEngine.dartExecutor, "stringCodecDemo", StringCodec.INSTANCE) + + // Registers a MessageHandler for BasicMessageChannel to receive pet details to be + // added in petList and send the it back to Dart using stringCodecChannel. + BasicMessageChannel(flutterEngine.dartExecutor, "jsonMessageCodecDemo", JSONMessageCodec.INSTANCE) + .setMessageHandler { message, reply -> + petList.add(0, gson.fromJson(message.toString(), object : TypeToken>() {}.type)) + stringCodecChannel.send(gson.toJson(mapOf("petList" to petList))) + } + + // Registers a MessageHandler for BasicMessageChannel to receive the index of pet + // details to be removed from the petList and send the petList back to Dart using + // stringCodecChannel. If the index is not in the range of petList, we send null + // back to Dart. + BasicMessageChannel(flutterEngine.dartExecutor, "binaryCodecDemo", BinaryCodec.INSTANCE) + .setMessageHandler { message, reply -> + val index = String(message!!.array()).toInt() + if (index >= 0 && index < petList.size) { + petList.removeAt(index) + val replyMessage = "Removed Successfully" + reply.reply(ByteBuffer.allocateDirect(replyMessage.toByteArray().size).put(replyMessage.toByteArray())) + stringCodecChannel.send(gson.toJson(mapOf("petList" to petList))) + } else { + reply.reply(null) + } + } + } +} diff --git a/platform_channels/android/app/src/main/res/drawable-v21/launch_background.xml b/platform_channels/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/platform_channels/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/platform_channels/android/app/src/main/res/drawable/launch_background.xml b/platform_channels/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/platform_channels/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/platform_channels/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/platform_channels/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/platform_channels/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/platform_channels/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/platform_channels/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/platform_channels/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/platform_channels/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/platform_channels/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/platform_channels/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/platform_channels/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/platform_channels/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/platform_channels/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/platform_channels/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/platform_channels/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/platform_channels/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/platform_channels/android/app/src/main/res/values-night/styles.xml b/platform_channels/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/platform_channels/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/platform_channels/android/app/src/main/res/values/styles.xml b/platform_channels/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/platform_channels/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/platform_channels/android/app/src/profile/AndroidManifest.xml b/platform_channels/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/platform_channels/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/platform_channels/android/build.gradle b/platform_channels/android/build.gradle new file mode 100644 index 0000000..e83fb5d --- /dev/null +++ b/platform_channels/android/build.gradle @@ -0,0 +1,30 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/platform_channels/android/gradle.properties b/platform_channels/android/gradle.properties new file mode 100644 index 0000000..598d13f --- /dev/null +++ b/platform_channels/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G +android.useAndroidX=true +android.enableJetifier=true diff --git a/platform_channels/android/gradle/wrapper/gradle-wrapper.properties b/platform_channels/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/platform_channels/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/platform_channels/android/settings.gradle b/platform_channels/android/settings.gradle new file mode 100644 index 0000000..7cd7128 --- /dev/null +++ b/platform_channels/android/settings.gradle @@ -0,0 +1,29 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + + plugins { + id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false +} + +include ":app" diff --git a/platform_channels/assets/eat_new_orleans.jpg b/platform_channels/assets/eat_new_orleans.jpg new file mode 100644 index 0000000..517759e Binary files /dev/null and b/platform_channels/assets/eat_new_orleans.jpg differ diff --git a/platform_channels/ios/.gitignore b/platform_channels/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/platform_channels/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/platform_channels/ios/Flutter/AppFrameworkInfo.plist b/platform_channels/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/platform_channels/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/platform_channels/ios/Flutter/Debug.xcconfig b/platform_channels/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/platform_channels/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/platform_channels/ios/Flutter/Release.xcconfig b/platform_channels/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/platform_channels/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/platform_channels/ios/Runner.xcodeproj/project.pbxproj b/platform_channels/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..3de48db --- /dev/null +++ b/platform_channels/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,614 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + F1246E9A9D19DC437CB1F19C /* AccelerometerStreamHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 875C3ADF5E4877B6D6ECA77F /* AccelerometerStreamHandler.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 875C3ADF5E4877B6D6ECA77F /* AccelerometerStreamHandler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AccelerometerStreamHandler.swift; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + 875C3ADF5E4877B6D6ECA77F /* AccelerometerStreamHandler.swift */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + F1246E9A9D19DC437CB1F19C /* AccelerometerStreamHandler.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.platformChannels; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.platformChannels.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.platformChannels.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.platformChannels.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.platformChannels; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.platformChannels; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/platform_channels/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/platform_channels/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/platform_channels/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/platform_channels/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/platform_channels/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/platform_channels/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/platform_channels/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/platform_channels/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/platform_channels/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/platform_channels/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/platform_channels/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..87131a0 --- /dev/null +++ b/platform_channels/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform_channels/ios/Runner.xcworkspace/contents.xcworkspacedata b/platform_channels/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/platform_channels/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/platform_channels/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/platform_channels/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/platform_channels/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/platform_channels/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/platform_channels/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/platform_channels/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/platform_channels/ios/Runner/AccelerometerStreamHandler.swift b/platform_channels/ios/Runner/AccelerometerStreamHandler.swift new file mode 100644 index 0000000..c5cf351 --- /dev/null +++ b/platform_channels/ios/Runner/AccelerometerStreamHandler.swift @@ -0,0 +1,38 @@ +// Copyright 2020 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. + +import CoreMotion + +class AccelerometerStreamHandler: NSObject, FlutterStreamHandler { + + var motionManager: CMMotionManager; + + override init() { + motionManager = CMMotionManager() + } + + func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { + + if !motionManager.isAccelerometerAvailable { + events(FlutterError(code: "SENSOR_UNAVAILABLE", message: "Accelerometer is not available", details: nil)) + } + + motionManager.accelerometerUpdateInterval = 0.1 + + motionManager.startAccelerometerUpdates(to: OperationQueue.main) {(data, error) in + guard let accelerationData = data?.acceleration else { + events(FlutterError(code: "DATA_UNAVAILABLE", message: "Cannot get accelerometer data", details: nil )) + return + } + + events([accelerationData.x, accelerationData.y, accelerationData.z]) + } + + return nil + } + + func onCancel(withArguments arguments: Any?) -> FlutterError? { + return nil + } +} diff --git a/platform_channels/ios/Runner/AppDelegate.swift b/platform_channels/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..131fd18 --- /dev/null +++ b/platform_channels/ios/Runner/AppDelegate.swift @@ -0,0 +1,89 @@ +// Copyright 2020 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. + +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + let flutterViewController = window.rootViewController as! FlutterViewController + FlutterMethodChannel(name: "methodChannelDemo", binaryMessenger: flutterViewController.binaryMessenger).setMethodCallHandler({ + (call: FlutterMethodCall, result: FlutterResult) -> Void in + + guard let count = (call.arguments as? NSDictionary)?["count"] as? Int else { + result(FlutterError(code: "INVALID_ARGUMENT", message: "Value of count cannot be null", details: nil)) + return + } + + switch call.method { + case "increment": + result(count + 1) + case "decrement": + result(count - 1) + default: + result(FlutterMethodNotImplemented) + } + }) + + FlutterBasicMessageChannel(name: "platformImageDemo", binaryMessenger: flutterViewController.binaryMessenger, codec: FlutterStandardMessageCodec.sharedInstance()).setMessageHandler{ + (message: Any?, reply: FlutterReply) -> Void in + + if(message as! String == "getImage") { + guard let image = UIImage(named: "eat_new_orleans.jpg") else { + reply(nil) + return + } + + reply(FlutterStandardTypedData(bytes: image.jpegData(compressionQuality: 1)!)) + } + } + + FlutterEventChannel(name: "eventChannelDemo", binaryMessenger: flutterViewController.binaryMessenger).setStreamHandler(AccelerometerStreamHandler()) + + var petList : [[String:String]] = [] + + // A FlutterBasicMessageChannel for sending petList to Dart. + let stringCodecChannel = FlutterBasicMessageChannel(name: "stringCodecDemo", binaryMessenger: flutterViewController.binaryMessenger, codec: FlutterStringCodec.sharedInstance()) + + // Registers a MessageHandler for FlutterBasicMessageChannel to receive pet details. + FlutterBasicMessageChannel(name: "jsonMessageCodecDemo", binaryMessenger: flutterViewController.binaryMessenger, codec: FlutterJSONMessageCodec.sharedInstance()) + .setMessageHandler{(message: Any?, reply: FlutterReply) -> Void in + petList.insert(message! as! [String: String], at: 0) + stringCodecChannel.sendMessage(self.convertPetListToJson(petList: petList)) + } + + // Registers a MessageHandler for FlutterBasicMessageHandler to receive indices of detail records to remove from the petList. + FlutterBasicMessageChannel(name: "binaryCodecDemo", binaryMessenger: flutterViewController.binaryMessenger, codec: FlutterBinaryCodec.sharedInstance()).setMessageHandler{ + (message: Any?, reply: FlutterReply) -> Void in + + guard let index = Int.init(String.init(data: message! as! Data, encoding: String.Encoding.utf8)!) else { + reply(nil) + return + } + + if (index >= 0 && index < petList.count) { + petList.remove(at: index) + reply("Removed Successfully".data(using: .utf8)!) + stringCodecChannel.sendMessage(self.convertPetListToJson(petList: petList)) + } else { + reply(nil) + } + } + + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + // Function to convert petList to json string. + func convertPetListToJson(petList: [[String: String]]) -> String? { + guard let data = try? JSONSerialization.data(withJSONObject: ["petList": petList], options: .prettyPrinted) else { + return nil + } + return String(data: data, encoding: String.Encoding.utf8) + } +} diff --git a/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/platform_channels/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/platform_channels/ios/Runner/Assets.xcassets/Contents.json b/platform_channels/ios/Runner/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/platform_channels/ios/Runner/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/platform_channels/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/platform_channels/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/platform_channels/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/platform_channels/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/platform_channels/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/platform_channels/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/platform_channels/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/platform_channels/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/platform_channels/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/platform_channels/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/platform_channels/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/platform_channels/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/platform_channels/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/platform_channels/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/platform_channels/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/platform_channels/ios/Runner/Assets.xcassets/eat_new_orleans.imageset/Contents.json b/platform_channels/ios/Runner/Assets.xcassets/eat_new_orleans.imageset/Contents.json new file mode 100644 index 0000000..a205504 --- /dev/null +++ b/platform_channels/ios/Runner/Assets.xcassets/eat_new_orleans.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "eat_new_orleans.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/platform_channels/ios/Runner/Assets.xcassets/eat_new_orleans.imageset/eat_new_orleans.jpg b/platform_channels/ios/Runner/Assets.xcassets/eat_new_orleans.imageset/eat_new_orleans.jpg new file mode 100644 index 0000000..517759e Binary files /dev/null and b/platform_channels/ios/Runner/Assets.xcassets/eat_new_orleans.imageset/eat_new_orleans.jpg differ diff --git a/platform_channels/ios/Runner/Base.lproj/LaunchScreen.storyboard b/platform_channels/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/platform_channels/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform_channels/ios/Runner/Base.lproj/Main.storyboard b/platform_channels/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/platform_channels/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform_channels/ios/Runner/Info.plist b/platform_channels/ios/Runner/Info.plist new file mode 100644 index 0000000..2557472 --- /dev/null +++ b/platform_channels/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Platform Channels + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + platform_channels + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/platform_channels/ios/Runner/Runner-Bridging-Header.h b/platform_channels/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/platform_channels/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/platform_channels/ios/RunnerTests/RunnerTests.swift b/platform_channels/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/platform_channels/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/platform_channels/lib/main.dart b/platform_channels/lib/main.dart new file mode 100644 index 0000000..53fb0be --- /dev/null +++ b/platform_channels/lib/main.dart @@ -0,0 +1,113 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:platform_channels/src/add_pet_details.dart'; +import 'package:platform_channels/src/event_channel_demo.dart'; +import 'package:platform_channels/src/method_channel_demo.dart'; +import 'package:platform_channels/src/pet_list_screen.dart'; +import 'package:platform_channels/src/platform_image_demo.dart'; + +void main() { + runApp(const PlatformChannelSample()); +} + +class PlatformChannelSample extends StatelessWidget { + const PlatformChannelSample({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp.router( + title: 'Platform Channel Sample', + theme: ThemeData( + snackBarTheme: SnackBarThemeData( + backgroundColor: Colors.blue[500], + ), + ), + routerConfig: router(), + ); + } +} + +GoRouter router([String? initialLocation]) { + return GoRouter( + initialLocation: initialLocation ?? '/', + routes: [ + GoRoute( + path: '/', + builder: (context, state) => const HomePage(), + routes: [ + GoRoute( + path: 'methodChannelDemo', + builder: (context, state) => const MethodChannelDemo(), + ), + GoRoute( + path: 'eventChannelDemo', + builder: (context, state) => const EventChannelDemo(), + ), + GoRoute( + path: 'platformImageDemo', + builder: (context, state) => const PlatformImageDemo(), + ), + GoRoute( + path: 'petListScreen', + builder: (context, state) => const PetListScreen(), + routes: [ + GoRoute( + path: 'addPetDetails', + builder: (context, state) => const AddPetDetails(), + ), + ], + ), + ], + ), + ], + ); +} + +class DemoInfo { + final String demoTitle; + final String demoRoute; + + DemoInfo(this.demoTitle, this.demoRoute); +} + +List demoList = [ + DemoInfo('MethodChannel Demo', '/methodChannelDemo'), + DemoInfo('EventChannel Demo', '/eventChannelDemo'), + DemoInfo('Platform Image Demo', '/platformImageDemo'), + DemoInfo('BasicMessageChannel Demo', '/petListScreen'), +]; + +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Platform Channel Sample')), + body: ListView( + children: demoList.map((demoInfo) => DemoTile(demoInfo)).toList(), + ), + ); + } +} + +/// This widget is responsible for displaying the [ListTile] on [HomePage]. +class DemoTile extends StatelessWidget { + final DemoInfo demoInfo; + + const DemoTile(this.demoInfo, {super.key}); + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text(demoInfo.demoTitle), + onTap: () { + context.go(demoInfo.demoRoute); + }, + ); + } +} diff --git a/platform_channels/lib/src/accelerometer_event_channel.dart b/platform_channels/lib/src/accelerometer_event_channel.dart new file mode 100644 index 0000000..a7d7b60 --- /dev/null +++ b/platform_channels/lib/src/accelerometer_event_channel.dart @@ -0,0 +1,37 @@ +// Copyright 2020 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. + +import 'package:flutter/services.dart'; + +/// This class includes the implementation for [EventChannel] to listen to value +/// changes from the Accelerometer sensor from native side. It has a [readings] +/// getter to provide a stream of [AccelerometerReadings]. +class Accelerometer { + static const _eventChannel = EventChannel('eventChannelDemo'); + + /// Method responsible for providing a stream of [AccelerometerReadings] to listen + /// to value changes from the Accelerometer sensor. + static Stream get readings { + return _eventChannel.receiveBroadcastStream().map( + (dynamic event) => AccelerometerReadings( + event[0] as double, + event[1] as double, + event[2] as double, + ), + ); + } +} + +class AccelerometerReadings { + /// Acceleration force along the x-axis. + final double x; + + /// Acceleration force along the y-axis. + final double y; + + /// Acceleration force along the z-axis. + final double z; + + AccelerometerReadings(this.x, this.y, this.z); +} diff --git a/platform_channels/lib/src/add_pet_details.dart b/platform_channels/lib/src/add_pet_details.dart new file mode 100644 index 0000000..6da6ced --- /dev/null +++ b/platform_channels/lib/src/add_pet_details.dart @@ -0,0 +1,81 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:platform_channels/src/pet_list_message_channel.dart'; + +/// Demonstrates how to use [BasicMessageChannel] to send a message to platform. +/// +/// The widget uses [TextField] and [RadioListTile] to take the [PetDetails.breed] and +/// [PetDetails.petType] from the user respectively. +class AddPetDetails extends StatefulWidget { + const AddPetDetails({super.key}); + + @override + State createState() => _AddPetDetailsState(); +} + +class _AddPetDetailsState extends State { + final breedTextController = TextEditingController(); + String petType = 'Dog'; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Add Pet Details'), + actions: [ + IconButton( + icon: const Icon(Icons.add), + onPressed: () { + PetListMessageChannel.addPetDetails( + PetDetails( + petType: petType, + breed: breedTextController.text, + ), + ); + + context.pop(); + }, + ), + ], + ), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: RadioGroup( + groupValue: petType, + onChanged: (value) { + setState(() { + petType = value!; + }); + }, + child: Column( + children: [ + const SizedBox(height: 8), + TextField( + controller: breedTextController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + filled: true, + hintText: 'Breed of pet', + labelText: 'Breed', + ), + ), + const SizedBox(height: 8), + RadioListTile( + title: const Text('Dog'), + value: 'Dog', + ), + RadioListTile( + title: const Text('Cat'), + value: 'Cat', + ), + ], + ), + ), + ), + ); + } +} diff --git a/platform_channels/lib/src/counter_method_channel.dart b/platform_channels/lib/src/counter_method_channel.dart new file mode 100644 index 0000000..38b948a --- /dev/null +++ b/platform_channels/lib/src/counter_method_channel.dart @@ -0,0 +1,33 @@ +// Copyright 2020 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. + +import 'package:flutter/services.dart'; + +/// This class includes implementation of two platform methods [increment], +/// and [decrement] which are used to increment and decrement value +/// of count respectively. +class Counter { + /// Creates a [MethodChannel] with the specified name to invoke platform method. + /// In order to communicate across platforms, the name of MethodChannel + /// should be same on native and dart side. + static MethodChannel methodChannel = const MethodChannel( + 'methodChannelDemo', + ); + + /// This method is responsible to increment and return the value of count. + static Future increment({required int counterValue}) async { + final result = await methodChannel.invokeMethod('increment', { + 'count': counterValue, + }); + return result!; + } + + /// This method is responsible to decrement and return the value of count. + static Future decrement({required int counterValue}) async { + final result = await methodChannel.invokeMethod('decrement', { + 'count': counterValue, + }); + return result!; + } +} diff --git a/platform_channels/lib/src/event_channel_demo.dart b/platform_channels/lib/src/event_channel_demo.dart new file mode 100644 index 0000000..6a88a60 --- /dev/null +++ b/platform_channels/lib/src/event_channel_demo.dart @@ -0,0 +1,56 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:platform_channels/src/accelerometer_event_channel.dart'; + +/// Demonstrates how to use [EventChannel] to listen continuous values +/// of Accelerometer Sensor from platform. +/// +/// The widget uses a [StreamBuilder] to rebuild it's descendant whenever it +/// listens a new value from the [Accelerometer.readings] stream. It has three +/// [Text] widgets to display the value of [AccelerometerReadings.x], +/// [AccelerometerReadings.y], and [AccelerometerReadings.z] respectively. +class EventChannelDemo extends StatelessWidget { + const EventChannelDemo({super.key}); + + @override + Widget build(BuildContext context) { + final textStyle = Theme.of(context).textTheme.headlineSmall; + return Scaffold( + appBar: AppBar(title: const Text('EventChannel Demo')), + body: Center( + child: StreamBuilder( + stream: Accelerometer.readings, + builder: (context, snapshot) { + return switch (snapshot) { + AsyncSnapshot(hasError: true) => Text( + (snapshot.error as PlatformException).message!, + ), + AsyncSnapshot(hasData: true) => Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'x axis: ${snapshot.data!.x.toStringAsFixed(3)}', + style: textStyle, + ), + Text( + 'y axis: ${snapshot.data!.y.toStringAsFixed(3)}', + style: textStyle, + ), + Text( + 'z axis: ${snapshot.data!.z.toStringAsFixed(3)}', + style: textStyle, + ), + ], + ), + _ => Text('No Data Available', style: textStyle), + }; + }, + ), + ), + ); + } +} diff --git a/platform_channels/lib/src/image_basic_message_channel.dart b/platform_channels/lib/src/image_basic_message_channel.dart new file mode 100644 index 0000000..58c5724 --- /dev/null +++ b/platform_channels/lib/src/image_basic_message_channel.dart @@ -0,0 +1,28 @@ +// Copyright 2020 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. + +import 'package:flutter/services.dart'; + +/// This class manages a [BasicMessageChannel] that can return an image loaded +/// from a native asset. The [BasicMessageChannel] uses [StandardMessageCodec] +/// since it supports [Uint8List], which is used to transport the image data. +class PlatformImageFetcher { + static const _basicMessageChannel = BasicMessageChannel( + 'platformImageDemo', + StandardMessageCodec(), + ); + + /// Method responsible for providing the platform image. + static Future getImage() async { + final reply = await _basicMessageChannel.send('getImage') as Uint8List?; + if (reply == null) { + throw PlatformException( + code: 'Error', + message: 'Failed to load Platform Image', + details: null, + ); + } + return reply; + } +} diff --git a/platform_channels/lib/src/method_channel_demo.dart b/platform_channels/lib/src/method_channel_demo.dart new file mode 100644 index 0000000..e3ecf02 --- /dev/null +++ b/platform_channels/lib/src/method_channel_demo.dart @@ -0,0 +1,90 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:platform_channels/src/counter_method_channel.dart'; + +/// The widget demonstrates how to use [MethodChannel] to invoke platform methods. +/// It has two [FilledButton]s to increment and decrement the value of +/// [count], and a [Text] widget to display its value. +class MethodChannelDemo extends StatefulWidget { + const MethodChannelDemo({super.key}); + + @override + State createState() => _MethodChannelDemoState(); +} + +class _MethodChannelDemoState extends State { + int count = 0; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('MethodChannel Demo')), + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Value of count is $count', + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + // Whenever users press the FilledButton, it invokes + // Counter.increment method to increment the value of count. + FilledButton.icon( + onPressed: () async { + try { + final value = await Counter.increment( + counterValue: count, + ); + setState(() => count = value); + } catch (error) { + if (!context.mounted) return; + showErrorMessage( + context, + (error as PlatformException).message!, + ); + } + }, + icon: const Icon(Icons.add), + label: const Text('Increment'), + ), + + // Whenever users press the FilledButton, it invokes + // Counter.decrement method to decrement the value of count. + FilledButton.icon( + onPressed: () async { + try { + final value = await Counter.decrement( + counterValue: count, + ); + setState(() => count = value); + } catch (error) { + if (!context.mounted) return; + showErrorMessage( + context, + (error as PlatformException).message!, + ); + } + }, + icon: const Icon(Icons.remove), + label: const Text('Decrement'), + ), + ], + ), + ], + ), + ); + } + + void showErrorMessage(BuildContext context, String errorMessage) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(errorMessage))); + } +} diff --git a/platform_channels/lib/src/pet_list_message_channel.dart b/platform_channels/lib/src/pet_list_message_channel.dart new file mode 100644 index 0000000..d9ed627 --- /dev/null +++ b/platform_channels/lib/src/pet_list_message_channel.dart @@ -0,0 +1,86 @@ +// Copyright 2020 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. + +import 'dart:convert'; + +import 'package:flutter/services.dart'; + +/// This class includes two methods [addPetDetails] and [removePet] which are used +/// to add a new pet and remove a pet from the the list respectively. +class PetListMessageChannel { + static const _jsonMessageCodecChannel = BasicMessageChannel( + 'jsonMessageCodecDemo', + JSONMessageCodec(), + ); + + static const _binaryCodecChannel = BasicMessageChannel( + 'binaryCodecDemo', + BinaryCodec(), + ); + + /// Method to add a new pet to the list. + /// + /// Demonstrates how to use [BasicMessageChannel] and [JSONMessageCodec] to + /// send more structured data to platform like a [Map] in this case. + static void addPetDetails(PetDetails petDetails) { + _jsonMessageCodecChannel.send(petDetails.toJson()); + } + + /// Method to remove a pet from the list. + /// + /// Demonstrates how to use [BasicMessageChannel] and [BinaryCodec] to + /// send [ByteData] to platform. If the reply received is null, then + /// we will throw a [PlatformException]. + static Future removePet(int index) async { + final uInt8List = utf8.encoder.convert(index.toString()); + final reply = await _binaryCodecChannel.send( + uInt8List.buffer.asByteData(), + ); + if (reply == null) { + throw PlatformException( + code: 'INVALID INDEX', + message: 'Failed to delete pet details', + details: null, + ); + } + } +} + +/// A model class that provides [petList] which is received from platform. +class PetListModel { + PetListModel({required this.petList}); + + final List petList; + + /// Method that maps the incoming string of json object to List of [PetDetails]. + factory PetListModel.fromJson(String jsonString) { + final jsonData = json.decode(jsonString) as Map; + return PetListModel( + petList: List.from( + (jsonData['petList'] as List).map( + (dynamic petDetailsMap) => + PetDetails.fromMap(petDetailsMap as Map), + ), + ), + ); + } +} + +/// A simple model that provides pet details like [petType] and [breed] of pet. +class PetDetails { + PetDetails({required this.petType, required this.breed}); + + final String petType; + final String breed; + + factory PetDetails.fromMap(Map map) => PetDetails( + petType: map['petType'] as String, + breed: map['breed'] as String, + ); + + Map toJson() => { + 'petType': petType, + 'breed': breed, + }; +} diff --git a/platform_channels/lib/src/pet_list_screen.dart b/platform_channels/lib/src/pet_list_screen.dart new file mode 100644 index 0000000..caf821b --- /dev/null +++ b/platform_channels/lib/src/pet_list_screen.dart @@ -0,0 +1,103 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:go_router/go_router.dart'; +import 'package:platform_channels/src/pet_list_message_channel.dart'; + +/// Demonstrates how to use [BasicMessageChannel] to send & receive the platform +/// Message. +class PetListScreen extends StatefulWidget { + const PetListScreen({super.key}); + + @override + State createState() => _PetListScreenState(); +} + +class _PetListScreenState extends State { + PetListModel petListModel = PetListModel(petList: []); + final scaffoldKey = GlobalKey(); + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + // Receives a string of json object from the platform and converts it + // to PetModel. + final scaffoldMessenger = ScaffoldMessenger.of(context); + const BasicMessageChannel( + 'stringCodecDemo', + StringCodec(), + ).setMessageHandler((message) async { + if (message == null) { + scaffoldMessenger.showSnackBar( + const SnackBar( + content: Text('An error occurred while adding pet details.'), + ), + ); + } else { + setState(() { + petListModel = PetListModel.fromJson(message); + }); + } + return null; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + key: scaffoldKey, + appBar: AppBar(title: const Text('Pet List')), + floatingActionButton: FloatingActionButton( + child: const Icon(Icons.add), + onPressed: () { + context.go('/petListScreen/addPetDetails'); + }, + ), + body: petListModel.petList.isEmpty + ? const Center(child: Text('Enter Pet Details')) + : BuildPetList(petListModel.petList), + ); + } +} + +/// Shows list of [PetDetails]. +class BuildPetList extends StatelessWidget { + final List petList; + + const BuildPetList(this.petList, {super.key}); + + @override + Widget build(BuildContext context) { + return ListView.builder( + padding: const EdgeInsets.all(8), + itemCount: petList.length, + itemBuilder: (context, index) { + return ListTile( + title: Text('Pet breed: ${petList[index].breed}'), + subtitle: Text('Pet type: ${petList[index].petType}'), + trailing: IconButton( + icon: const Icon(Icons.delete), + onPressed: () async { + final scaffoldMessenger = ScaffoldMessenger.of(context); + try { + await PetListMessageChannel.removePet(index); + scaffoldMessenger.showSnackBar( + const SnackBar(content: Text('Removed successfully!')), + ); + } catch (error) { + scaffoldMessenger.showSnackBar( + SnackBar( + content: Text((error as PlatformException).message!), + ), + ); + } + }, + ), + ); + }, + ); + } +} diff --git a/platform_channels/lib/src/platform_image_demo.dart b/platform_channels/lib/src/platform_image_demo.dart new file mode 100644 index 0000000..6e981cb --- /dev/null +++ b/platform_channels/lib/src/platform_image_demo.dart @@ -0,0 +1,74 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:platform_channels/src/image_basic_message_channel.dart'; + +/// Demonstrates how to use [BasicMessageChannel] to get an image from platform. +/// +/// The widget uses [Image.memory] to display the image obtained from the +/// platform. +class PlatformImageDemo extends StatefulWidget { + const PlatformImageDemo({super.key}); + + @override + State createState() => _PlatformImageDemoState(); +} + +class _PlatformImageDemoState extends State { + Future? imageData; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Platform Image Demo')), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: FractionallySizedBox( + widthFactor: 1, + heightFactor: 0.6, + child: FutureBuilder( + future: imageData, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.none) { + return const Placeholder(); + } else if (snapshot.hasError) { + return Center( + child: Text( + (snapshot.error as PlatformException).message!, + ), + ); + } else if (snapshot.connectionState == + ConnectionState.done) { + return Image.memory( + snapshot.data!, + fit: BoxFit.fill, + ); + } + return const CircularProgressIndicator(); + }, + ), + ), + ), + const SizedBox(height: 16), + FilledButton( + onPressed: imageData != null + ? null + : () { + setState(() { + imageData = PlatformImageFetcher.getImage(); + }); + }, + child: const Text('Get Image'), + ), + ], + ), + ), + ); + } +} diff --git a/platform_channels/pubspec.yaml b/platform_channels/pubspec.yaml new file mode 100644 index 0000000..7ebe27e --- /dev/null +++ b/platform_channels/pubspec.yaml @@ -0,0 +1,23 @@ +name: platform_channels +description: A new Flutter project. +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^1.0.3 + go_router: ^16.0.0 + +dev_dependencies: + analysis_defaults: + path: ../analysis_defaults + flutter_test: + sdk: flutter +flutter: + uses-material-design: true + assets: + - assets/ diff --git a/platform_channels/test/home_page_test.dart b/platform_channels/test/home_page_test.dart new file mode 100644 index 0000000..522037d --- /dev/null +++ b/platform_channels/test/home_page_test.dart @@ -0,0 +1,17 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:platform_channels/main.dart'; + +void main() { + group('HomePage tests', () { + testWidgets('HomePage has multiple Text widgets', (tester) async { + await tester.pumpWidget(const MaterialApp(home: HomePage())); + + expect(find.byType(Text), findsWidgets); + }); + }); +} diff --git a/platform_channels/test/src/add_pet_details_test.dart b/platform_channels/test/src/add_pet_details_test.dart new file mode 100644 index 0000000..dc0ba03 --- /dev/null +++ b/platform_channels/test/src/add_pet_details_test.dart @@ -0,0 +1,47 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:platform_channels/main.dart' as app; + +void main() { + group('AddPetDetails tests', () { + var petList = []; + + testWidgets('Enter pet details', (tester) async { + tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler( + const BasicMessageChannel( + 'jsonMessageCodecDemo', + JSONMessageCodec(), + ), + (dynamic message) async { + petList.add(message as Map); + }, + ); + var router = app.router('/petListScreen/addPetDetails'); + await tester.pumpWidget(MaterialApp.router(routerConfig: router)); + + // Enter the breed of cat. + await tester.enterText(find.byType(TextField), 'Persian'); + // Select cat from the pet type. + await tester.tap(find.text('Cat')); + + // Initially the list will be empty. + expect(petList, isEmpty); + await tester.tap(find.byIcon(Icons.add)); + + expect(petList, isNotEmpty); + expect(petList.last['breed'], 'Persian'); + + // Navigate back to /petListScreen + await tester.pumpAndSettle(); + expect( + router.routeInformationProvider.value.uri.path, + '/petListScreen', + ); + }); + }); +} diff --git a/platform_channels/test/src/event_channel_demo_test.dart b/platform_channels/test/src/event_channel_demo_test.dart new file mode 100644 index 0000000..1b854f9 --- /dev/null +++ b/platform_channels/test/src/event_channel_demo_test.dart @@ -0,0 +1,72 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:platform_channels/src/event_channel_demo.dart'; + +void main() { + group('EventChannel Demo tests', () { + final sensorValues = [1.3556, 2.3, -0.12]; + setUpAll(() { + // By default EventChannel uses StandardMethodCodec to communicate with + // platform. + const standardMethod = StandardMethodCodec(); + + // This function handles the incoming messages from the platform. It + // calls the BinaryMessenger.setMessageHandler registered for the EventChannel + // and add the incoming message to the StreamController used by the EventChannel + // after decoding the message with codec used by the EventChannel. + void emitValues(ByteData? event) { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .handlePlatformMessage('eventChannelDemo', event, (reply) {}); + } + + // Register a mock for EventChannel. EventChannel under the hood uses + // MethodChannel to listen and cancel the created stream. + + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMessageHandler('eventChannelDemo', (message) async { + // Decode the message into MethodCallHandler. + final methodCall = standardMethod.decodeMethodCall(message); + + if (methodCall.method == 'listen') { + // Emit new sensor values. + emitValues( + standardMethod.encodeSuccessEnvelope(sensorValues), + ); + emitValues(null); + return standardMethod.encodeSuccessEnvelope(null); + } else if (methodCall.method == 'cancel') { + return standardMethod.encodeSuccessEnvelope(null); + } else { + fail('Expected listen or cancel'); + } + }); + }); + + testWidgets('EventChannel AccelerometerReadings Stream test', ( + tester, + ) async { + await tester.pumpWidget(const MaterialApp(home: EventChannelDemo())); + + await tester.pumpAndSettle(); + + // Check the values of axis. The value is rounded to 3 decimal places. + expect( + find.text('x axis: ${sensorValues[0].toStringAsFixed(3)}'), + findsOneWidget, + ); + expect( + find.text('y axis: ${sensorValues[1].toStringAsFixed(3)}'), + findsOneWidget, + ); + expect( + find.text('z axis: ${sensorValues[2].toStringAsFixed(3)}'), + findsOneWidget, + ); + }); + }); +} diff --git a/platform_channels/test/src/method_channel_demo_test.dart b/platform_channels/test/src/method_channel_demo_test.dart new file mode 100644 index 0000000..5fea9a5 --- /dev/null +++ b/platform_channels/test/src/method_channel_demo_test.dart @@ -0,0 +1,46 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:platform_channels/src/method_channel_demo.dart'; + +void main() { + group('MethodChannelDemo tests', () { + testWidgets('MethodChannelDemo count test', (tester) async { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( + const MethodChannel('methodChannelDemo'), + (call) async { + var count = call.arguments['count'] as int; + if (call.method == 'increment') { + return ++count; + } else if (call.method == 'decrement') { + return --count; + } + + return MissingPluginException(); + }, + ); + await tester.pumpWidget( + const MaterialApp(home: MethodChannelDemo()), + ); + + // Initially the value of count should be 0. + expect(find.text('Value of count is 0'), findsOneWidget); + + // Tap the ElevatedButton with Icons.add to increment the value of count. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + expect(find.text('Value of count is 1'), findsOneWidget); + + // Tap the ElevatedButton with Icons.remove to decrement the value of count. + await tester.tap(find.byIcon(Icons.remove)); + await tester.pump(); + + expect(find.text('Value of count is 0'), findsOneWidget); + }); + }); +} diff --git a/platform_channels/test/src/pet_list_screen_test.dart b/platform_channels/test/src/pet_list_screen_test.dart new file mode 100644 index 0000000..2eb8a60 --- /dev/null +++ b/platform_channels/test/src/pet_list_screen_test.dart @@ -0,0 +1,96 @@ +// Copyright 2020 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. + +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:platform_channels/src/pet_list_message_channel.dart'; +import 'package:platform_channels/src/pet_list_screen.dart'; + +void main() { + group('PetListScreen tests', () { + const basicMessageChannel = BasicMessageChannel( + 'stringCodecDemo', + StringCodec(), + ); + + var petList = [ + {'petType': 'Dog', 'breed': 'Pug'}, + ]; + + PetListModel? petListModel; + + setUpAll(() { + // Mock for the pet list received from the platform. + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockDecodedMessageHandler(basicMessageChannel, ( + message, + ) async { + petListModel = PetListModel.fromJson(message!); + return null; + }); + + // Mock for the index received from the Dart to delete the pet details, + // and send the updated pet list back to Dart. + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockDecodedMessageHandler( + const BasicMessageChannel( + 'binaryCodecDemo', + BinaryCodec(), + ), + (message) async { + // Convert the ByteData to String. + final index = utf8.decoder.convert( + message!.buffer.asUint8List( + message.offsetInBytes, + message.lengthInBytes, + ), + ); + + // Remove the pet details at the given index. + petList.removeAt(int.parse(index)); + + // Send the updated petList back. + final map = {'petList': petList}; + await basicMessageChannel.send(json.encode(map)); + + return null; + }, + ); + }); + + test('convert json message to PetListModel', () { + TestWidgetsFlutterBinding.ensureInitialized(); + + // Initially petListModel will be null. + expect(petListModel, isNull); + + // Send the pet list using BasicMessageChannel. + final map = {'petList': petList}; + basicMessageChannel.send(json.encode(map)); + + // Get the details of first pet. + final petDetails = petListModel!.petList.first; + expect(petDetails.petType, 'Dog'); + expect(petDetails.breed, 'Pug'); + }); + + testWidgets('BuildPetList test', (tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold(body: BuildPetList(petListModel!.petList)), + ), + ); + + expect(find.text('Pet type: Dog'), findsOneWidget); + expect(find.text('Pet breed: Pug'), findsOneWidget); + + // Delete the pet details. + await tester.tap(find.byIcon(Icons.delete).first); + expect(petListModel!.petList, isEmpty); + }); + }); +} diff --git a/platform_channels/test/src/platform_image_demo_test.dart b/platform_channels/test/src/platform_image_demo_test.dart new file mode 100644 index 0000000..c46c9f2 --- /dev/null +++ b/platform_channels/test/src/platform_image_demo_test.dart @@ -0,0 +1,41 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:platform_channels/src/platform_image_demo.dart'; + +void main() { + group('Platform Image Demo tests', () { + testWidgets('Platform Image test', (tester) async { + tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler( + const BasicMessageChannel( + 'platformImageDemo', + StandardMessageCodec(), + ), + (dynamic message) async { + var byteData = await rootBundle.load( + 'assets/eat_new_orleans.jpg', + ); + return byteData.buffer.asUint8List(); + }, + ); + await tester.pumpWidget( + const MaterialApp(home: PlatformImageDemo()), + ); + + // Initially a PlaceHolder is displayed when imageData is null. + expect(find.byType(Placeholder), findsOneWidget); + expect(find.byType(Image), findsNothing); + + // Tap on ElevatedButton to get Image. + await tester.tap(find.byType(FilledButton)); + await tester.pumpAndSettle(); + + expect(find.byType(Placeholder), findsNothing); + expect(find.byType(Image), findsOneWidget); + }); + }); +} diff --git a/platform_design/.gitignore b/platform_design/.gitignore new file mode 100644 index 0000000..808f1fc --- /dev/null +++ b/platform_design/.gitignore @@ -0,0 +1,71 @@ +# 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 +.packages +.pub-cache/ +.pub/ +/build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# 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.* + +# 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 diff --git a/platform_design/.metadata b/platform_design/.metadata new file mode 100644 index 0000000..07d68a6 --- /dev/null +++ b/platform_design/.metadata @@ -0,0 +1,36 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "db7ef5bf9f59442b0e200a90587e8fa5e0c6336a" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: android + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: ios + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + - platform: web + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/platform_design/README.md b/platform_design/README.md new file mode 100644 index 0000000..441edea --- /dev/null +++ b/platform_design/README.md @@ -0,0 +1,104 @@ +# Platform Design + +Instead of transliterating widgets one by one between Cupertino and Material, +Android and iOS apps often follow different information architecture patterns +that require some design decisions. + +This sample project shows a Flutter app that maximizes application code reuse +while adhering to different design patterns on Android and iOS. On +Android, it uses Material's [lateral navigation](https://material.io/design/navigation/understanding-navigation.html#types-of-navigation) +based on a drawer and on iOS, it adheres to Apple Human Interface Guideline's +[flat navigation](https://developer.apple.com/design/human-interface-guidelines/ios/app-architecture/navigation/) +by using a bottom tab bar. + +Visually, the app presents platform-agnostic content surrounded by +platform-specific 'chrome'. + +# Preview + +![App's platform toggling preview](adaptive-overview.gif) + +See https://youtu.be/svhbbFZg1IA for a longer non-gif format. + +# Features + +## Home + +Defines the top level navigation structure of the app and shows the contents +of the songs tab on launch. + +### Android + +* Uses the drawer paradigm on the root page. + +### iOS + +* Uses bottom tab bars with parallel navigation stacks. + +## Songs feed tab + +Shows platform-agnostic cards that is tappable and that performs a hero +transition on top of the platform native page transitions. + +Both platforms also show a button in their app/nav bar to toggle the platform. + +### Android + +* Android uses a static pull-to-refresh pattern with an additional refresh +button in the app bar. +* The song details page must be popped in order to change tabs on Android. + +### iOS + +* The iOS songs tab uses a scrollable iOS 11 large title style navigation bar. +* iOS uses an overscrolling pull-to-refresh pattern. +* On iOS, parallel tabs are always accessible and the songs tab's navigation +stack is preserved when changing tabs. + +## News Tab + +Shows platform-agnostic news boxes. + +### Android + +* The news tab always appears on top of the songs tab when summoned from the +drawer. + +### iOS + +* The news tab appears instead of the songs tab on iOS when switching tabs from +the tab bar. + +## Profile Tab + +Shows a number of user preferences. + +### Android + +* The profile tab appears on top of the songs tab on Android. +* Has tappable preference cards which shows a multiple-choice dialog on Android. +* The log out button shows a 2 button dialog on Android. + +### iOS + +* The profile tab appears instead of the songs tab on iOS. +* Has tappable preference cards which shows a picker on iOS. +* The log out button shows a 3 choice action sheet on iOS. + +## Settings Tab + +Shows a number of app settings via Material switches which auto adapt to the +platform. + +### Android + +* The settings is directly available in the drawer on Android since a Material +Design drawer can fit many tabs. + +### iOS + +* The settings is accessible from a button inside the profile tab's nav bar on +iOS. This is a common pattern since there are conventionally more items in the +drawer than there are tabs. +* On iOS, the settings page is shown as a full screen dialog instead of a tab +in the tab scaffold. diff --git a/platform_design/analysis_options.yaml b/platform_design/analysis_options.yaml new file mode 100644 index 0000000..5e2133e --- /dev/null +++ b/platform_design/analysis_options.yaml @@ -0,0 +1 @@ +include: ../analysis_options.yaml diff --git a/platform_design/android/.gitignore b/platform_design/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/platform_design/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/platform_design/android/app/build.gradle b/platform_design/android/app/build.gradle new file mode 100644 index 0000000..fa14479 --- /dev/null +++ b/platform_design/android/app/build.gradle @@ -0,0 +1,67 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +android { + namespace "dev.flutter.platform_design" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "dev.flutter.platform_design" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies {} diff --git a/platform_design/android/app/src/debug/AndroidManifest.xml b/platform_design/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/platform_design/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/platform_design/android/app/src/main/AndroidManifest.xml b/platform_design/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..60a98fa --- /dev/null +++ b/platform_design/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/platform_design/android/app/src/main/kotlin/dev/flutter/platform_design/MainActivity.kt b/platform_design/android/app/src/main/kotlin/dev/flutter/platform_design/MainActivity.kt new file mode 100644 index 0000000..372365b --- /dev/null +++ b/platform_design/android/app/src/main/kotlin/dev/flutter/platform_design/MainActivity.kt @@ -0,0 +1,6 @@ +package dev.flutter.platform_design + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/platform_design/android/app/src/main/res/drawable-v21/launch_background.xml b/platform_design/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/platform_design/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/platform_design/android/app/src/main/res/drawable/launch_background.xml b/platform_design/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/platform_design/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/platform_design/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/platform_design/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/platform_design/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/platform_design/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/platform_design/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/platform_design/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/platform_design/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/platform_design/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/platform_design/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/platform_design/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/platform_design/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/platform_design/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/platform_design/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/platform_design/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/platform_design/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/platform_design/android/app/src/main/res/values-night/styles.xml b/platform_design/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/platform_design/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/platform_design/android/app/src/main/res/values/styles.xml b/platform_design/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/platform_design/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/platform_design/android/app/src/profile/AndroidManifest.xml b/platform_design/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/platform_design/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/platform_design/android/build.gradle b/platform_design/android/build.gradle new file mode 100644 index 0000000..e83fb5d --- /dev/null +++ b/platform_design/android/build.gradle @@ -0,0 +1,30 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/platform_design/android/gradle.properties b/platform_design/android/gradle.properties new file mode 100644 index 0000000..598d13f --- /dev/null +++ b/platform_design/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G +android.useAndroidX=true +android.enableJetifier=true diff --git a/platform_design/android/gradle/wrapper/gradle-wrapper.properties b/platform_design/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/platform_design/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/platform_design/android/settings.gradle b/platform_design/android/settings.gradle new file mode 100644 index 0000000..7cd7128 --- /dev/null +++ b/platform_design/android/settings.gradle @@ -0,0 +1,29 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + + plugins { + id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false +} + +include ":app" diff --git a/platform_design/codelab_rebuild.yaml b/platform_design/codelab_rebuild.yaml new file mode 100644 index 0000000..718f7f5 --- /dev/null +++ b/platform_design/codelab_rebuild.yaml @@ -0,0 +1,29 @@ +# Run with tooling from https://github.com/flutter/codelabs/tree/main/tooling/codelab_rebuild +name: Platform Design rebuild script +steps: + - name: Remove runner + rmdirs: + - android + - ios + - web + - name: Flutter recreate runner + flutter: create --platform android,ios,web --org dev.flutter . + - name: Patch web/manifest.json + path: web/manifest.json + patch-u: | + --- b/platform_design/web/manifest.json + +++ a/platform_design/web/manifest.json + @@ -5,7 +5,7 @@ + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + - "description": "A new Flutter project.", + + "description": "A project showcasing a Flutter app following different platform IA conventions.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + - name: Update dependencies + flutter: pub upgrade --major-versions + - name: Build for iOS + platforms: [ macos ] + flutter: build ios --simulator diff --git a/platform_design/ios/.gitignore b/platform_design/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/platform_design/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/platform_design/ios/Flutter/AppFrameworkInfo.plist b/platform_design/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/platform_design/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/platform_design/ios/Flutter/Debug.xcconfig b/platform_design/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/platform_design/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/platform_design/ios/Flutter/Release.xcconfig b/platform_design/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/platform_design/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/platform_design/ios/Runner.xcodeproj/project.pbxproj b/platform_design/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..d8a4ad2 --- /dev/null +++ b/platform_design/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,614 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807E294A63A400263BE5 /* Frameworks */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.platformDesign; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.platformDesign.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.platformDesign.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.platformDesign.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.platformDesign; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.platformDesign; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/platform_design/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/platform_design/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/platform_design/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/platform_design/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/platform_design/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/platform_design/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/platform_design/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/platform_design/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/platform_design/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/platform_design/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/platform_design/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..87131a0 --- /dev/null +++ b/platform_design/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform_design/ios/Runner.xcworkspace/contents.xcworkspacedata b/platform_design/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/platform_design/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/platform_design/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/platform_design/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/platform_design/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/platform_design/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/platform_design/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/platform_design/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/platform_design/ios/Runner/AppDelegate.swift b/platform_design/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/platform_design/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/platform_design/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/platform_design/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/platform_design/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/platform_design/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/platform_design/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/platform_design/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/platform_design/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/platform_design/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/platform_design/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/platform_design/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/platform_design/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/platform_design/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/platform_design/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/platform_design/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/platform_design/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/platform_design/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/platform_design/ios/Runner/Base.lproj/LaunchScreen.storyboard b/platform_design/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/platform_design/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform_design/ios/Runner/Base.lproj/Main.storyboard b/platform_design/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/platform_design/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform_design/ios/Runner/Info.plist b/platform_design/ios/Runner/Info.plist new file mode 100644 index 0000000..e92f26f --- /dev/null +++ b/platform_design/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Platform Design + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + platform_design + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/platform_design/ios/Runner/Runner-Bridging-Header.h b/platform_design/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/platform_design/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/platform_design/ios/RunnerTests/RunnerTests.swift b/platform_design/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/platform_design/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/platform_design/lib/main.dart b/platform_design/lib/main.dart new file mode 100644 index 0000000..cc1ef22 --- /dev/null +++ b/platform_design/lib/main.dart @@ -0,0 +1,202 @@ +// Copyright 2020 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. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import 'news_tab.dart'; +import 'profile_tab.dart'; +import 'settings_tab.dart'; +import 'songs_tab.dart'; +import 'widgets.dart'; + +void main() => runApp(const MyAdaptingApp()); + +class MyAdaptingApp extends StatelessWidget { + const MyAdaptingApp({super.key}); + + @override + Widget build(context) { + // Either Material or Cupertino widgets work in either Material or Cupertino + // Apps. + return MaterialApp( + title: 'Adaptive Music App', + theme: ThemeData( + // Use the green theme for Material widgets. + primarySwatch: Colors.green, + ), + darkTheme: ThemeData.dark(), + builder: (context, child) { + return CupertinoTheme( + // Instead of letting Cupertino widgets auto-adapt to the Material + // theme (which is green), this app will use a different theme + // for Cupertino (which is blue by default). + data: const CupertinoThemeData(), + child: Material(child: child), + ); + }, + home: const PlatformAdaptingHomePage(), + ); + } +} + +// Shows a different type of scaffold depending on the platform. +// +// This file has the most amount of non-sharable code since it behaves the most +// differently between the platforms. +// +// These differences are also subjective and have more than one 'right' answer +// depending on the app and content. +class PlatformAdaptingHomePage extends StatefulWidget { + const PlatformAdaptingHomePage({super.key}); + + @override + State createState() => + _PlatformAdaptingHomePageState(); +} + +class _PlatformAdaptingHomePageState extends State { + // This app keeps a global key for the songs tab because it owns a bunch of + // data. Since changing platform re-parents those tabs into different + // scaffolds, keeping a global key to it lets this app keep that tab's data as + // the platform toggles. + // + // This isn't needed for apps that doesn't toggle platforms while running. + final songsTabKey = GlobalKey(); + + // In Material, this app uses the hamburger menu paradigm and flatly lists + // all 4 possible tabs. This drawer is injected into the songs tab which is + // actually building the scaffold around the drawer. + Widget _buildAndroidHomePage(BuildContext context) { + return SongsTab(key: songsTabKey, androidDrawer: _AndroidDrawer()); + } + + // On iOS, the app uses a bottom tab paradigm. Here, each tab view sits inside + // a tab in the tab scaffold. The tab scaffold also positions the tab bar + // in a row at the bottom. + // + // An important thing to note is that while a Material Drawer can display a + // large number of items, a tab bar cannot. To illustrate one way of adjusting + // for this, the app folds its fourth tab (the settings page) into the + // third tab. This is a common pattern on iOS. + Widget _buildIosHomePage(BuildContext context) { + return CupertinoTabScaffold( + tabBar: CupertinoTabBar( + items: const [ + BottomNavigationBarItem( + label: SongsTab.title, + icon: SongsTab.iosIcon, + ), + BottomNavigationBarItem( + label: NewsTab.title, + icon: NewsTab.iosIcon, + ), + BottomNavigationBarItem( + label: ProfileTab.title, + icon: ProfileTab.iosIcon, + ), + ], + ), + tabBuilder: (context, index) { + assert(index <= 2 && index >= 0, 'Unexpected tab index: $index'); + return switch (index) { + 0 => CupertinoTabView( + defaultTitle: SongsTab.title, + builder: (context) => SongsTab(key: songsTabKey), + ), + 1 => CupertinoTabView( + defaultTitle: NewsTab.title, + builder: (context) => const NewsTab(), + ), + 2 => CupertinoTabView( + defaultTitle: ProfileTab.title, + builder: (context) => const ProfileTab(), + ), + _ => const SizedBox.shrink(), + }; + }, + ); + } + + @override + Widget build(context) { + return PlatformWidget( + androidBuilder: _buildAndroidHomePage, + iosBuilder: _buildIosHomePage, + ); + } +} + +class _AndroidDrawer extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Drawer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + DrawerHeader( + decoration: const BoxDecoration(color: Colors.green), + child: Padding( + padding: const EdgeInsets.only(bottom: 20), + child: Icon( + Icons.account_circle, + color: Colors.green.shade800, + size: 96, + ), + ), + ), + ListTile( + leading: SongsTab.androidIcon, + title: const Text(SongsTab.title), + onTap: () { + Navigator.pop(context); + }, + ), + ListTile( + leading: NewsTab.androidIcon, + title: const Text(NewsTab.title), + onTap: () { + Navigator.pop(context); + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const NewsTab()), + ); + }, + ), + ListTile( + leading: ProfileTab.androidIcon, + title: const Text(ProfileTab.title), + onTap: () { + Navigator.pop(context); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const ProfileTab(), + ), + ); + }, + ), + // Long drawer contents are often segmented. + const Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: Divider(), + ), + ListTile( + leading: SettingsTab.androidIcon, + title: const Text(SettingsTab.title), + onTap: () { + Navigator.pop(context); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const SettingsTab(), + ), + ); + }, + ), + ], + ), + ); + } +} diff --git a/platform_design/lib/news_tab.dart b/platform_design/lib/news_tab.dart new file mode 100644 index 0000000..e4415d3 --- /dev/null +++ b/platform_design/lib/news_tab.dart @@ -0,0 +1,127 @@ +// Copyright 2020 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. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_lorem/flutter_lorem.dart'; + +import 'utils.dart'; +import 'widgets.dart'; + +class NewsTab extends StatefulWidget { + static const title = 'News'; + static const androidIcon = Icon(Icons.library_books); + static const iosIcon = Icon(CupertinoIcons.news); + + const NewsTab({super.key}); + + @override + State createState() => _NewsTabState(); +} + +class _NewsTabState extends State { + static const _itemsLength = 20; + + late final List colors; + late final List titles; + late final List contents; + + @override + void initState() { + colors = getRandomColors(_itemsLength); + titles = List.generate( + _itemsLength, + (index) => generateRandomHeadline(), + ); + contents = List.generate( + _itemsLength, + (index) => lorem(paragraphs: 1, words: 24), + ); + super.initState(); + } + + Widget _listBuilder(BuildContext context, int index) { + return SafeArea( + top: false, + bottom: false, + child: Card( + elevation: 1.5, + margin: const EdgeInsets.fromLTRB(6, 12, 6, 0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + ), + child: InkWell( + // Make it splash on Android. It would happen automatically if this + // was a real card but this is just a demo. Skip the splash on iOS. + onTap: defaultTargetPlatform == TargetPlatform.iOS ? null : () {}, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CircleAvatar( + backgroundColor: colors[index], + child: Text( + titles[index].substring(0, 1), + style: const TextStyle(color: Colors.white), + ), + ), + const Padding(padding: EdgeInsets.only(left: 16)), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + titles[index], + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + ), + ), + const Padding(padding: EdgeInsets.only(top: 8)), + Text(contents[index]), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } + + // =========================================================================== + // Non-shared code below because this tab uses different scaffolds. + // =========================================================================== + + Widget _buildAndroid(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text(NewsTab.title)), + body: ListView.builder( + itemCount: _itemsLength, + itemBuilder: _listBuilder, + ), + ); + } + + Widget _buildIos(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar(), + child: ListView.builder( + itemCount: _itemsLength, + itemBuilder: _listBuilder, + ), + ); + } + + @override + Widget build(context) { + return PlatformWidget( + androidBuilder: _buildAndroid, + iosBuilder: _buildIos, + ); + } +} diff --git a/platform_design/lib/profile_tab.dart b/platform_design/lib/profile_tab.dart new file mode 100644 index 0000000..6ce9bf5 --- /dev/null +++ b/platform_design/lib/profile_tab.dart @@ -0,0 +1,254 @@ +// Copyright 2020 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. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import 'settings_tab.dart'; +import 'widgets.dart'; + +class ProfileTab extends StatelessWidget { + static const title = 'Profile'; + static const androidIcon = Icon(Icons.person); + static const iosIcon = Icon(CupertinoIcons.profile_circled); + + const ProfileTab({super.key}); + + Widget _buildBody(BuildContext context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + children: [ + const Padding( + padding: EdgeInsets.all(8), + child: Center( + child: Text( + '😼', + style: TextStyle( + fontSize: 80, + decoration: TextDecoration.none, + ), + ), + ), + ), + const PreferenceCard( + header: 'MY INTENSITY PREFERENCE', + content: 'πŸ”₯', + preferenceChoices: [ + 'Super heavy', + 'Dial it to 11', + "Head bangin'", + '1000W', + 'My neighbor hates me', + ], + ), + const PreferenceCard( + header: 'CURRENT MOOD', + content: 'πŸ€˜πŸΎπŸš€', + preferenceChoices: [ + 'Over the moon', + 'Basking in sunlight', + 'Hello fellow Martians', + 'Into the darkness', + ], + ), + Expanded(child: Container()), + const LogOutButton(), + ], + ), + ), + ); + } + + // =========================================================================== + // Non-shared code below because on iOS, the settings tab is nested inside of + // the profile tab as a button in the nav bar. + // =========================================================================== + + Widget _buildAndroid(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text(title)), + body: _buildBody(context), + ); + } + + Widget _buildIos(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + trailing: CupertinoButton( + padding: EdgeInsets.zero, + child: SettingsTab.iosIcon, + onPressed: () { + // This pushes the settings page as a full page modal dialog on top + // of the tab bar and everything. + Navigator.of(context, rootNavigator: true).push( + CupertinoPageRoute( + title: SettingsTab.title, + fullscreenDialog: true, + builder: (context) => const SettingsTab(), + ), + ); + }, + ), + ), + child: _buildBody(context), + ); + } + + @override + Widget build(context) { + return PlatformWidget( + androidBuilder: _buildAndroid, + iosBuilder: _buildIos, + ); + } +} + +class PreferenceCard extends StatelessWidget { + const PreferenceCard({ + required this.header, + required this.content, + required this.preferenceChoices, + super.key, + }); + + final String header; + final String content; + final List preferenceChoices; + + @override + Widget build(context) { + return PressableCard( + color: Colors.green, + flattenAnimation: const AlwaysStoppedAnimation(0), + child: Stack( + children: [ + SizedBox( + height: 120, + width: 250, + child: Padding( + padding: const EdgeInsets.only(top: 40), + child: Center( + child: Text(content, style: const TextStyle(fontSize: 48)), + ), + ), + ), + Positioned( + top: 0, + left: 0, + right: 0, + child: Container( + color: Colors.black12, + height: 40, + padding: const EdgeInsets.only(left: 12), + alignment: Alignment.centerLeft, + child: Text( + header, + style: const TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], + ), + onPressed: () { + showChoices(context, preferenceChoices); + }, + ); + } +} + +class LogOutButton extends StatelessWidget { + static const _logoutMessage = Text( + "You can't actually log out! This is just a demo of how alerts work.", + ); + + const LogOutButton({super.key}); + + // =========================================================================== + // Non-shared code below because this tab shows different interfaces. On + // Android, it's showing an alert dialog with 2 buttons and on iOS, + // it's showing an action sheet with 3 choices. + // + // This is a design choice and you may want to do something different in your + // app. + // =========================================================================== + + Widget _buildAndroid(BuildContext context) { + return ElevatedButton( + child: const Text('LOG OUT', style: TextStyle(color: Colors.red)), + onPressed: () { + // You should do something with the result of the dialog prompt in a + // real app but this is just a demo. + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('Log out?'), + content: _logoutMessage, + actions: [ + TextButton( + child: const Text('Got it'), + onPressed: () => Navigator.pop(context), + ), + TextButton( + child: const Text('Cancel'), + onPressed: () => Navigator.pop(context), + ), + ], + ); + }, + ); + }, + ); + } + + Widget _buildIos(BuildContext context) { + return CupertinoButton( + color: CupertinoColors.destructiveRed, + child: const Text('Log out'), + onPressed: () { + // You should do something with the result of the action sheet prompt + // in a real app but this is just a demo. + showCupertinoModalPopup( + context: context, + builder: (context) { + return CupertinoActionSheet( + title: const Text('Log out?'), + message: _logoutMessage, + actions: [ + CupertinoActionSheetAction( + isDestructiveAction: true, + onPressed: () => Navigator.pop(context), + child: const Text('Reprogram the night man'), + ), + CupertinoActionSheetAction( + child: const Text('Got it'), + onPressed: () => Navigator.pop(context), + ), + ], + cancelButton: CupertinoActionSheetAction( + isDefaultAction: true, + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + ); + }, + ); + }, + ); + } + + @override + Widget build(context) { + return PlatformWidget( + androidBuilder: _buildAndroid, + iosBuilder: _buildIos, + ); + } +} diff --git a/platform_design/lib/settings_tab.dart b/platform_design/lib/settings_tab.dart new file mode 100644 index 0000000..43fa0d8 --- /dev/null +++ b/platform_design/lib/settings_tab.dart @@ -0,0 +1,113 @@ +// Copyright 2020 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. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import 'widgets.dart'; + +class SettingsTab extends StatefulWidget { + static const title = 'Settings'; + static const androidIcon = Icon(Icons.settings); + static const iosIcon = Icon(CupertinoIcons.gear); + + const SettingsTab({super.key}); + + @override + State createState() => _SettingsTabState(); +} + +class _SettingsTabState extends State { + var switch1 = false; + var switch2 = true; + var switch3 = true; + var switch4 = true; + var switch5 = true; + var switch6 = false; + var switch7 = true; + + Widget _buildList() { + return ListView( + children: [ + const Padding(padding: EdgeInsets.only(top: 24)), + ListTile( + title: const Text('Send me marketing emails'), + // The Material switch has a platform adaptive constructor. + trailing: Switch.adaptive( + value: switch1, + onChanged: (value) => setState(() => switch1 = value), + ), + ), + ListTile( + title: const Text('Enable notifications'), + trailing: Switch.adaptive( + value: switch2, + onChanged: (value) => setState(() => switch2 = value), + ), + ), + ListTile( + title: const Text('Remind me to rate this app'), + trailing: Switch.adaptive( + value: switch3, + onChanged: (value) => setState(() => switch3 = value), + ), + ), + ListTile( + title: const Text('Background song refresh'), + trailing: Switch.adaptive( + value: switch4, + onChanged: (value) => setState(() => switch4 = value), + ), + ), + ListTile( + title: const Text('Recommend me songs based on my location'), + trailing: Switch.adaptive( + value: switch5, + onChanged: (value) => setState(() => switch5 = value), + ), + ), + ListTile( + title: const Text('Auto-transition playback to cast devices'), + trailing: Switch.adaptive( + value: switch6, + onChanged: (value) => setState(() => switch6 = value), + ), + ), + ListTile( + title: const Text('Find friends from my contact list'), + trailing: Switch.adaptive( + value: switch7, + onChanged: (value) => setState(() => switch7 = value), + ), + ), + ], + ); + } + + // =========================================================================== + // Non-shared code below because this tab uses different scaffolds. + // =========================================================================== + + Widget _buildAndroid(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text(SettingsTab.title)), + body: _buildList(), + ); + } + + Widget _buildIos(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar(), + child: _buildList(), + ); + } + + @override + Widget build(context) { + return PlatformWidget( + androidBuilder: _buildAndroid, + iosBuilder: _buildIos, + ); + } +} diff --git a/platform_design/lib/song_detail_tab.dart b/platform_design/lib/song_detail_tab.dart new file mode 100644 index 0000000..2236c28 --- /dev/null +++ b/platform_design/lib/song_detail_tab.dart @@ -0,0 +1,112 @@ +// Copyright 2020 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. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import 'widgets.dart'; + +/// Page shown when a card in the songs tab is tapped. +/// +/// On Android, this page sits at the top of your app. On iOS, this page is on +/// top of the songs tab's content but is below the tab bar itself. +class SongDetailTab extends StatelessWidget { + const SongDetailTab({ + required this.id, + required this.song, + required this.color, + super.key, + }); + + final int id; + final String song; + final Color color; + + Widget _buildBody() { + return SafeArea( + bottom: false, + left: false, + right: false, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Hero( + tag: id, + child: HeroAnimatingSongCard( + song: song, + color: color, + heroAnimation: const AlwaysStoppedAnimation(1), + ), + // This app uses a flightShuttleBuilder to specify the exact widget + // to build while the hero transition is mid-flight. + // + // It could either be specified here or in SongsTab. + flightShuttleBuilder: + ( + context, + animation, + flightDirection, + fromHeroContext, + toHeroContext, + ) { + return HeroAnimatingSongCard( + song: song, + color: color, + heroAnimation: animation, + ); + }, + ), + const Divider(height: 0, color: Colors.grey), + Expanded( + child: ListView.builder( + itemCount: 10, + itemBuilder: (context, index) => switch (index) { + 0 => const Padding( + padding: EdgeInsets.only(left: 15, top: 16, bottom: 8), + child: Text( + 'You might also like:', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + _ => const SongPlaceholderTile(), + }, + ), + ), + ], + ), + ); + } + + // =========================================================================== + // Non-shared code below because we're using different scaffolds. + // =========================================================================== + + Widget _buildAndroid(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(song)), + body: _buildBody(), + ); + } + + Widget _buildIos(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text(song), + previousPageTitle: 'Songs', + ), + child: _buildBody(), + ); + } + + @override + Widget build(context) { + return PlatformWidget( + androidBuilder: _buildAndroid, + iosBuilder: _buildIos, + ); + } +} diff --git a/platform_design/lib/songs_tab.dart b/platform_design/lib/songs_tab.dart new file mode 100644 index 0000000..928b497 --- /dev/null +++ b/platform_design/lib/songs_tab.dart @@ -0,0 +1,173 @@ +// Copyright 2020 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. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'song_detail_tab.dart'; +import 'utils.dart'; +import 'widgets.dart'; + +class SongsTab extends StatefulWidget { + static const title = 'Songs'; + static const androidIcon = Icon(Icons.music_note); + static const iosIcon = Icon(CupertinoIcons.music_note); + + const SongsTab({super.key, this.androidDrawer}); + + final Widget? androidDrawer; + + @override + State createState() => _SongsTabState(); +} + +class _SongsTabState extends State { + static const _itemsLength = 50; + + final _androidRefreshKey = GlobalKey(); + + late List colors; + late List songNames; + + @override + void initState() { + _setData(); + super.initState(); + } + + void _setData() { + colors = getRandomColors(_itemsLength); + songNames = getRandomNames(_itemsLength); + } + + Future _refreshData() { + return Future.delayed( + // This is just an arbitrary delay that simulates some network activity. + const Duration(seconds: 2), + () => setState(() => _setData()), + ); + } + + Widget _listBuilder(BuildContext context, int index) { + if (index >= _itemsLength) return Container(); + + // Show a slightly different color palette. Show poppy-ier colors on iOS + // due to lighter contrasting bars and tone it down on Android. + final color = defaultTargetPlatform == TargetPlatform.iOS + ? colors[index] + : colors[index].shade400; + + return SafeArea( + top: false, + bottom: false, + child: Hero( + tag: index, + child: HeroAnimatingSongCard( + song: songNames[index], + color: color, + heroAnimation: const AlwaysStoppedAnimation(0), + onPressed: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => SongDetailTab( + id: index, + song: songNames[index], + color: color, + ), + ), + ), + ), + ), + ); + } + + void _togglePlatform() { + if (defaultTargetPlatform == TargetPlatform.iOS) { + debugDefaultTargetPlatformOverride = TargetPlatform.android; + } else { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + } + + // This rebuilds the application. This should obviously never be + // done in a real app but it's done here since this app + // unrealistically toggles the current platform for demonstration + // purposes. + WidgetsBinding.instance.reassembleApplication(); + } + + // =========================================================================== + // Non-shared code below because: + // - Android and iOS have different scaffolds + // - There are different items in the app bar / nav bar + // - Android has a hamburger drawer, iOS has bottom tabs + // - The iOS nav bar is scrollable, Android is not + // - Pull-to-refresh works differently, and Android has a button to trigger it too + // + // And these are all design time choices that doesn't have a single 'right' + // answer. + // =========================================================================== + Widget _buildAndroid(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text(SongsTab.title), + actions: [ + IconButton( + icon: const Icon(Icons.refresh), + onPressed: () async => + await _androidRefreshKey.currentState!.show(), + ), + IconButton( + icon: const Icon(Icons.shuffle), + onPressed: _togglePlatform, + ), + ], + ), + drawer: widget.androidDrawer, + body: RefreshIndicator( + key: _androidRefreshKey, + onRefresh: _refreshData, + child: ListView.builder( + padding: const EdgeInsets.symmetric(vertical: 12), + itemCount: _itemsLength, + itemBuilder: _listBuilder, + ), + ), + ); + } + + Widget _buildIos(BuildContext context) { + return CustomScrollView( + slivers: [ + CupertinoSliverNavigationBar( + trailing: CupertinoButton( + padding: EdgeInsets.zero, + onPressed: _togglePlatform, + child: const Icon(CupertinoIcons.shuffle), + ), + ), + CupertinoSliverRefreshControl(onRefresh: _refreshData), + SliverSafeArea( + top: false, + sliver: SliverPadding( + padding: const EdgeInsets.symmetric(vertical: 12), + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + _listBuilder, + childCount: _itemsLength, + ), + ), + ), + ), + ], + ); + } + + @override + Widget build(context) { + return PlatformWidget( + androidBuilder: _buildAndroid, + iosBuilder: _buildIos, + ); + } +} diff --git a/platform_design/lib/utils.dart b/platform_design/lib/utils.dart new file mode 100644 index 0000000..c26417f --- /dev/null +++ b/platform_design/lib/utils.dart @@ -0,0 +1,76 @@ +// Copyright 2020 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. + +import 'dart:math'; + +import 'package:english_words/english_words.dart'; +// ignore: implementation_imports +import 'package:flutter/material.dart'; + +// This file has a number of platform-agnostic non-Widget utility functions. + +const _myListOfRandomColors = [ + Colors.red, + Colors.blue, + Colors.teal, + Colors.yellow, + Colors.amber, + Colors.deepOrange, + Colors.green, + Colors.indigo, + Colors.lime, + Colors.pink, + Colors.orange, +]; + +final _random = Random(); + +// Avoid customizing the word generator, which can be slow. +// https://github.com/filiph/english_words/issues/9 +final wordPairIterator = generateWordPairs(); + +String generateRandomHeadline() { + final artist = capitalizePair(wordPairIterator.first); + + return switch (_random.nextInt(10)) { + 0 => '$artist says ${nouns[_random.nextInt(nouns.length)]}', + 1 => '$artist arrested due to ${wordPairIterator.first.join(' ')}', + 2 => '$artist releases ${capitalizePair(wordPairIterator.first)}', + 3 => '$artist talks about his ${nouns[_random.nextInt(nouns.length)]}', + 4 => '$artist talks about her ${nouns[_random.nextInt(nouns.length)]}', + 5 => '$artist talks about their ${nouns[_random.nextInt(nouns.length)]}', + 6 => + '$artist says their music is inspired by ${wordPairIterator.first.join(' ')}', + 7 => + '$artist says the world needs more ${nouns[_random.nextInt(nouns.length)]}', + 8 => + '$artist calls their band ${adjectives[_random.nextInt(adjectives.length)]}', + 9 => + '$artist finally ready to talk about ${nouns[_random.nextInt(nouns.length)]}', + _ => 'Failed to generate news headline', + }; +} + +List getRandomColors(int amount) { + return List.generate(amount, (index) { + return _myListOfRandomColors[_random.nextInt( + _myListOfRandomColors.length, + )]; + }); +} + +List getRandomNames(int amount) { + return wordPairIterator + .take(amount) + .map((pair) => capitalizePair(pair)) + .toList(); +} + +String capitalize(String word) { + return '${word[0].toUpperCase()}${word.substring(1).toLowerCase()}'; +} + +String capitalizePair(WordPair pair) { + return '${capitalize(pair.first)} ${capitalize(pair.second)}'; +} diff --git a/platform_design/lib/widgets.dart b/platform_design/lib/widgets.dart new file mode 100644 index 0000000..439f276 --- /dev/null +++ b/platform_design/lib/widgets.dart @@ -0,0 +1,373 @@ +// Copyright 2020 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. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +/// A simple widget that builds different things on different platforms. +class PlatformWidget extends StatelessWidget { + const PlatformWidget({ + super.key, + required this.androidBuilder, + required this.iosBuilder, + }); + + final WidgetBuilder androidBuilder; + final WidgetBuilder iosBuilder; + + @override + Widget build(context) { + assert( + defaultTargetPlatform == TargetPlatform.android || + defaultTargetPlatform == TargetPlatform.iOS, + 'Unexpected platform $defaultTargetPlatform', + ); + return switch (defaultTargetPlatform) { + TargetPlatform.android => androidBuilder(context), + TargetPlatform.iOS => iosBuilder(context), + _ => const SizedBox.shrink(), + }; + } +} + +/// A platform-agnostic card with a high elevation that reacts when tapped. +/// +/// This is an example of a custom widget that an app developer might create for +/// use on both iOS and Android as part of their brand's unique design. +class PressableCard extends StatefulWidget { + const PressableCard({ + this.onPressed, + required this.color, + required this.flattenAnimation, + this.child, + super.key, + }); + + final VoidCallback? onPressed; + final Color color; + final Animation flattenAnimation; + final Widget? child; + + @override + State createState() => _PressableCardState(); +} + +class _PressableCardState extends State + with SingleTickerProviderStateMixin { + bool pressed = false; + late final AnimationController controller; + late final Animation elevationAnimation; + + @override + void initState() { + controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 40), + ); + elevationAnimation = controller.drive( + CurveTween(curve: Curves.easeInOutCubic), + ); + super.initState(); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + double get flatten => 1 - widget.flattenAnimation.value; + + @override + Widget build(context) { + return Listener( + onPointerDown: (details) { + if (widget.onPressed != null) { + controller.forward(); + } + }, + onPointerUp: (details) { + controller.reverse(); + }, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + widget.onPressed?.call(); + }, + // This widget both internally drives an animation when pressed and + // responds to an external animation to flatten the card when in a + // hero animation. You likely want to modularize them more in your own + // app. + child: AnimatedBuilder( + animation: Listenable.merge([ + elevationAnimation, + widget.flattenAnimation, + ]), + child: widget.child, + builder: (context, child) { + return Transform.scale( + // This is just a sample. You likely want to keep the math cleaner + // in your own app. + scale: 1 - elevationAnimation.value * 0.03, + child: Padding( + padding: + const EdgeInsets.symmetric( + vertical: 16, + horizontal: 16, + ) * + flatten, + child: PhysicalModel( + elevation: + ((1 - elevationAnimation.value) * 10 + 10) * flatten, + borderRadius: BorderRadius.circular(12 * flatten), + clipBehavior: Clip.antiAlias, + color: widget.color, + child: child, + ), + ), + ); + }, + ), + ), + ); + } +} + +/// A platform-agnostic card representing a song which can be in a card state, +/// a flat state or anything in between. +/// +/// When it's in a card state, it's pressable. +/// +/// This is an example of a custom widget that an app developer might create for +/// use on both iOS and Android as part of their brand's unique design. +class HeroAnimatingSongCard extends StatelessWidget { + const HeroAnimatingSongCard({ + required this.song, + required this.color, + required this.heroAnimation, + this.onPressed, + super.key, + }); + + final String song; + final Color color; + final Animation heroAnimation; + final VoidCallback? onPressed; + + double get playButtonSize => 50 + 50 * heroAnimation.value; + + @override + Widget build(context) { + // This is an inefficient usage of AnimatedBuilder since it's rebuilding + // the entire subtree instead of passing in a non-changing child and + // building a transition widget in between. + // + // Left simple in this demo because this card doesn't have any real inner + // content so this just rebuilds everything while animating. + return AnimatedBuilder( + animation: heroAnimation, + builder: (context, child) { + return PressableCard( + onPressed: heroAnimation.value == 0 ? onPressed : null, + color: color, + flattenAnimation: heroAnimation, + child: SizedBox( + height: 250, + child: Stack( + alignment: Alignment.center, + children: [ + // The song title banner slides off in the hero animation. + Positioned( + bottom: -80 * heroAnimation.value, + left: 0, + right: 0, + child: Container( + height: 80, + color: Colors.black12, + alignment: Alignment.centerLeft, + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text( + song, + style: const TextStyle( + fontSize: 21, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + // The play button grows in the hero animation. + Padding( + padding: + const EdgeInsets.only(bottom: 45) * + (1 - heroAnimation.value), + child: Container( + height: playButtonSize, + width: playButtonSize, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.black12, + ), + alignment: Alignment.center, + child: Icon( + Icons.play_arrow, + size: playButtonSize, + color: Colors.black38, + ), + ), + ), + ], + ), + ), + ); + }, + ); + } +} + +/// A loading song tile's silhouette. +/// +/// This is an example of a custom widget that an app developer might create for +/// use on both iOS and Android as part of their brand's unique design. +class SongPlaceholderTile extends StatelessWidget { + const SongPlaceholderTile({super.key}); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 95, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8), + child: Row( + children: [ + Container( + color: Theme.of(context).textTheme.bodyMedium!.color, + width: 130, + ), + const Padding(padding: EdgeInsets.only(left: 12)), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 9, + margin: const EdgeInsets.only(right: 60), + color: Theme.of(context).textTheme.bodyMedium!.color, + ), + Container( + height: 9, + margin: const EdgeInsets.only(right: 20, top: 8), + color: Theme.of(context).textTheme.bodyMedium!.color, + ), + Container( + height: 9, + margin: const EdgeInsets.only(right: 40, top: 8), + color: Theme.of(context).textTheme.bodyMedium!.color, + ), + Container( + height: 9, + margin: const EdgeInsets.only(right: 80, top: 8), + color: Theme.of(context).textTheme.bodyMedium!.color, + ), + Container( + height: 9, + margin: const EdgeInsets.only(right: 50, top: 8), + color: Theme.of(context).textTheme.bodyMedium!.color, + ), + ], + ), + ), + ], + ), + ), + ); + } +} + +// =========================================================================== +// Non-shared code below because different interfaces are shown to prompt +// for a multiple-choice answer. +// +// This is a design choice and you may want to do something different in your +// app. +// =========================================================================== +/// This uses a platform-appropriate mechanism to show users multiple choices. +/// +/// On Android, it uses a dialog with radio buttons. On iOS, it uses a picker. +void showChoices(BuildContext context, List choices) { + switch (defaultTargetPlatform) { + case TargetPlatform.android: + showDialog( + context: context, + builder: (context) { + int? selectedRadio = 1; + return AlertDialog( + contentPadding: const EdgeInsets.only(top: 12), + content: StatefulBuilder( + builder: (context, setState) { + return RadioGroup( + groupValue: selectedRadio, + onChanged: (value) { + setState(() => selectedRadio = value); + }, + child: Column( + mainAxisSize: MainAxisSize.min, + children: List.generate(choices.length, ( + index, + ) { + return RadioListTile( + title: Text(choices[index]), + value: index, + ); + }), + ), + ); + }, + ), + actions: [ + TextButton( + child: const Text('OK'), + onPressed: () => Navigator.of(context).pop(), + ), + TextButton( + child: const Text('CANCEL'), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ); + }, + ); + return; + case TargetPlatform.iOS: + showCupertinoModalPopup( + context: context, + builder: (context) { + return SizedBox( + height: 250, + child: CupertinoPicker( + backgroundColor: Theme.of(context).canvasColor, + useMagnifier: true, + magnification: 1.1, + itemExtent: 40, + scrollController: FixedExtentScrollController( + initialItem: 1, + ), + children: List.generate(choices.length, (index) { + return Center( + child: Text( + choices[index], + style: const TextStyle(fontSize: 21), + ), + ); + }), + onSelectedItemChanged: (value) {}, + ), + ); + }, + ); + return; + default: + assert(false, 'Unexpected platform $defaultTargetPlatform'); + } +} diff --git a/platform_design/pubspec.yaml b/platform_design/pubspec.yaml new file mode 100644 index 0000000..53709da --- /dev/null +++ b/platform_design/pubspec.yaml @@ -0,0 +1,22 @@ +name: platform_design +description: A project showcasing a Flutter app following different platform IA conventions. +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + english_words: ^4.0.0 + flutter_lorem: ^2.0.0 + flutter: + sdk: flutter + cupertino_icons: ^1.0.2 + +dev_dependencies: + analysis_defaults: + path: ../analysis_defaults + flutter_test: + sdk: flutter +flutter: + uses-material-design: true diff --git a/platform_design/test/widget_test.dart b/platform_design/test/widget_test.dart new file mode 100644 index 0000000..f4756a3 --- /dev/null +++ b/platform_design/test/widget_test.dart @@ -0,0 +1,42 @@ +// Copyright 2020 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. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:platform_design/main.dart'; + +void main() { + group('Platform tests', () { + testWidgets('Builds for Android correctly', (tester) async { + debugDefaultTargetPlatformOverride = TargetPlatform.android; + await tester.pumpWidget(const MyAdaptingApp()); + + // The test should be able to find the drawer button. + expect(find.byIcon(Icons.menu), findsOneWidget); + // There should be a refresh button. + expect(find.byIcon(Icons.refresh), findsOneWidget); + + // Since this is a static, undo any change made in the test. + debugDefaultTargetPlatformOverride = null; + }); + + testWidgets('Builds for iOS correctly', (tester) async { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + await tester.pumpWidget(const MyAdaptingApp()); + + // There should now be a large title style nav bar. + expect(find.byType(CupertinoSliverNavigationBar), findsOneWidget); + // There's a tab button for the first tab. + expect(find.byIcon(CupertinoIcons.music_note), findsOneWidget); + // The hamburger button isn't there anymore. + expect(find.byIcon(Icons.menu), findsNothing); + + // Since this is a static, undo any change made in the test. + debugDefaultTargetPlatformOverride = null; + }); + }); +} diff --git a/platform_design/web/favicon.png b/platform_design/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/platform_design/web/favicon.png differ diff --git a/platform_design/web/icons/Icon-192.png b/platform_design/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/platform_design/web/icons/Icon-192.png differ diff --git a/platform_design/web/icons/Icon-512.png b/platform_design/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/platform_design/web/icons/Icon-512.png differ diff --git a/platform_design/web/icons/Icon-maskable-192.png b/platform_design/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/platform_design/web/icons/Icon-maskable-192.png differ diff --git a/platform_design/web/icons/Icon-maskable-512.png b/platform_design/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/platform_design/web/icons/Icon-maskable-512.png differ diff --git a/platform_design/web/index.html b/platform_design/web/index.html new file mode 100644 index 0000000..c4f4da5 --- /dev/null +++ b/platform_design/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + platform_design + + + + + + diff --git a/platform_design/web/manifest.json b/platform_design/web/manifest.json new file mode 100644 index 0000000..08f599d --- /dev/null +++ b/platform_design/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "platform_design", + "short_name": "platform_design", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A project showcasing a Flutter app following different platform IA conventions.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/platform_view_swift/.gitignore b/platform_view_swift/.gitignore new file mode 100644 index 0000000..63c9203 --- /dev/null +++ b/platform_view_swift/.gitignore @@ -0,0 +1,74 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.packages +.pub-cache/ +.pub/ +/build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# 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/ephemeral/ +**/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.* + +# 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 diff --git a/platform_view_swift/.metadata b/platform_view_swift/.metadata new file mode 100644 index 0000000..aeb01ee --- /dev/null +++ b/platform_view_swift/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 2d2a1ffec95cc70a3218872a2cd3f8de4933c42f + channel: stable + +project_type: app diff --git a/platform_view_swift/README.md b/platform_view_swift/README.md new file mode 100644 index 0000000..90a9e0e --- /dev/null +++ b/platform_view_swift/README.md @@ -0,0 +1,33 @@ +# platform_view_swift + +A Flutter sample app that combines a native iOS UIViewController +with a full-screen Flutter view. + +## Goals for this sample + +* Show a simple technique for combining native and Flutter views. + +## The important bits + +### `lib/main.dart` + +The Flutter part of the application is quite simple, and all the action +takes place in a single file. + +### `ios/Runner/PlatformViewController.swift` and `AppDelegate.swift` + +These files contain the Swift code responsible for setting up a platform +channel, launching a native UIViewController, and returning control to +Flutter when finished. + +## Questions/issues + +If you have a general question about Flutter, the best places to go are: + +* [The FlutterDev Google Group](https://groups.google.com/forum/#!forum/flutter-dev) +* [The Flutter Gitter channel](https://gitter.im/flutter/flutter) +* [StackOverflow](https://stackoverflow.com/questions/tagged/flutter) + +If you run into an issue with the sample itself, please file an issue +in the [main Flutter repo](https://github.com/flutter/flutter/issues). + diff --git a/platform_view_swift/analysis_options.yaml b/platform_view_swift/analysis_options.yaml new file mode 100644 index 0000000..5e2133e --- /dev/null +++ b/platform_view_swift/analysis_options.yaml @@ -0,0 +1 @@ +include: ../analysis_options.yaml diff --git a/platform_view_swift/ios/.gitignore b/platform_view_swift/ios/.gitignore new file mode 100644 index 0000000..e96ef60 --- /dev/null +++ b/platform_view_swift/ios/.gitignore @@ -0,0 +1,32 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/platform_view_swift/ios/Flutter/AppFrameworkInfo.plist b/platform_view_swift/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..6b4c0f7 --- /dev/null +++ b/platform_view_swift/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/platform_view_swift/ios/Flutter/Debug.xcconfig b/platform_view_swift/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/platform_view_swift/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/platform_view_swift/ios/Flutter/Release.xcconfig b/platform_view_swift/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/platform_view_swift/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/platform_view_swift/ios/Runner.xcodeproj/project.pbxproj b/platform_view_swift/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..f509ccf --- /dev/null +++ b/platform_view_swift/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,529 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 26CDDD6B234BE139004C7A30 /* PlatformViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26CDDD69234BE139004C7A30 /* PlatformViewController.swift */; }; + 26CDDD6C234BE139004C7A30 /* PlatformViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 26CDDD6A234BE139004C7A30 /* PlatformViewController.xib */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 26CDDD69234BE139004C7A30 /* PlatformViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformViewController.swift; sourceTree = ""; }; + 26CDDD6A234BE139004C7A30 /* PlatformViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PlatformViewController.xib; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B80C3931E831B6300D905FE /* App.framework */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEBA1CF902C7004384FC /* Flutter.framework */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + 26CDDD69234BE139004C7A30 /* PlatformViewController.swift */, + 26CDDD6A234BE139004C7A30 /* PlatformViewController.xib */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = "The Chromium Authors"; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 0910; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + 26CDDD6C234BE139004C7A30 /* PlatformViewController.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 26CDDD6B234BE139004C7A30 /* PlatformViewController.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.samples.platformViewSwift; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.samples.platformViewSwift; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.samples.platformViewSwift; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/platform_view_swift/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/platform_view_swift/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/platform_view_swift/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/platform_view_swift/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/platform_view_swift/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..a28140c --- /dev/null +++ b/platform_view_swift/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform_view_swift/ios/Runner.xcworkspace/contents.xcworkspacedata b/platform_view_swift/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/platform_view_swift/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/platform_view_swift/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/platform_view_swift/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/platform_view_swift/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/platform_view_swift/ios/Runner/AppDelegate.swift b/platform_view_swift/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..630e344 --- /dev/null +++ b/platform_view_swift/ios/Runner/AppDelegate.swift @@ -0,0 +1,43 @@ +// Copyright 2018, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate, PlatformViewControllerDelegate { + var flutterResult: FlutterResult? + + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + let controller: FlutterViewController = window?.rootViewController as! FlutterViewController + let channel = FlutterMethodChannel.init(name: "dev.flutter.sample/platform_view_swift", binaryMessenger: controller.binaryMessenger) + + channel.setMethodCallHandler({ + (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in + if ("switchView" == call.method) { + self.flutterResult = result + + let platformViewController = PlatformViewController(nibName: "PlatformViewController", bundle: nil) + platformViewController.counter = call.arguments as! Int + platformViewController.delegate = self + + let navigationController = UINavigationController(rootViewController: platformViewController) + navigationController.navigationBar.topItem?.title = "Platform View" + controller.present(navigationController, animated: true, completion: nil) + } else { + result(FlutterMethodNotImplemented) + } + }); + + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + func didUpdateCounter(counter: Int) { + flutterResult?(counter) + } +} diff --git a/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..28c6bf0 Binary files /dev/null and b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..f091b6b Binary files /dev/null and b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cde121 Binary files /dev/null and b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..d0ef06e Binary files /dev/null and b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..dcdc230 Binary files /dev/null and b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..c8f9ed8 Binary files /dev/null and b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..75b2d16 Binary files /dev/null and b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..c4df70d Binary files /dev/null and b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..6a84f41 Binary files /dev/null and b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..d0e1f58 Binary files /dev/null and b/platform_view_swift/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/platform_view_swift/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/platform_view_swift/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/platform_view_swift/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/platform_view_swift/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/platform_view_swift/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/platform_view_swift/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/platform_view_swift/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/platform_view_swift/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/platform_view_swift/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/platform_view_swift/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/platform_view_swift/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/platform_view_swift/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/platform_view_swift/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/platform_view_swift/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/platform_view_swift/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/platform_view_swift/ios/Runner/Base.lproj/LaunchScreen.storyboard b/platform_view_swift/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/platform_view_swift/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform_view_swift/ios/Runner/Base.lproj/Main.storyboard b/platform_view_swift/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/platform_view_swift/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform_view_swift/ios/Runner/Info.plist b/platform_view_swift/ios/Runner/Info.plist new file mode 100644 index 0000000..83cff7f --- /dev/null +++ b/platform_view_swift/ios/Runner/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + platform_view_swift + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/platform_view_swift/ios/Runner/PlatformViewController.swift b/platform_view_swift/ios/Runner/PlatformViewController.swift new file mode 100644 index 0000000..1b99226 --- /dev/null +++ b/platform_view_swift/ios/Runner/PlatformViewController.swift @@ -0,0 +1,37 @@ +// Copyright 2018, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import UIKit +import Foundation + +protocol PlatformViewControllerDelegate { + func didUpdateCounter(counter: Int) +} + +class PlatformViewController : UIViewController { + var delegate: PlatformViewControllerDelegate? = nil + var counter: Int = 0 + + @IBOutlet weak var incrementLabel: UILabel! + + override func viewDidLoad() { + super.viewDidLoad() + setIncrementLabelText() + } + + @IBAction func handleIncrement(_ sender: Any) { + self.counter += 1 + self.setIncrementLabelText() + } + + @IBAction func switchToFlutterView(_ sender: Any) { + self.delegate?.didUpdateCounter(counter: self.counter) + dismiss(animated:false, completion:nil) + } + + func setIncrementLabelText() { + let text = String(format: "Button tapped %d %@", self.counter, (self.counter == 1) ? "time" : "times") + self.incrementLabel.text = text; + } +} diff --git a/platform_view_swift/ios/Runner/PlatformViewController.xib b/platform_view_swift/ios/Runner/PlatformViewController.xib new file mode 100644 index 0000000..2846ab5 --- /dev/null +++ b/platform_view_swift/ios/Runner/PlatformViewController.xib @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform_view_swift/ios/Runner/Runner-Bridging-Header.h b/platform_view_swift/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/platform_view_swift/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/platform_view_swift/lib/main.dart b/platform_view_swift/lib/main.dart new file mode 100644 index 0000000..554d694 --- /dev/null +++ b/platform_view_swift/lib/main.dart @@ -0,0 +1,95 @@ +// Copyright 2018, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +void main() { + runApp(const PlatformView()); +} + +class PlatformView extends StatelessWidget { + const PlatformView({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Platform View', + theme: ThemeData(primarySwatch: Colors.grey), + home: const HomePage(), + ); + } +} + +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + static const MethodChannel _methodChannel = MethodChannel( + 'dev.flutter.sample/platform_view_swift', + ); + + int _counter = 0; + + Future _launchPlatformCount() async { + final platformCounter = await _methodChannel.invokeMethod( + 'switchView', + _counter, + ); + setState(() => _counter = platformCounter ?? 0); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Home page')), + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Button tapped $_counter time${_counter == 1 ? '' : 's'}.', + style: Theme.of(context).textTheme.titleSmall, + ), + const SizedBox(height: 18), + ElevatedButton( + onPressed: _launchPlatformCount, + child: const Text('Continue in iOS view'), + ), + ], + ), + ), + ), + Container( + padding: const EdgeInsets.only(bottom: 15, left: 5), + child: Row( + children: [ + const FlutterLogo(), + Text( + 'Flutter', + style: Theme.of(context).textTheme.headlineSmall, + ), + ], + ), + ), + ], + ), + floatingActionButton: FloatingActionButton( + onPressed: () => setState(() => _counter++), + tooltip: 'Increment', + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/platform_view_swift/pubspec.yaml b/platform_view_swift/pubspec.yaml new file mode 100644 index 0000000..22c2b7d --- /dev/null +++ b/platform_view_swift/pubspec.yaml @@ -0,0 +1,20 @@ +name: platform_view_swift +description: A new Flutter project. +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^1.0.4 + +dev_dependencies: + analysis_defaults: + path: ../analysis_defaults + flutter_test: + sdk: flutter +flutter: + uses-material-design: true diff --git a/platform_view_swift/test/widget_test.dart b/platform_view_swift/test/widget_test.dart new file mode 100644 index 0000000..52ad356 --- /dev/null +++ b/platform_view_swift/test/widget_test.dart @@ -0,0 +1,9 @@ +// Copyright 2018, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('This test always passes', (tester) async {}); +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..60a3d1a --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,60 @@ +name: samples +description: A collection of samples for Dart and Flutter. + +environment: + sdk: ^3.9.0-0 + +dependencies: + flutter_lints: ^6.0.0 + +workspace: + - add_to_app/android_view/flutter_module_using_plugin_android_view + - add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view + - add_to_app/books/flutter_module_books + - add_to_app/fullscreen/flutter_module_fullscreen + - add_to_app/multiple_flutters/multiple_flutters_module + - add_to_app/plugin/flutter_module_using_plugin + - add_to_app/prebuilt_module/flutter_module + - analysis_defaults + - android_splash_screen + - animations + - asset_transformation + - asset_transformation/grayscale_transformer + - background_isolate_channels + - compass_app/app + - compass_app/server + - cupertino_gallery + - date_planner + - desktop_photo_search/fluent_ui + - desktop_photo_search/material + - dynamic_theme + - form_app + - google_maps + - ios_app_clip + - material_3_demo + - navigation_and_routing + - pedometer + - pedometer/example + - platform_channels + - platform_design + - platform_view_swift + - simple_sdf + - simple_shader + - testing_app + - tool + - web_embedding/element_embedding_demo + - web_embedding/ng-flutter/flutter + +skip_ci: + - add_to_app/android_view/flutter_module_using_plugin_android_view + - add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view + - add_to_app/books/flutter_module_books + - add_to_app/fullscreen/flutter_module_fullscreen + - add_to_app/multiple_flutters/multiple_flutters_module + - add_to_app/plugin/flutter_module_using_plugin + - add_to_app/prebuilt_module/flutter_module + - cupertino_gallery + +skip_ci_beta: + # - sample_name_to_skip + diff --git a/simple_sdf/.gitignore b/simple_sdf/.gitignore new file mode 100644 index 0000000..24476c5 --- /dev/null +++ b/simple_sdf/.gitignore @@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/simple_sdf/README.md b/simple_sdf/README.md new file mode 100644 index 0000000..564c2cd --- /dev/null +++ b/simple_sdf/README.md @@ -0,0 +1,9 @@ +# `simple_sdf` + +A simple [Flutter fragment shaders][] sample project showing how to draw Signed Distance Functions with the FragmentShader API. + +Use `flutter create --no-overwrite .` to initialize the project. + + [Flutter fragment shaders]: https://docs.flutter.dev/development/ui/advanced/shaders + +![Screenshot of the `simple_sdf` app](screenshot.png) diff --git a/simple_sdf/analysis_options.yaml b/simple_sdf/analysis_options.yaml new file mode 100644 index 0000000..5e2133e --- /dev/null +++ b/simple_sdf/analysis_options.yaml @@ -0,0 +1 @@ +include: ../analysis_options.yaml diff --git a/simple_sdf/lib/main.dart b/simple_sdf/lib/main.dart new file mode 100644 index 0000000..3d5ec85 --- /dev/null +++ b/simple_sdf/lib/main.dart @@ -0,0 +1,65 @@ +// 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. + +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; +import 'package:flutter_shaders/flutter_shaders.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Simple SDF Demo', + theme: ThemeData(colorSchemeSeed: Colors.blue), + home: const MyHomePage(), + ); + } +} + +class MyHomePage extends StatelessWidget { + const MyHomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Simple SDF Demo')), + body: ShaderBuilder( + assetKey: 'shaders/SDF.frag', + (context, shader, child) => CustomPaint( + size: MediaQuery.of(context).size, + painter: ShaderPainter(shader: shader), + ), + child: const Center(child: CircularProgressIndicator()), + ), + ); + } +} + +class ShaderPainter extends CustomPainter { + ShaderPainter({required this.shader}) + : _resolution = shader.getUniformVec2('resolution'); + + final ui.FragmentShader shader; + final ui.UniformVec2Slot _resolution; + + @override + void paint(Canvas canvas, Size size) { + _resolution.set(size.width, size.height); + + final paint = Paint()..shader = shader; + canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return false; + } +} diff --git a/simple_sdf/pubspec.yaml b/simple_sdf/pubspec.yaml new file mode 100644 index 0000000..4aebd30 --- /dev/null +++ b/simple_sdf/pubspec.yaml @@ -0,0 +1,23 @@ +name: simple_sdf +description: Using a shader, simply. +publish_to: 'none' +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + flutter: + sdk: flutter + flutter_shaders: ^0.1.0 + +dev_dependencies: + analysis_defaults: + path: ../analysis_defaults + flutter_test: + sdk: flutter +flutter: + uses-material-design: true + shaders: + - shaders/SDF.frag diff --git a/simple_sdf/screenshot.png b/simple_sdf/screenshot.png new file mode 100644 index 0000000..405769e Binary files /dev/null and b/simple_sdf/screenshot.png differ diff --git a/simple_sdf/shaders/SDF.frag b/simple_sdf/shaders/SDF.frag new file mode 100644 index 0000000..e854a22 --- /dev/null +++ b/simple_sdf/shaders/SDF.frag @@ -0,0 +1,66 @@ +// 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. + +#version 460 core + +#include + +precision mediump float; + +uniform vec2 resolution; +out vec4 fragColor; + +vec3 pink = vec3(255, 105, 180) / 255; + +// dot2 and sdHeart from https://iquilezles.org/articles/distfunctions2d/ +// +// The MIT License +// Copyright Β© 2015 Inigo Quilez +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: The above copyright +// notice and this permission notice shall be included in all copies or +// substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", +// WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +// THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// https://www.youtube.com/c/InigoQuilez +// https://iquilezles.org + +float dot2(vec2 v) { return dot(v, v); } +float sdHeart(in vec2 p) { + p.x = abs(p.x); + + if (p.y + p.x > 1.0) + return sqrt(dot2(p - vec2(0.25, 0.75))) - sqrt(2.0) / 4.0; + return sqrt(min(dot2(p - vec2(0.00, 1.00)), + dot2(p - 0.5 * max(p.x + p.y, 0.0)))) * + sign(p.x - p.y); +} + +void main() { + vec2 st = FlutterFragCoord().xy / resolution.xy; + // Remap coordinates. + // Flutter normalized coordinates have range [0,1] but sdHeart expects [-1,1]. + st = (st - vec2(0.5)) * vec2(2.0); + // Center the heart. + // sdHeart is written such that the bottom point is at (0,0) and it's about 1 + // unit tall. + st.y -= 0.5; + // Invert Y coordinate. + st.y *= -1; + + // Calculate the color of this pixel according to the heart SDF, using + // smoothstep to anti-alias the edges. + vec3 color = vec3(0.0); + color = mix(pink, color, smoothstep(0.01, 0.02, sdHeart(st))); + + fragColor = vec4(color, 1); +} diff --git a/simple_sdf/test/widget_test.dart b/simple_sdf/test/widget_test.dart new file mode 100644 index 0000000..50e51b2 --- /dev/null +++ b/simple_sdf/test/widget_test.dart @@ -0,0 +1,13 @@ +// 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. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:simple_sdf/main.dart'; + +void main() { + testWidgets('Smoke test', (tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + }); +} diff --git a/simple_shader/.gitignore b/simple_shader/.gitignore new file mode 100644 index 0000000..24476c5 --- /dev/null +++ b/simple_shader/.gitignore @@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/simple_shader/.metadata b/simple_shader/.metadata new file mode 100644 index 0000000..a7cbe79 --- /dev/null +++ b/simple_shader/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + channel: beta + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + base_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + - platform: android + create_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + base_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + - platform: ios + create_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + base_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + - platform: linux + create_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + base_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + - platform: macos + create_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + base_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + - platform: web + create_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + base_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + - platform: windows + create_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + base_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/simple_shader/README.md b/simple_shader/README.md new file mode 100644 index 0000000..bf957b2 --- /dev/null +++ b/simple_shader/README.md @@ -0,0 +1,7 @@ +# `simple_shader` + +A simple [Flutter fragment shaders][] sample project. + + [Flutter fragment shaders]: https://docs.flutter.dev/development/ui/advanced/shaders + +![Screenshot of the `simple_shader` app](screenshot.png) diff --git a/simple_shader/analysis_options.yaml b/simple_shader/analysis_options.yaml new file mode 100644 index 0000000..5e2133e --- /dev/null +++ b/simple_shader/analysis_options.yaml @@ -0,0 +1 @@ +include: ../analysis_options.yaml diff --git a/simple_shader/android/.gitignore b/simple_shader/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/simple_shader/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/simple_shader/android/app/build.gradle b/simple_shader/android/app/build.gradle new file mode 100644 index 0000000..c63a3a8 --- /dev/null +++ b/simple_shader/android/app/build.gradle @@ -0,0 +1,71 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.simple_shader" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/simple_shader/android/app/src/debug/AndroidManifest.xml b/simple_shader/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..3bb7d4d --- /dev/null +++ b/simple_shader/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/simple_shader/android/app/src/main/AndroidManifest.xml b/simple_shader/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..79b9a19 --- /dev/null +++ b/simple_shader/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/simple_shader/android/app/src/main/kotlin/com/example/simple_shader/MainActivity.kt b/simple_shader/android/app/src/main/kotlin/com/example/simple_shader/MainActivity.kt new file mode 100644 index 0000000..74f99a8 --- /dev/null +++ b/simple_shader/android/app/src/main/kotlin/com/example/simple_shader/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.simple_shader + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/simple_shader/android/app/src/main/res/drawable-v21/launch_background.xml b/simple_shader/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/simple_shader/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/simple_shader/android/app/src/main/res/drawable/launch_background.xml b/simple_shader/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/simple_shader/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/simple_shader/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/simple_shader/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/simple_shader/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/simple_shader/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/simple_shader/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/simple_shader/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/simple_shader/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/simple_shader/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/simple_shader/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/simple_shader/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/simple_shader/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/simple_shader/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/simple_shader/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/simple_shader/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/simple_shader/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/simple_shader/android/app/src/main/res/values-night/styles.xml b/simple_shader/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/simple_shader/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/simple_shader/android/app/src/main/res/values/styles.xml b/simple_shader/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/simple_shader/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/simple_shader/android/app/src/profile/AndroidManifest.xml b/simple_shader/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..3bb7d4d --- /dev/null +++ b/simple_shader/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/simple_shader/android/build.gradle b/simple_shader/android/build.gradle new file mode 100644 index 0000000..f7eb7f6 --- /dev/null +++ b/simple_shader/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.3.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/simple_shader/android/gradle.properties b/simple_shader/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/simple_shader/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/simple_shader/android/gradle/wrapper/gradle-wrapper.properties b/simple_shader/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/simple_shader/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/simple_shader/android/settings.gradle b/simple_shader/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/simple_shader/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/simple_shader/ios/.gitignore b/simple_shader/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/simple_shader/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/simple_shader/ios/Flutter/AppFrameworkInfo.plist b/simple_shader/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/simple_shader/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/simple_shader/ios/Flutter/Debug.xcconfig b/simple_shader/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/simple_shader/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/simple_shader/ios/Flutter/Release.xcconfig b/simple_shader/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/simple_shader/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/simple_shader/ios/Runner.xcodeproj/project.pbxproj b/simple_shader/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..544fe7b --- /dev/null +++ b/simple_shader/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,616 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807E294A63A400263BE5 /* Frameworks */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = TC87DMJLQP; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.simpleShader; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.simpleShader.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.simpleShader.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.simpleShader.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = TC87DMJLQP; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.simpleShader; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = TC87DMJLQP; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.simpleShader; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/simple_shader/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/simple_shader/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/simple_shader/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/simple_shader/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/simple_shader/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/simple_shader/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/simple_shader/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/simple_shader/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/simple_shader/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/simple_shader/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/simple_shader/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..e42adcb --- /dev/null +++ b/simple_shader/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/simple_shader/ios/Runner.xcworkspace/contents.xcworkspacedata b/simple_shader/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/simple_shader/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/simple_shader/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/simple_shader/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/simple_shader/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/simple_shader/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/simple_shader/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/simple_shader/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/simple_shader/ios/Runner/AppDelegate.swift b/simple_shader/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/simple_shader/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/simple_shader/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/simple_shader/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/simple_shader/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/simple_shader/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/simple_shader/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/simple_shader/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/simple_shader/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/simple_shader/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/simple_shader/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/simple_shader/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/simple_shader/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/simple_shader/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/simple_shader/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/simple_shader/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/simple_shader/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/simple_shader/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/simple_shader/ios/Runner/Base.lproj/LaunchScreen.storyboard b/simple_shader/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/simple_shader/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/simple_shader/ios/Runner/Base.lproj/Main.storyboard b/simple_shader/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/simple_shader/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/simple_shader/ios/Runner/Info.plist b/simple_shader/ios/Runner/Info.plist new file mode 100644 index 0000000..29cfd74 --- /dev/null +++ b/simple_shader/ios/Runner/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Simple Shader + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + simple_shader + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/simple_shader/ios/Runner/Runner-Bridging-Header.h b/simple_shader/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/simple_shader/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/simple_shader/ios/RunnerTests/RunnerTests.swift b/simple_shader/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/simple_shader/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/simple_shader/lib/main.dart b/simple_shader/lib/main.dart new file mode 100644 index 0000000..037eb69 --- /dev/null +++ b/simple_shader/lib/main.dart @@ -0,0 +1,65 @@ +// 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. + +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; +import 'package:flutter_shaders/flutter_shaders.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Simple Shader Demo', + theme: ThemeData(colorSchemeSeed: Colors.blue), + home: const MyHomePage(), + ); + } +} + +class MyHomePage extends StatelessWidget { + const MyHomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Simple Shader Demo')), + body: ShaderBuilder( + assetKey: 'shaders/simple.frag', + (context, shader, child) => CustomPaint( + size: MediaQuery.of(context).size, + painter: ShaderPainter(shader: shader), + ), + child: const Center(child: CircularProgressIndicator()), + ), + ); + } +} + +class ShaderPainter extends CustomPainter { + ShaderPainter({required this.shader}) + : _resolution = shader.getUniformVec2('resolution'); + + final ui.FragmentShader shader; + final ui.UniformVec2Slot _resolution; + + @override + void paint(Canvas canvas, Size size) { + _resolution.set(size.width, size.height); + + final paint = Paint()..shader = shader; + canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return false; + } +} diff --git a/simple_shader/linux/.gitignore b/simple_shader/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/simple_shader/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/simple_shader/linux/CMakeLists.txt b/simple_shader/linux/CMakeLists.txt new file mode 100644 index 0000000..8500292 --- /dev/null +++ b/simple_shader/linux/CMakeLists.txt @@ -0,0 +1,139 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "simple_shader") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.simple_shader") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/simple_shader/linux/flutter/CMakeLists.txt b/simple_shader/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/simple_shader/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/simple_shader/linux/flutter/generated_plugin_registrant.cc b/simple_shader/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..e71a16d --- /dev/null +++ b/simple_shader/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/simple_shader/linux/flutter/generated_plugin_registrant.h b/simple_shader/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/simple_shader/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/simple_shader/linux/flutter/generated_plugins.cmake b/simple_shader/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..2e1de87 --- /dev/null +++ b/simple_shader/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/simple_shader/linux/main.cc b/simple_shader/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/simple_shader/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/simple_shader/linux/my_application.cc b/simple_shader/linux/my_application.cc new file mode 100644 index 0000000..4086d6e --- /dev/null +++ b/simple_shader/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "simple_shader"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "simple_shader"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/simple_shader/linux/my_application.h b/simple_shader/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/simple_shader/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/simple_shader/macos/.gitignore b/simple_shader/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/simple_shader/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/simple_shader/macos/Flutter/Flutter-Debug.xcconfig b/simple_shader/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/simple_shader/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/simple_shader/macos/Flutter/Flutter-Release.xcconfig b/simple_shader/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/simple_shader/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/simple_shader/macos/Flutter/GeneratedPluginRegistrant.swift b/simple_shader/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..cccf817 --- /dev/null +++ b/simple_shader/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,10 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { +} diff --git a/simple_shader/macos/Runner.xcodeproj/project.pbxproj b/simple_shader/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..e9e25a2 --- /dev/null +++ b/simple_shader/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,695 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* simple_shader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "simple_shader.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* simple_shader.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* simple_shader.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.simpleShader.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/simple_shader.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/simple_shader"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.simpleShader.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/simple_shader.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/simple_shader"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.simpleShader.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/simple_shader.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/simple_shader"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/simple_shader/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/simple_shader/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/simple_shader/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/simple_shader/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/simple_shader/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..192c91f --- /dev/null +++ b/simple_shader/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/simple_shader/macos/Runner.xcworkspace/contents.xcworkspacedata b/simple_shader/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/simple_shader/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/simple_shader/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/simple_shader/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/simple_shader/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/simple_shader/macos/Runner/AppDelegate.swift b/simple_shader/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..d53ef64 --- /dev/null +++ b/simple_shader/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/simple_shader/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/simple_shader/macos/Runner/Base.lproj/MainMenu.xib b/simple_shader/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/simple_shader/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/simple_shader/macos/Runner/Configs/AppInfo.xcconfig b/simple_shader/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..aac4235 --- /dev/null +++ b/simple_shader/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = simple_shader + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.simpleShader + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright Β© 2023 com.example. All rights reserved. diff --git a/simple_shader/macos/Runner/Configs/Debug.xcconfig b/simple_shader/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/simple_shader/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/simple_shader/macos/Runner/Configs/Release.xcconfig b/simple_shader/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/simple_shader/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/simple_shader/macos/Runner/Configs/Warnings.xcconfig b/simple_shader/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/simple_shader/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/simple_shader/macos/Runner/DebugProfile.entitlements b/simple_shader/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/simple_shader/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/simple_shader/macos/Runner/Info.plist b/simple_shader/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/simple_shader/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/simple_shader/macos/Runner/MainFlutterWindow.swift b/simple_shader/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..2722837 --- /dev/null +++ b/simple_shader/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/simple_shader/macos/Runner/Release.entitlements b/simple_shader/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/simple_shader/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/simple_shader/macos/RunnerTests/RunnerTests.swift b/simple_shader/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..5418c9f --- /dev/null +++ b/simple_shader/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import FlutterMacOS +import Cocoa +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/simple_shader/pubspec.yaml b/simple_shader/pubspec.yaml new file mode 100644 index 0000000..cfca8f5 --- /dev/null +++ b/simple_shader/pubspec.yaml @@ -0,0 +1,23 @@ +name: simple_shader +description: Using a shader, simply. +publish_to: 'none' +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + flutter: + sdk: flutter + flutter_shaders: ^0.1.0 + +dev_dependencies: + analysis_defaults: + path: ../analysis_defaults + flutter_test: + sdk: flutter +flutter: + uses-material-design: true + shaders: + - shaders/simple.frag diff --git a/simple_shader/shaders/simple.frag b/simple_shader/shaders/simple.frag new file mode 100644 index 0000000..8ca802f --- /dev/null +++ b/simple_shader/shaders/simple.frag @@ -0,0 +1,29 @@ +// 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. + +#version 460 core + +#include + +precision mediump float; + +uniform vec2 resolution; +out vec4 fragColor; + +vec3 flutterBlue = vec3(5, 83, 177) / 255; +vec3 flutterNavy = vec3(4, 43, 89) / 255; +vec3 flutterSky = vec3(2, 125, 253) / 255; + +void main() { + vec2 st = FlutterFragCoord().xy / resolution.xy; + + vec3 color = vec3(0.0); + vec3 percent = vec3((st.x + st.y) / 2); + + color = + mix(mix(flutterSky, flutterBlue, percent * 2), + mix(flutterBlue, flutterNavy, percent * 2 - 1), step(0.5, percent)); + + fragColor = vec4(color, 1); +} diff --git a/simple_shader/test/widget_test.dart b/simple_shader/test/widget_test.dart new file mode 100644 index 0000000..8b876a5 --- /dev/null +++ b/simple_shader/test/widget_test.dart @@ -0,0 +1,13 @@ +// 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. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:simple_shader/main.dart'; + +void main() { + testWidgets('Smoke test', (tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + }); +} diff --git a/simple_shader/web/favicon.png b/simple_shader/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/simple_shader/web/favicon.png differ diff --git a/simple_shader/web/icons/Icon-192.png b/simple_shader/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/simple_shader/web/icons/Icon-192.png differ diff --git a/simple_shader/web/icons/Icon-512.png b/simple_shader/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/simple_shader/web/icons/Icon-512.png differ diff --git a/simple_shader/web/icons/Icon-maskable-192.png b/simple_shader/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/simple_shader/web/icons/Icon-maskable-192.png differ diff --git a/simple_shader/web/icons/Icon-maskable-512.png b/simple_shader/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/simple_shader/web/icons/Icon-maskable-512.png differ diff --git a/simple_shader/web/index.html b/simple_shader/web/index.html new file mode 100644 index 0000000..9b54304 --- /dev/null +++ b/simple_shader/web/index.html @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + simple_shader + + + + + + + diff --git a/simple_shader/web/manifest.json b/simple_shader/web/manifest.json new file mode 100644 index 0000000..e60e0f1 --- /dev/null +++ b/simple_shader/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "simple_shader", + "short_name": "simple_shader", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/simple_shader/windows/.gitignore b/simple_shader/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/simple_shader/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/simple_shader/windows/CMakeLists.txt b/simple_shader/windows/CMakeLists.txt new file mode 100644 index 0000000..d7aa30f --- /dev/null +++ b/simple_shader/windows/CMakeLists.txt @@ -0,0 +1,102 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(simple_shader LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "simple_shader") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/simple_shader/windows/flutter/CMakeLists.txt b/simple_shader/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..930d207 --- /dev/null +++ b/simple_shader/windows/flutter/CMakeLists.txt @@ -0,0 +1,104 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/simple_shader/windows/flutter/generated_plugin_registrant.cc b/simple_shader/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..8b6d468 --- /dev/null +++ b/simple_shader/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/simple_shader/windows/flutter/generated_plugin_registrant.h b/simple_shader/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/simple_shader/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/simple_shader/windows/flutter/generated_plugins.cmake b/simple_shader/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..b93c4c3 --- /dev/null +++ b/simple_shader/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/simple_shader/windows/runner/CMakeLists.txt b/simple_shader/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/simple_shader/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/simple_shader/windows/runner/Runner.rc b/simple_shader/windows/runner/Runner.rc new file mode 100644 index 0000000..ba580bd --- /dev/null +++ b/simple_shader/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "simple_shader" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "simple_shader" "\0" + VALUE "LegalCopyright", "Copyright (C) 2023 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "simple_shader.exe" "\0" + VALUE "ProductName", "simple_shader" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/simple_shader/windows/runner/flutter_window.cpp b/simple_shader/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..b25e363 --- /dev/null +++ b/simple_shader/windows/runner/flutter_window.cpp @@ -0,0 +1,66 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/simple_shader/windows/runner/flutter_window.h b/simple_shader/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/simple_shader/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/simple_shader/windows/runner/main.cpp b/simple_shader/windows/runner/main.cpp new file mode 100644 index 0000000..fa8f25a --- /dev/null +++ b/simple_shader/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"simple_shader", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/simple_shader/windows/runner/resource.h b/simple_shader/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/simple_shader/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/simple_shader/windows/runner/resources/app_icon.ico b/simple_shader/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/simple_shader/windows/runner/resources/app_icon.ico differ diff --git a/simple_shader/windows/runner/runner.exe.manifest b/simple_shader/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..a42ea76 --- /dev/null +++ b/simple_shader/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/simple_shader/windows/runner/utils.cpp b/simple_shader/windows/runner/utils.cpp new file mode 100644 index 0000000..b2b0873 --- /dev/null +++ b/simple_shader/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/simple_shader/windows/runner/utils.h b/simple_shader/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/simple_shader/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/simple_shader/windows/runner/win32_window.cpp b/simple_shader/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/simple_shader/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/simple_shader/windows/runner/win32_window.h b/simple_shader/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/simple_shader/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/testing_app/.gitignore b/testing_app/.gitignore new file mode 100644 index 0000000..363e23d --- /dev/null +++ b/testing_app/.gitignore @@ -0,0 +1,83 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# 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 +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# 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 diff --git a/testing_app/.metadata b/testing_app/.metadata new file mode 100644 index 0000000..a7cbe79 --- /dev/null +++ b/testing_app/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + channel: beta + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + base_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + - platform: android + create_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + base_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + - platform: ios + create_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + base_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + - platform: linux + create_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + base_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + - platform: macos + create_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + base_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + - platform: web + create_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + base_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + - platform: windows + create_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + base_revision: 0df8557c56a182d31fa024eeb08c428ae52edf7f + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/testing_app/README.md b/testing_app/README.md new file mode 100644 index 0000000..8366759 --- /dev/null +++ b/testing_app/README.md @@ -0,0 +1,64 @@ +# testing_app + +A Sample app that shows different types of testing in Flutter. + +This particular sample uses the [Provider][] package but any other state management approach +would do. + +[provider]: https://pub.dev/packages/provider + +## Goals for this sample + +Show how to perform: + +- Widget Testing, +- Integration Testing, +- Performance Testing, and +- State Management Testing using the [Provider][] package. + +## How to run tests +- Navigate to the project's root folder using command line and follow the instructions below. + +### To run tests using only the Flutter SDK: +The Flutter SDK can run unit tests and widget tests in a virtual machine, without the need of a physical device or emulator. +- To run all the test files in the `test/` directory in one go, run `flutter test`. +- To run a particular test file, run `flutter test test/` + +### To run tests on a physical device/emulator: +- Widget Tests: + - Run `flutter run test/` +- Integration Tests: + - Run `flutter test integration_test` to run all the integration tests with a single command. + - Alternatively, you can run `flutter drive --driver=integration_test/driver.dart --target=integration_test/app_test.dart` to run them separately. You can also provide custom driver files with this command. +- Performance Tests: + - Run `flutter drive --driver=integration_test/perf_driver.dart --target=integration_test/perf_test.dart --profile --trace-startup` + - Using a physical device and running performance tests in profile mode is recommended. + - The `--trace-startup` option is used to avoid flushing older timeline events when the timeline gets long. +- State Management Tests: + - For testing state using Flutter Integration Tests + - Run `flutter drive --driver=integration_test/driver.dart --target=integration_test/state_mgmt_test.dart` + +### To generate test coverage report: +- Install the `lcov` tool: + - For MacOS, run `brew install lcov` + - For Linux, run `sudo apt install lcov` +- Run tests with coverage: + - `flutter test --coverage` +- Convert `lcov.info` into readable html: + - Run `genhtml coverage/lcov.info -o coverage/index` +- Open `coverage/index/index.html` in your preferred browser. + +### CI/CD +- Refer [.github](../.github) and the [tool](../tool) directory to see how to test Flutter projects using GitHub Actions. + +Note that tools like GitHub Actions can't run tests on a physical device, which is required to run integration tests. Instead, you can use [Firebase Test Lab](https://firebase.google.com/docs/test-lab), [Codemagic](https://docs.codemagic.io/testing/aws/) or any platform of your choice to do that. + +## Questions/issues + +If you have a general question about testing in Flutter, the best places to go are: + +- [Flutter documentation](https://flutter.dev/) +- [StackOverflow](https://stackoverflow.com/questions/tagged/flutter) + +If you run into an issue with the sample itself, please +[file an issue](https://github.com/flutter/samples/issues). diff --git a/testing_app/analysis_options.yaml b/testing_app/analysis_options.yaml new file mode 100644 index 0000000..5e2133e --- /dev/null +++ b/testing_app/analysis_options.yaml @@ -0,0 +1 @@ +include: ../analysis_options.yaml diff --git a/testing_app/android/.gitignore b/testing_app/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/testing_app/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/testing_app/android/app/build.gradle b/testing_app/android/app/build.gradle new file mode 100644 index 0000000..a453bc1 --- /dev/null +++ b/testing_app/android/app/build.gradle @@ -0,0 +1,71 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "dev.flutter.testing_app" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/testing_app/android/app/src/debug/AndroidManifest.xml b/testing_app/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..fcc8f19 --- /dev/null +++ b/testing_app/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/testing_app/android/app/src/main/AndroidManifest.xml b/testing_app/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0ebc358 --- /dev/null +++ b/testing_app/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/testing_app/android/app/src/main/kotlin/dev/flutter/testing_app/MainActivity.kt b/testing_app/android/app/src/main/kotlin/dev/flutter/testing_app/MainActivity.kt new file mode 100644 index 0000000..e3725b5 --- /dev/null +++ b/testing_app/android/app/src/main/kotlin/dev/flutter/testing_app/MainActivity.kt @@ -0,0 +1,6 @@ +package dev.flutter.testing_app + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/testing_app/android/app/src/main/res/drawable-v21/launch_background.xml b/testing_app/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/testing_app/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/testing_app/android/app/src/main/res/drawable/launch_background.xml b/testing_app/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/testing_app/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/testing_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/testing_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/testing_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/testing_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/testing_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/testing_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/testing_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/testing_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/testing_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/testing_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/testing_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/testing_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/testing_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/testing_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/testing_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/testing_app/android/app/src/main/res/values-night/styles.xml b/testing_app/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/testing_app/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/testing_app/android/app/src/main/res/values/styles.xml b/testing_app/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/testing_app/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/testing_app/android/app/src/profile/AndroidManifest.xml b/testing_app/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..fcc8f19 --- /dev/null +++ b/testing_app/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/testing_app/android/build.gradle b/testing_app/android/build.gradle new file mode 100644 index 0000000..e50c3a0 --- /dev/null +++ b/testing_app/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.3.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/testing_app/android/gradle.properties b/testing_app/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/testing_app/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/testing_app/android/gradle/wrapper/gradle-wrapper.properties b/testing_app/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/testing_app/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/testing_app/android/settings.gradle b/testing_app/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/testing_app/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/testing_app/integration_test/app_test.dart b/testing_app/integration_test/app_test.dart new file mode 100644 index 0000000..625b055 --- /dev/null +++ b/testing_app/integration_test/app_test.dart @@ -0,0 +1,99 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:testing_app/main.dart'; + +void main() { + group('Testing App Driver Tests', () { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('Finding an item in the list', (tester) async { + await tester.pumpWidget(const TestingApp()); + + // Create variables for finders that are used multiple times. + final itemFinder = find.byKey(const ValueKey('text_25')); + + // Scroll until the item to be found appears. + await tester.scrollUntilVisible(itemFinder, 500.0); + + // Check if the item contains the correct text. + expect(tester.widget(itemFinder).data, 'Item 25'); + }); + + testWidgets('Testing IconButtons', (tester) async { + await tester.pumpWidget(const TestingApp()); + + // Create a finder for the icon. + final iconFinder = find.byKey(const ValueKey('icon_0')); + + // Tap on the icon. + await tester.tap(iconFinder); + + // Wait 1 second for the SnackBar to be displayed + await tester.pumpAndSettle(const Duration(seconds: 1)); + + // Verify if appropriate message appears. + expect(find.text('Added to favorites.'), findsOneWidget); + + // Tap on the icon again. + await tester.tap(iconFinder); + + // Wait 1 second for the SnackBar to be displayed + await tester.pumpAndSettle(const Duration(seconds: 1)); + + // Verify if appropriate message appears. + expect(find.text('Removed from favorites.'), findsOneWidget); + }); + + testWidgets('Verifying whether item gets added to favorites', ( + tester, + ) async { + await tester.pumpWidget(const TestingApp()); + + // Add item to favorites. + await tester.tap(find.byKey(const ValueKey('icon_5'))); + await tester.pumpAndSettle(); + + // Tap on the favorites button on the AppBar. + // The Favorites List should appear. + await tester.tap(find.text('Favorites')); + await tester.pumpAndSettle(); + + // Check if the added item has appeared in the list. + expect( + tester + .widget(find.byKey(const ValueKey('favorites_text_5'))) + .data, + equals('Item 5'), + ); + }); + + testWidgets('Testing remove button', (tester) async { + await tester.pumpWidget(const TestingApp()); + + // Add item to favorites. + await tester.tap(find.byKey(const ValueKey('icon_5'))); + await tester.pumpAndSettle(); + + // Navigate to Favorites screen. + await tester.tap(find.text('Favorites')); + await tester.pumpAndSettle(); + + // Tap on the remove icon. + await tester.tap(find.byKey(const ValueKey('remove_icon_5'))); + + // Wait 1 second for the SnackBar to be displayed + await tester.pumpAndSettle(const Duration(seconds: 1)); + + // Verify if it disappears. + expect(find.text('Item 5'), findsNothing); + + // Verify if appropriate message appears. + expect(find.text('Removed from favorites.'), findsOneWidget); + }); + }); +} diff --git a/testing_app/integration_test/driver.dart b/testing_app/integration_test/driver.dart new file mode 100644 index 0000000..3a11b86 --- /dev/null +++ b/testing_app/integration_test/driver.dart @@ -0,0 +1,7 @@ +// Copyright 2020 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. + +import 'package:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver(); diff --git a/testing_app/integration_test/perf_driver.dart b/testing_app/integration_test/perf_driver.dart new file mode 100644 index 0000000..6d2ee88 --- /dev/null +++ b/testing_app/integration_test/perf_driver.dart @@ -0,0 +1,25 @@ +// Copyright 2021 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. + +import 'dart:developer'; + +import 'package:integration_test/integration_test_driver.dart'; + +Future main() { + return integrationDriver( + responseDataCallback: (data) async { + // If the tests reported any data, save it to the disk. + if (data != null) { + for (var entry in data.entries) { + log('Writing ${entry.key} to the disk.'); + // Default storage destination is the 'build' directory. + await writeResponseData( + entry.value as Map, + testOutputFilename: entry.key, + ); + } + } + }, + ); +} diff --git a/testing_app/integration_test/perf_test.dart b/testing_app/integration_test/perf_test.dart new file mode 100644 index 0000000..4536de1 --- /dev/null +++ b/testing_app/integration_test/perf_test.dart @@ -0,0 +1,96 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:testing_app/main.dart'; + +void main() { + group('Testing App Performance Tests', () { + final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + // The fullyLive frame policy simulates + // the way Flutter responds to animations. + binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; + + testWidgets('Scrolling test', (tester) async { + await tester.pumpWidget(const TestingApp()); + + // Create variables for finders that are used multiple times. + final listFinder = find.byType(ListView); + final scroller = tester.widget(listFinder).controller; + + // Record the performance summary as the app scrolls through + // the list of items. + await binding.watchPerformance( + () async { + // Quickly scroll all the way down. + await scroller!.animateTo( + 7000, + duration: const Duration(seconds: 1), + curve: Curves.linear, + ); + await tester.pumpAndSettle(); + + // Quickly scroll back up all the way. + await scroller.animateTo( + -7000, + duration: const Duration(seconds: 1), + curve: Curves.linear, + ); + await tester.pumpAndSettle(); + }, + // Send the performance summary to the driver. + reportKey: 'scrolling_summary', + ); + }); + + testWidgets('Favorites operations test', (tester) async { + await tester.pumpWidget(const TestingApp()); + + // Record the performance summary as operations are performed + // on the favorites list. + await binding.watchPerformance( + () async { + // Create a list of icon keys. + final iconKeys = ['icon_0', 'icon_1', 'icon_2']; + + // Add first three items to favorites. + for (var icon in iconKeys) { + // Tap onto the icon. + await tester.tap(find.byKey(ValueKey(icon))); + await tester.pumpAndSettle(const Duration(seconds: 1)); + + // Verify if appropriate message appears. + expect(find.text('Added to favorites.'), findsOneWidget); + } + + // Tap onto the favorites button on the AppBar. + // The Favorites List should appear. + await tester.tap(find.text('Favorites')); + await tester.pumpAndSettle(); + + final removeIconKeys = [ + 'remove_icon_0', + 'remove_icon_1', + 'remove_icon_2', + ]; + + // Remove all the items from favorites. + for (final iconKey in removeIconKeys) { + // Tap onto the remove icon. + await tester.tap(find.byKey(ValueKey(iconKey))); + await tester.pumpAndSettle(const Duration(seconds: 1)); + + // Verify if appropriate message appears. + expect(find.text('Removed from favorites.'), findsOneWidget); + } + }, + // Send the performance summary to the driver. + reportKey: 'favorites_operations_summary', + ); + }); + }); +} diff --git a/testing_app/integration_test/state_mgmt_test.dart b/testing_app/integration_test/state_mgmt_test.dart new file mode 100644 index 0000000..6dabad4 --- /dev/null +++ b/testing_app/integration_test/state_mgmt_test.dart @@ -0,0 +1,48 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:provider/provider.dart'; +import 'package:testing_app/models/favorites.dart'; +import 'package:testing_app/screens/favorites.dart'; + +late Favorites favoritesList; + +Widget createFavoritesScreen() => ChangeNotifierProvider( + create: (context) { + favoritesList = Favorites(); + return favoritesList; + }, + child: const MaterialApp(home: FavoritesPage()), +); + +void main() { + group('Testing App State Management Tests', () { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('Verifying add method', (tester) async { + await tester.pumpWidget(createFavoritesScreen()); + + // Add an item to the list. + favoritesList.add(30); + await tester.pumpAndSettle(); + + // Check if the new item appears in the list. + expect(find.text('Item 30'), findsOneWidget); + }); + + testWidgets('Verifying remove method', (tester) async { + await tester.pumpWidget(createFavoritesScreen()); + + // Remove an item from the list. + favoritesList.remove(30); + await tester.pumpAndSettle(); + + // Verify if it disappears. + expect(find.text('Item 30'), findsNothing); + }); + }); +} diff --git a/testing_app/ios/.gitignore b/testing_app/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/testing_app/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/testing_app/ios/Flutter/AppFrameworkInfo.plist b/testing_app/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/testing_app/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/testing_app/ios/Flutter/Debug.xcconfig b/testing_app/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/testing_app/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/testing_app/ios/Flutter/Release.xcconfig b/testing_app/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/testing_app/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/testing_app/ios/Podfile b/testing_app/ios/Podfile new file mode 100644 index 0000000..fdcc671 --- /dev/null +++ b/testing_app/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '11.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/testing_app/ios/Runner.xcodeproj/project.pbxproj b/testing_app/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..05fdec6 --- /dev/null +++ b/testing_app/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,616 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807E294A63A400263BE5 /* Frameworks */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = TC87DMJLQP; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.testingApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.testingApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.testingApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.testingApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = TC87DMJLQP; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.testingApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = TC87DMJLQP; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.testingApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/testing_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/testing_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/testing_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/testing_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/testing_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/testing_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/testing_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/testing_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/testing_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/testing_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/testing_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..e42adcb --- /dev/null +++ b/testing_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/testing_app/ios/Runner.xcworkspace/contents.xcworkspacedata b/testing_app/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/testing_app/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/testing_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/testing_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/testing_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/testing_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/testing_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/testing_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/testing_app/ios/Runner/AppDelegate.swift b/testing_app/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..c30b367 --- /dev/null +++ b/testing_app/ios/Runner/AppDelegate.swift @@ -0,0 +1,16 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } +} diff --git a/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/testing_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/testing_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/testing_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/testing_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/testing_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/testing_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/testing_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/testing_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/testing_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/testing_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/testing_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/testing_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/testing_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/testing_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/testing_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/testing_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/testing_app/ios/Runner/Base.lproj/LaunchScreen.storyboard b/testing_app/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/testing_app/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/testing_app/ios/Runner/Base.lproj/Main.storyboard b/testing_app/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/testing_app/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/testing_app/ios/Runner/Info.plist b/testing_app/ios/Runner/Info.plist new file mode 100644 index 0000000..4347345 --- /dev/null +++ b/testing_app/ios/Runner/Info.plist @@ -0,0 +1,72 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Testing App + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + testing_app + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + FlutterSceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/testing_app/ios/Runner/Runner-Bridging-Header.h b/testing_app/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/testing_app/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/testing_app/ios/RunnerTests/RunnerTests.swift b/testing_app/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/testing_app/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/testing_app/lib/main.dart b/testing_app/lib/main.dart new file mode 100644 index 0000000..ca673a6 --- /dev/null +++ b/testing_app/lib/main.dart @@ -0,0 +1,50 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:provider/provider.dart'; +import 'package:testing_app/models/favorites.dart'; +import 'package:testing_app/screens/favorites.dart'; +import 'package:testing_app/screens/home.dart'; + +void main() { + runApp(const TestingApp()); +} + +GoRouter router() { + return GoRouter( + routes: [ + GoRoute( + path: HomePage.routeName, + builder: (context, state) => const HomePage(), + routes: [ + GoRoute( + path: FavoritesPage.routeName, + builder: (context, state) => const FavoritesPage(), + ), + ], + ), + ], + ); +} + +class TestingApp extends StatelessWidget { + const TestingApp({super.key}); + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider( + create: (context) => Favorites(), + child: MaterialApp.router( + title: 'Testing Sample', + theme: ThemeData( + colorSchemeSeed: Colors.blue, + visualDensity: VisualDensity.adaptivePlatformDensity, + ), + routerConfig: router(), + ), + ); + } +} diff --git a/testing_app/lib/models/favorites.dart b/testing_app/lib/models/favorites.dart new file mode 100644 index 0000000..83c0804 --- /dev/null +++ b/testing_app/lib/models/favorites.dart @@ -0,0 +1,22 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; + +/// The [Favorites] class holds a list of favorite items saved by the user. +class Favorites extends ChangeNotifier { + final List _favoriteItems = []; + + List get items => _favoriteItems; + + void add(int itemNo) { + _favoriteItems.add(itemNo); + notifyListeners(); + } + + void remove(int itemNo) { + _favoriteItems.remove(itemNo); + notifyListeners(); + } +} diff --git a/testing_app/lib/screens/favorites.dart b/testing_app/lib/screens/favorites.dart new file mode 100644 index 0000000..7db6a7c --- /dev/null +++ b/testing_app/lib/screens/favorites.dart @@ -0,0 +1,63 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:testing_app/models/favorites.dart'; + +class FavoritesPage extends StatelessWidget { + static const routeName = 'favorites_page'; + static const fullPath = '/$routeName'; + + const FavoritesPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Favorites')), + body: Consumer( + builder: (context, value, child) => value.items.isNotEmpty + ? ListView.builder( + itemCount: value.items.length, + padding: const EdgeInsets.symmetric(vertical: 16), + itemBuilder: (context, index) => + FavoriteItemTile(value.items[index]), + ) + : const Center(child: Text('No favorites added.')), + ), + ); + } +} + +class FavoriteItemTile extends StatelessWidget { + final int itemNo; + + const FavoriteItemTile(this.itemNo, {super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: ListTile( + leading: CircleAvatar( + backgroundColor: Colors.primaries[itemNo % Colors.primaries.length], + ), + title: Text('Item $itemNo', key: Key('favorites_text_$itemNo')), + trailing: IconButton( + key: Key('remove_icon_$itemNo'), + icon: const Icon(Icons.close), + onPressed: () { + context.read().remove(itemNo); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Removed from favorites.'), + duration: Duration(seconds: 1), + ), + ); + }, + ), + ), + ); + } +} diff --git a/testing_app/lib/screens/home.dart b/testing_app/lib/screens/home.dart new file mode 100644 index 0000000..c013a3b --- /dev/null +++ b/testing_app/lib/screens/home.dart @@ -0,0 +1,81 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:provider/provider.dart'; +import 'package:testing_app/models/favorites.dart'; +import 'package:testing_app/screens/favorites.dart'; + +class HomePage extends StatelessWidget { + static const routeName = '/'; + + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Testing Sample'), + actions: [ + TextButton.icon( + onPressed: () { + context.go(FavoritesPage.fullPath); + }, + icon: const Icon(Icons.favorite_border), + label: const Text('Favorites'), + ), + ], + ), + body: ListView.builder( + itemCount: 100, + controller: ScrollController(), + padding: const EdgeInsets.symmetric(vertical: 16), + itemBuilder: (context, index) => ItemTile(index), + ), + ); + } +} + +class ItemTile extends StatelessWidget { + final int itemNo; + + const ItemTile(this.itemNo, {super.key}); + + @override + Widget build(BuildContext context) { + final favoritesList = context.watch(); + + return Padding( + padding: const EdgeInsets.all(8.0), + child: ListTile( + leading: CircleAvatar( + backgroundColor: Colors.primaries[itemNo % Colors.primaries.length], + ), + title: Text('Item $itemNo', key: Key('text_$itemNo')), + trailing: IconButton( + key: Key('icon_$itemNo'), + icon: favoritesList.items.contains(itemNo) + ? const Icon(Icons.favorite) + : const Icon(Icons.favorite_border), + onPressed: () { + !favoritesList.items.contains(itemNo) + ? favoritesList.add(itemNo) + : favoritesList.remove(itemNo); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + favoritesList.items.contains(itemNo) + ? 'Added to favorites.' + : 'Removed from favorites.', + ), + duration: const Duration(seconds: 1), + ), + ); + }, + ), + ), + ); + } +} diff --git a/testing_app/linux/.gitignore b/testing_app/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/testing_app/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/testing_app/linux/CMakeLists.txt b/testing_app/linux/CMakeLists.txt new file mode 100644 index 0000000..ae86125 --- /dev/null +++ b/testing_app/linux/CMakeLists.txt @@ -0,0 +1,139 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "testing_app") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "dev.flutter.testing_app") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/testing_app/linux/flutter/CMakeLists.txt b/testing_app/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/testing_app/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/testing_app/linux/flutter/generated_plugin_registrant.cc b/testing_app/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..e71a16d --- /dev/null +++ b/testing_app/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/testing_app/linux/flutter/generated_plugin_registrant.h b/testing_app/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/testing_app/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/testing_app/linux/flutter/generated_plugins.cmake b/testing_app/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..2e1de87 --- /dev/null +++ b/testing_app/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/testing_app/linux/main.cc b/testing_app/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/testing_app/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/testing_app/linux/my_application.cc b/testing_app/linux/my_application.cc new file mode 100644 index 0000000..2428e22 --- /dev/null +++ b/testing_app/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "testing_app"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "testing_app"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/testing_app/linux/my_application.h b/testing_app/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/testing_app/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/testing_app/macos/.gitignore b/testing_app/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/testing_app/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/testing_app/macos/Flutter/Flutter-Debug.xcconfig b/testing_app/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/testing_app/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/testing_app/macos/Flutter/Flutter-Release.xcconfig b/testing_app/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/testing_app/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/testing_app/macos/Flutter/GeneratedPluginRegistrant.swift b/testing_app/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..cccf817 --- /dev/null +++ b/testing_app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,10 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { +} diff --git a/testing_app/macos/Podfile b/testing_app/macos/Podfile new file mode 100644 index 0000000..c795730 --- /dev/null +++ b/testing_app/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/testing_app/macos/Runner.xcodeproj/project.pbxproj b/testing_app/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..14f7dff --- /dev/null +++ b/testing_app/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,695 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* testing_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "testing_app.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* testing_app.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* testing_app.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.testingApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/testing_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/testing_app"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.testingApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/testing_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/testing_app"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.testingApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/testing_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/testing_app"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/testing_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/testing_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/testing_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/testing_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/testing_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..0152693 --- /dev/null +++ b/testing_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/testing_app/macos/Runner.xcworkspace/contents.xcworkspacedata b/testing_app/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/testing_app/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/testing_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/testing_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/testing_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/testing_app/macos/Runner/AppDelegate.swift b/testing_app/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..d53ef64 --- /dev/null +++ b/testing_app/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/testing_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/testing_app/macos/Runner/Base.lproj/MainMenu.xib b/testing_app/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/testing_app/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/testing_app/macos/Runner/Configs/AppInfo.xcconfig b/testing_app/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..0470555 --- /dev/null +++ b/testing_app/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = testing_app + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.testingApp + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright Β© 2023 dev.flutter. All rights reserved. diff --git a/testing_app/macos/Runner/Configs/Debug.xcconfig b/testing_app/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/testing_app/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/testing_app/macos/Runner/Configs/Release.xcconfig b/testing_app/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/testing_app/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/testing_app/macos/Runner/Configs/Warnings.xcconfig b/testing_app/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/testing_app/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/testing_app/macos/Runner/DebugProfile.entitlements b/testing_app/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/testing_app/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/testing_app/macos/Runner/Info.plist b/testing_app/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/testing_app/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/testing_app/macos/Runner/MainFlutterWindow.swift b/testing_app/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..2722837 --- /dev/null +++ b/testing_app/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/testing_app/macos/Runner/Release.entitlements b/testing_app/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/testing_app/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/testing_app/macos/RunnerTests/RunnerTests.swift b/testing_app/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..5418c9f --- /dev/null +++ b/testing_app/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import FlutterMacOS +import Cocoa +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/testing_app/pubspec.yaml b/testing_app/pubspec.yaml new file mode 100644 index 0000000..3d0b135 --- /dev/null +++ b/testing_app/pubspec.yaml @@ -0,0 +1,26 @@ +name: testing_app +description: A sample that shows testing in Flutter. +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^1.0.3 + provider: ^6.0.2 + go_router: ^16.0.0 + +dev_dependencies: + analysis_defaults: + path: ../analysis_defaults + integration_test: + sdk: flutter + flutter_test: + sdk: flutter + test: ^1.16.8 + +flutter: + uses-material-design: true diff --git a/testing_app/test/favorites_test.dart b/testing_app/test/favorites_test.dart new file mode 100644 index 0000000..c1344cb --- /dev/null +++ b/testing_app/test/favorites_test.dart @@ -0,0 +1,71 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:testing_app/models/favorites.dart'; +import 'package:testing_app/screens/favorites.dart'; + +late Favorites favoritesList; + +Widget createFavoritesScreen() => ChangeNotifierProvider( + create: (context) { + favoritesList = Favorites(); + return favoritesList; + }, + child: const MaterialApp(home: FavoritesPage()), +); + +void addItems() { + for (var i = 0; i < 5; i++) { + favoritesList.add(i); + } +} + +void main() { + group('Favorites Page Widget Tests', () { + testWidgets('Test if Placeholder shows in case of empty list', ( + tester, + ) async { + await tester.pumpWidget(createFavoritesScreen()); + + // Verify if the placeholder text shows up. + expect(find.text('No favorites added.'), findsOneWidget); + }); + + testWidgets('Test if ListView shows up', (tester) async { + await tester.pumpWidget(createFavoritesScreen()); + + addItems(); + await tester.pumpAndSettle(); + + // Verify if ListView shows up. + expect(find.byType(ListView), findsOneWidget); + }); + + testWidgets('Testing Remove Button', (tester) async { + await tester.pumpWidget(createFavoritesScreen()); + + addItems(); + await tester.pumpAndSettle(); + + // Get the total number of items available. + final totalItems = tester.widgetList(find.byIcon(Icons.close)).length; + + // Remove one item. + await tester.tap(find.byIcon(Icons.close).first); + await tester.pumpAndSettle(); + + // Check if removed properly. + expect( + tester.widgetList(find.byIcon(Icons.close)).length, + lessThan(totalItems), + ); + + // Verify if the appropriate message is shown. + expect(find.text('Removed from favorites.'), findsOneWidget); + }); + }); +} diff --git a/testing_app/test/home_test.dart b/testing_app/test/home_test.dart new file mode 100644 index 0000000..a73f0c5 --- /dev/null +++ b/testing_app/test/home_test.dart @@ -0,0 +1,82 @@ +// Copyright 2020 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. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:testing_app/main.dart'; +import 'package:testing_app/models/favorites.dart'; + +Widget createHomeScreen() => ChangeNotifierProvider( + create: (context) => Favorites(), + child: MaterialApp.router(routerConfig: router()), +); + +void main() { + group('Home Page Widget Tests', () { + testWidgets('Testing if ListView shows up', (tester) async { + await tester.pumpWidget(createHomeScreen()); + + // Verify if ListView shows up. + expect(find.byType(ListView), findsOneWidget); + }); + + testWidgets('Testing Scrolling', (tester) async { + await tester.pumpWidget(createHomeScreen()); + + // Check if "Item 0" is present on the screen. + expect(find.text('Item 0'), findsOneWidget); + + // Fling i.e scroll down. + await tester.fling( + find.byType(ListView), + const Offset(0, -200), + 3000, + ); + await tester.pumpAndSettle(); + + // Check if "Item 0" disappeared. + expect(find.text('Item 0'), findsNothing); + }); + + testWidgets('Testing IconButtons', (tester) async { + await tester.pumpWidget(createHomeScreen()); + + // Check if any solid favorite icon is present. + expect(find.byIcon(Icons.favorite), findsNothing); + + // Tap the first item's icon to add it to favorites. + await tester.tap(find.byIcon(Icons.favorite_border).first); + await tester.pumpAndSettle(const Duration(seconds: 1)); + + // Verify if the appropriate message is shown. + expect(find.text('Added to favorites.'), findsOneWidget); + + // Check if any solid favorite icon shows up. + expect(find.byIcon(Icons.favorite), findsWidgets); + + // Tap the first item's icon which has filled icon to + // remove it from favorites. + await tester.tap(find.byIcon(Icons.favorite).first); + await tester.pumpAndSettle(const Duration(seconds: 1)); + + // Verify if the appropriate message is shown. + expect(find.text('Removed from favorites.'), findsOneWidget); + + // Check if the filled icon changes back to bordered icon. + expect(find.byIcon(Icons.favorite), findsNothing); + }); + + testWidgets('Testing Navigation', (tester) async { + await tester.pumpWidget(createHomeScreen()); + + // Tap the Favorites button in the app bar + await tester.tap(find.text('Favorites')); + await tester.pumpAndSettle(); + + // Verify if the empty favorites screen is shown. + expect(find.text('No favorites added.'), findsOneWidget); + }); + }); +} diff --git a/testing_app/test/models/favorites_test.dart b/testing_app/test/models/favorites_test.dart new file mode 100644 index 0000000..7d15ebc --- /dev/null +++ b/testing_app/test/models/favorites_test.dart @@ -0,0 +1,39 @@ +// Copyright 2020 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. + +import 'package:test/test.dart'; +import 'package:testing_app/models/favorites.dart'; + +void main() { + group('Testing App Provider Tests', () { + // Create an object of the provider. + var favorites = Favorites(); + + test('A new item should be added', () { + var number = 35; + + // Add the number to the list. + favorites.add(number); + + // Verify if the number was inserted. + expect(favorites.items.contains(number), true); + }); + + test('An item should be removed', () { + var number = 45; + + // Add the number to the list. + favorites.add(number); + + // Verify if the number was inserted. + expect(favorites.items.contains(number), true); + + // Remove the number from the list. + favorites.remove(number); + + // Verify if the number was removed successfully. + expect(favorites.items.contains(number), false); + }); + }); +} diff --git a/testing_app/web/favicon.png b/testing_app/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/testing_app/web/favicon.png differ diff --git a/testing_app/web/icons/Icon-192.png b/testing_app/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/testing_app/web/icons/Icon-192.png differ diff --git a/testing_app/web/icons/Icon-512.png b/testing_app/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/testing_app/web/icons/Icon-512.png differ diff --git a/testing_app/web/icons/Icon-maskable-192.png b/testing_app/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/testing_app/web/icons/Icon-maskable-192.png differ diff --git a/testing_app/web/icons/Icon-maskable-512.png b/testing_app/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/testing_app/web/icons/Icon-maskable-512.png differ diff --git a/testing_app/web/index.html b/testing_app/web/index.html new file mode 100644 index 0000000..6dd3f85 --- /dev/null +++ b/testing_app/web/index.html @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + testing_app + + + + + + + diff --git a/testing_app/web/manifest.json b/testing_app/web/manifest.json new file mode 100644 index 0000000..eb36df4 --- /dev/null +++ b/testing_app/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "testing_app", + "short_name": "testing_app", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/testing_app/windows/.gitignore b/testing_app/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/testing_app/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/testing_app/windows/CMakeLists.txt b/testing_app/windows/CMakeLists.txt new file mode 100644 index 0000000..6d4cf85 --- /dev/null +++ b/testing_app/windows/CMakeLists.txt @@ -0,0 +1,102 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(testing_app LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "testing_app") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/testing_app/windows/flutter/CMakeLists.txt b/testing_app/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..930d207 --- /dev/null +++ b/testing_app/windows/flutter/CMakeLists.txt @@ -0,0 +1,104 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/testing_app/windows/flutter/generated_plugin_registrant.cc b/testing_app/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..8b6d468 --- /dev/null +++ b/testing_app/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/testing_app/windows/flutter/generated_plugin_registrant.h b/testing_app/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/testing_app/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/testing_app/windows/flutter/generated_plugins.cmake b/testing_app/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..b93c4c3 --- /dev/null +++ b/testing_app/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/testing_app/windows/runner/CMakeLists.txt b/testing_app/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/testing_app/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/testing_app/windows/runner/Runner.rc b/testing_app/windows/runner/Runner.rc new file mode 100644 index 0000000..ea8bcf9 --- /dev/null +++ b/testing_app/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "dev.flutter" "\0" + VALUE "FileDescription", "testing_app" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "testing_app" "\0" + VALUE "LegalCopyright", "Copyright (C) 2023 dev.flutter. All rights reserved." "\0" + VALUE "OriginalFilename", "testing_app.exe" "\0" + VALUE "ProductName", "testing_app" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/testing_app/windows/runner/flutter_window.cpp b/testing_app/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..b25e363 --- /dev/null +++ b/testing_app/windows/runner/flutter_window.cpp @@ -0,0 +1,66 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/testing_app/windows/runner/flutter_window.h b/testing_app/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/testing_app/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/testing_app/windows/runner/main.cpp b/testing_app/windows/runner/main.cpp new file mode 100644 index 0000000..5509e7d --- /dev/null +++ b/testing_app/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"testing_app", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/testing_app/windows/runner/resource.h b/testing_app/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/testing_app/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/testing_app/windows/runner/resources/app_icon.ico b/testing_app/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/testing_app/windows/runner/resources/app_icon.ico differ diff --git a/testing_app/windows/runner/runner.exe.manifest b/testing_app/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..a42ea76 --- /dev/null +++ b/testing_app/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/testing_app/windows/runner/utils.cpp b/testing_app/windows/runner/utils.cpp new file mode 100644 index 0000000..b2b0873 --- /dev/null +++ b/testing_app/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/testing_app/windows/runner/utils.h b/testing_app/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/testing_app/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/testing_app/windows/runner/win32_window.cpp b/testing_app/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/testing_app/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/testing_app/windows/runner/win32_window.h b/testing_app/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/testing_app/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/tool/android_ci_script.sh b/tool/android_ci_script.sh new file mode 100755 index 0000000..b2f3fb5 --- /dev/null +++ b/tool/android_ci_script.sh @@ -0,0 +1,88 @@ +#!/bin/bash + +set -e + +flutter doctor -v + +echo "Fetching dependencies and building 'prebuilt_module/flutter_module/'." +pushd add_to_app/prebuilt_module/flutter_module/ +flutter packages get +flutter build aar +popd + +echo "Fetching dependencies for 'plugin/flutter_module_using_plugin'." +pushd add_to_app/plugin/flutter_module_using_plugin +flutter packages get +popd + +echo "Fetching dependencies for 'fullscreen/'." +pushd add_to_app/fullscreen/flutter_module +flutter packages get +popd + +echo "Fetching dependencies for 'multiple_flutters/'." +pushd add_to_app/multiple_flutters/multiple_flutters_module +flutter packages get +popd + +declare -ar ANDROID_PROJECT_NAMES=( + "add_to_app/fullscreen/android_fullscreen" \ + "add_to_app/plugin/android_using_plugin" \ + "add_to_app/multiple_flutters/multiple_flutters_android" \ + "add_to_app/prebuilt_module/android_using_prebuilt_module" \ +) + +for PROJECT_NAME in "${ANDROID_PROJECT_NAMES[@]}" +do + echo "== Testing '${PROJECT_NAME}' on Flutter's stable channel ==" + pushd "${PROJECT_NAME}" + + ./gradlew --stacktrace assembleDebug + ./gradlew --stacktrace assembleRelease + + popd +done + +# If the credentials don't exist, this script isn't being run from within the +# flutter/samples repo. Rather than throw an error, allow the test to pass +# successfully. +if [ ! -f "svc-keyfile.json" ] +then + echo "Keyfile for Firebase Test Lab not found. Skipping integration tests." + echo "-- Success --" + exit 0 +fi + +# At this time, espresso tests only exist for android_fullscreen. These will +# eventually be rolled out to each Android project and included in the loop +# above. +echo "== Espresso testing 'android_fullscreen' on Flutter's ${FLUTTER_VERSION} channel ==" +pushd "add_to_app/fullscreen/android_fullscreen" +./gradlew app:assembleAndroidTest +./gradlew app:assembleDebug -Ptarget=../flutter_module/test_driver/example.dart +gcloud auth activate-service-account --key-file=../../svc-keyfile.json +gcloud --quiet config set project test-lab-project-ccbec +gcloud firebase test android run --type instrumentation \ + --app app/build/outputs/apk/debug/app-debug.apk \ + --test app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk\ + --timeout 5m +popd + +echo "== Run e2e test for testing_app ==" +pushd "testing_app" +readonly APP_DIR=$(pwd) +flutter packages get +flutter build apk +pushd "android" +./gradlew app:assembleAndroidTest +./gradlew app:assembleRelease -Ptarget=${APP_DIR}/test/perf_test_e2e.dart +popd +gcloud auth activate-service-account --key-file=../svc-keyfile.json +gcloud --quiet config set project test-lab-project-ccbec +gcloud firebase test android run --type instrumentation \ + --app build/app/outputs/apk/release/app-release.apk \ + --test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk \ + --timeout 5m +popd + +echo "-- Success --" diff --git a/tool/ci_script.dart b/tool/ci_script.dart new file mode 100644 index 0000000..f1ad812 --- /dev/null +++ b/tool/ci_script.dart @@ -0,0 +1,119 @@ +// ignore_for_file: avoid_print, + +import 'dart:convert'; +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:yaml/yaml.dart'; + +Future main() async { + final rootDir = Directory.current; + final pubspecFile = File(path.join(rootDir.path, 'pubspec.yaml')); + final pubspecContent = await pubspecFile.readAsString(); + final pubspecYaml = loadYaml(pubspecContent); + + // contains full list of samples + final workspace = pubspecYaml['workspace'] as YamlList?; + + if (workspace == null) { + print('No workspace found in pubspec.yaml'); + exit(1); + } + + // CI always skips the samples on this list + final skipCiList = pubspecYaml['skip_ci'] as YamlList?; + + // CI will also skip for samples on specific branches + // This is useful when API changes in the beta branch are causing CI to fail + // and there isn't a solution that works in both beta and main + final channel = await _getFlutterChannel(); + print('Current Flutter channel: $channel'); + + final channelSkipKey = 'skip_ci_$channel'; + final channelSkipList = pubspecYaml[channelSkipKey] as YamlList?; + if (channelSkipList != null) { + print('Applying skips for channel "$channel": $channelSkipList'); + } + + // pub workspace, only run 'get' once + await _runCommand('flutter', ['pub', 'get'], workingDirectory: rootDir.path); + + final packages = workspace + .where((e) { + if (skipCiList != null && skipCiList.contains(e)) return false; + if (channelSkipList != null && channelSkipList.contains(e)) { + return false; + } + return true; + }) + .map((e) => e.toString()) + .toList(); + + for (final package in packages) { + final packagePath = path.join(rootDir.path, package); + print('== Testing \'$package\' =='); + await _runCommand('dart', [ + 'analyze', + '--fatal-infos', + '--fatal-warnings', + ], workingDirectory: packagePath); + + await _runCommand('dart', ['format', '.'], workingDirectory: packagePath); + + if (await Directory(path.join(packagePath, 'test')).exists()) { + final packagePubspecFile = File(path.join(packagePath, 'pubspec.yaml')); + final packagePubspecContent = await packagePubspecFile.readAsString(); + if (packagePubspecContent.contains('flutter:')) { + await _runCommand('flutter', [ + 'test', + '--no-pub', + ], workingDirectory: packagePath); + } else { + await _runCommand('dart', ['test'], workingDirectory: packagePath); + } + } + } +} + +Future _runCommand( + String executable, + List arguments, { + String? workingDirectory, +}) async { + final process = await Process.start( + executable, + arguments, + workingDirectory: workingDirectory, + runInShell: true, + mode: ProcessStartMode.inheritStdio, + ); + final exitCode = await process.exitCode; + if (exitCode != 0) { + print( + 'Command "$executable ${arguments.join(' ')}" failed with exit code $exitCode in $workingDirectory', + ); + exit(exitCode); + } +} + +Future _getFlutterChannel() async { + try { + final result = await Process.run('flutter', [ + '--version', + '--machine', + ], runInShell: true); + if (result.exitCode != 0) { + print( + 'Flutter version command failed with exit code ${result.exitCode}', + ); + print('Stdout: ${result.stdout}'); + print('Stderr: ${result.stderr}'); + return 'unknown'; + } + final machineInfo = jsonDecode(result.stdout as String); + return machineInfo['channel'] as String? ?? 'unknown'; + } catch (e) { + print('Error detecting Flutter channel: $e'); + return 'unknown'; + } +} diff --git a/tool/ci_with_channel.dart b/tool/ci_with_channel.dart new file mode 100644 index 0000000..897fbaf --- /dev/null +++ b/tool/ci_with_channel.dart @@ -0,0 +1,140 @@ +// ignore_for_file: avoid_print, + +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:path/path.dart' as path; +import 'package:yaml/yaml.dart'; + +/// Do not use this with GitHub Actions. It is a convenience script for running +/// CI locally. +/// +/// Usage: dart tool/ci_with_channel.dart -c stable,beta + +Future main(List args) async { + final parser = ArgParser() + ..addFlag( + 'help', + abbr: 'h', + negatable: false, + help: 'Print this usage information.', + ) + ..addOption( + 'channels', + abbr: 'c', + help: + 'Comma-separated list of channels to run on (e.g., stable,beta,main)', + ); + + final results = parser.parse(args); + + if (results['help'] as bool) { + print(parser.usage); + return; + } + + final channelsArg = results['channels'] as String?; + + List channels; + if (channelsArg != null) { + channels = channelsArg.split(',').map((c) => c.trim()).toList(); + } else { + // Default to current channel if none specified + channels = []; + } + + final rootDir = Directory.current; + final originalChannel = await _getCurrentChannel(); + + try { + if (channels.isEmpty) { + print('Running on current channel: $originalChannel'); + await _runCI(rootDir); + } else { + for (final channel in channels) { + print('\n=== Switching to channel: $channel ==='); + await _runCommand('flutter', ['channel', channel]); + await _runCommand('flutter', ['doctor']); + await _runCI(rootDir); + } + } + } finally { + if (channels.isNotEmpty && originalChannel != null) { + print('\n=== Switching back to original channel: $originalChannel ==='); + await _runCommand('flutter', ['channel', originalChannel]); + } + } +} + +Future _getCurrentChannel() async { + final result = await Process.run('flutter', ['channel']); + if (result.exitCode != 0) { + return null; + } + final output = result.stdout as String; + final match = RegExp(r'\* (\w+)').firstMatch(output); + return match?.group(1); +} + +Future _runCI(Directory rootDir) async { + final pubspecFile = File(path.join(rootDir.path, 'pubspec.yaml')); + final pubspecContent = await pubspecFile.readAsString(); + final pubspecYaml = loadYaml(pubspecContent); + + final workspace = pubspecYaml['workspace'] as YamlList?; + if (workspace == null) { + print('No workspace found in pubspec.yaml'); + exit(1); + } + + // pub workspace, only run 'get' once + await _runCommand('flutter', ['pub', 'get'], workingDirectory: rootDir.path); + + final packages = workspace.map((e) => e.toString()).toList(); + + for (final package in packages) { + final packagePath = path.join(rootDir.path, package); + print('== Testing \'$package\' =='); + await _runCommand('dart', [ + 'analyze', + '--fatal-infos', + '--fatal-warnings', + ], workingDirectory: packagePath); + + await _runCommand('dart', ['format', '.'], workingDirectory: packagePath); + + if (await Directory(path.join(packagePath, 'test')).exists()) { + final packagePubspecFile = File(path.join(packagePath, 'pubspec.yaml')); + final packagePubspecContent = await packagePubspecFile.readAsString(); + if (packagePubspecContent.contains('flutter:')) { + await _runCommand('flutter', [ + 'test', + '--no-pub', + ], workingDirectory: packagePath); + } else { + await _runCommand('dart', ['test'], workingDirectory: packagePath); + } + } + } +} + +Future _runCommand( + String executable, + List arguments, { + String? workingDirectory, +}) async { + final process = await Process.start( + executable, + arguments, + workingDirectory: workingDirectory, + runInShell: true, + mode: ProcessStartMode.inheritStdio, + ); + final exitCode = await process.exitCode; + if (exitCode != 0) { + print( + 'Command "$executable ${arguments.join(' ')}" failed with exit code $exitCode in $workingDirectory', + ); + exit(exitCode); + } +} diff --git a/tool/clean_all.dart b/tool/clean_all.dart new file mode 100644 index 0000000..6c7a18e --- /dev/null +++ b/tool/clean_all.dart @@ -0,0 +1,79 @@ +// ignore_for_file: avoid_print, + +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:yaml/yaml.dart'; + +/// Runs `flutter clean` in every package listed in the root pubspec.yaml +/// workspace. Useful for clearing stale build artifacts after switching Flutter +/// channels. +Future main() async { + final rootDir = Directory.current; + final pubspecFile = File(path.join(rootDir.path, 'pubspec.yaml')); + final pubspecContent = await pubspecFile.readAsString(); + final pubspecYaml = loadYaml(pubspecContent); + + final workspace = pubspecYaml['workspace'] as YamlList?; + if (workspace == null) { + print('No workspace found in pubspec.yaml'); + exit(1); + } + + final flutterBin = _resolveFlutterBin(); + print('Using flutter: $flutterBin'); + + var cleaned = 0; + var skipped = 0; + + for (final entry in workspace) { + final package = entry.toString(); + final packagePath = path.join(rootDir.path, package); + + // Only clean packages that have a pubspec.yaml (i.e. are Flutter/Dart pkgs) + final pubspec = File(path.join(packagePath, 'pubspec.yaml')); + if (!await pubspec.exists()) { + print('-- Skipping \'$package\' (no pubspec.yaml found)'); + skipped++; + continue; + } + + print('== Cleaning \'$package\' =='); + final process = await Process.start( + flutterBin, + ['clean'], + workingDirectory: packagePath, + runInShell: true, + mode: ProcessStartMode.inheritStdio, + ); + final exitCode = await process.exitCode; + if (exitCode != 0) { + print( + 'Warning: flutter clean failed with exit code $exitCode in $packagePath', + ); + } else { + cleaned++; + } + } + + print(''); + print('Done. Cleaned $cleaned package(s), skipped $skipped.'); +} + +/// Resolves the path to the `flutter` binary by finding it relative to the +/// running Dart SDK. Falls back to 'flutter' (uses PATH) if not found. +String _resolveFlutterBin() { + // Platform.resolvedExecutable points to the dart binary, e.g.: + // /path/to/flutter/bin/cache/dart-sdk/bin/dart + // Flutter binary is at /path/to/flutter/bin/flutter + final dartBin = File(Platform.resolvedExecutable); + final flutterBin = path.join( + dartBin.parent.parent.parent.parent.path, + 'bin', + 'flutter', + ); + if (File(flutterBin).existsSync()) { + return flutterBin; + } + return 'flutter'; // fallback to PATH +} diff --git a/tool/ios_ci_script.sh b/tool/ios_ci_script.sh new file mode 100755 index 0000000..0024b37 --- /dev/null +++ b/tool/ios_ci_script.sh @@ -0,0 +1,85 @@ +#!/bin/bash + +set -e + +flutter doctor -v + +############################################################################### +# Helper functions +############################################################################### + +# Runs `packages get` on given flutter module. +# $1 = path to flutter module +function flutter_packages_get() { + echo "Fetching dependencies and building '$1'." + pushd $1 + flutter packages get + popd +} + +# Runs xcode build for Debug and Release. +# $1 = path to directory containing the xcode project +# $2 = name of the xcworkspace +# $3 = name of the scheme +function build() { + echo "== Testing '$1' on Flutter's $FLUTTER_VERSION channel ==" + pushd "$1" + + pod install + + xcodebuild -workspace "$2" \ + -scheme "$3" CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO \ + CODE_SIGN_IDENTITY=- EXPANDED_CODE_SIGN_IDENTITY=- \ + COMPILER_INDEX_STORE_ENABLE=NO CONFIGURATION=Debug | xcpretty + + xcodebuild -workspace "$2" \ + -scheme "$3" CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO \ + CODE_SIGN_IDENTITY=- EXPANDED_CODE_SIGN_IDENTITY=- \ + COMPILER_INDEX_STORE_ENABLE=NO CONFIGURATION=Release \ + -destination generic/platform=iOS | xcpretty + + popd +} + +############################################################################### +# Building preconditions +############################################################################### + +echo "Pre-caching ios artifacts, such as the Flutter.framework" +flutter precache --no-web --no-linux --no-windows --no-fuchsia --no-android --no-macos + +echo "Fetching dependencies and building 'prebuilt_module/flutter_module'." +pushd add_to_app/prebuilt_module/flutter_module +flutter packages get +flutter build ios-framework --xcframework --output="$(pwd)/../ios_using_prebuilt_module/Flutter" +popd + +flutter_packages_get "add_to_app/books/flutter_module_books" +flutter_packages_get "add_to_app/fullscreen/flutter_module" +flutter_packages_get "add_to_app/multiple_flutters/multiple_flutters_module" +flutter_packages_get "add_to_app/plugin/flutter_module_using_plugin" + +############################################################################### +# Build projects +############################################################################### + +build "add_to_app/books/ios_books" "IosBooks.xcworkspace" "IosBooks" +build "add_to_app/fullscreen/ios_fullscreen" "IOSFullScreen.xcworkspace" "IOSFullScreen" +build "add_to_app/multiple_flutters/multiple_flutters_ios" "MultipleFluttersIos.xcworkspace" "MultipleFluttersIos" +build "add_to_app/plugin/ios_using_plugin" "IOSUsingPlugin.xcworkspace" "IOSUsingPlugin" + +echo "== Testing 'add_to_app/prebuilt_module/ios_using_prebuilt_module' on Flutter's $FLUTTER_VERSION channel ==" +pushd "add_to_app/prebuilt_module/ios_using_prebuilt_module" + +xcodebuild CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO \ +CODE_SIGN_IDENTITY=- EXPANDED_CODE_SIGN_IDENTITY=- \ +COMPILER_INDEX_STORE_ENABLE=NO CONFIGURATION=Debug | xcpretty + +xcodebuild CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO \ +CODE_SIGN_IDENTITY=- EXPANDED_CODE_SIGN_IDENTITY=- \ +COMPILER_INDEX_STORE_ENABLE=NO CONFIGURATION=Release \ +-destination generic/platform=iOS | xcpretty + +popd + +echo "-- Success --" diff --git a/tool/pubspec.yaml b/tool/pubspec.yaml new file mode 100644 index 0000000..f953f2e --- /dev/null +++ b/tool/pubspec.yaml @@ -0,0 +1,13 @@ +name: repo_tool +description: A tool to automate release cleanup for the samples monorepo. +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + args: ^2.4.2 + io: ^1.0.4 + path: ^1.9.0 + yaml: ^3.1.2 + yaml_edit: ^2.1.1 diff --git a/tool/release.dart b/tool/release.dart new file mode 100644 index 0000000..6c9a33f --- /dev/null +++ b/tool/release.dart @@ -0,0 +1,371 @@ +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:io/ansi.dart'; +import 'package:path/path.dart' as p; +import 'package:yaml/yaml.dart'; +import 'package:yaml_edit/yaml_edit.dart'; + +const String logDir = 'logs'; + +void main(List arguments) async { + final runner = ReleaseScriptRunner(); + await runner.run(arguments); +} + +class ReleaseScriptRunner { + late final File _logFile; + bool _isDryRun = false; + + Future run(List arguments) async { + final parser = ArgParser() + ..addFlag( + 'dry-run', + negatable: false, + help: + 'Prints the commands that would be executed, but does not run them.', + ); + final argResults = parser.parse(arguments); + _isDryRun = argResults['dry-run'] as bool; + + _setupLogging(); + + log( + styleBold.wrap( + 'Flutter Monorepo Update Script Started: ${DateTime.now()}', + ), + stdout, + ); + log(blue.wrap('Log file: ${_logFile.path}')!, stdout); + + if (_isDryRun) { + log(yellow.wrap('--- DRY RUN MODE ---')!, stdout); + } + + if (!await _isFlutterInstalled()) { + log(red.wrap('Flutter is not installed or not in PATH')!, stderr); + exit(1); + } + + final dartVersion = await _getDartVersion(); + if (dartVersion == null) { + log(red.wrap('Failed to get Dart SDK version.'), stderr); + exit(1); + } + log(blue.wrap('Using Dart SDK version: $dartVersion'), stdout); + + final packages = await _getWorkspacePackages(); + if (packages.isEmpty) { + log( + yellow.wrap('No packages found in the root pubspec.yaml workspace.'), + stdout, + ); + exit(0); + } + + log( + blue.wrap( + 'Found ${packages.length} Flutter project(s): ${packages.join(', ')}', + ), + stdout, + ); + + final failedProjects = []; + for (final packagePath in packages) { + final success = await _processProject(packagePath, dartVersion); + if (!success) { + failedProjects.add(packagePath); + } + } + + _printSummary(packages.length, failedProjects); + + log( + styleBold.wrap( + '\nFlutter Monorepo Update Script Completed: ${DateTime.now()}', + ), + stdout, + ); + + if (failedProjects.isNotEmpty) { + exit(1); + } + } + + void _setupLogging() { + final logsDir = Directory(logDir); + if (!logsDir.existsSync()) { + logsDir.createSync(recursive: true); + } + final timestamp = DateTime.now().toIso8601String().replaceAll(':', '-'); + _logFile = File(p.join(logDir, 'release_logs_$timestamp.log')); + } + + void log(String? message, IOSink sink) { + if (message == null) return; + sink.writeln(message); + } + + void logToFile(String? message) { + if (message == null) return; + _logFile.writeAsStringSync( + '\n${_stripAnsiCodes(message)}', + mode: FileMode.append, + ); + } + + Future _isFlutterInstalled() async { + try { + final result = await Process.run('flutter', ['--version']); + return result.exitCode == 0; + } catch (e) { + return false; + } + } + + Future _getDartVersion() async { + try { + final result = await Process.run('flutter', ['--version', '--machine']); + if (result.exitCode == 0) { + final json = loadYaml(result.stdout as String) as YamlMap; + final fullDartVersion = json['dartSdkVersion'] as String?; + if (fullDartVersion != null) { + return fullDartVersion.split(' ').first; + } + return null; + } + } catch (e) { + log(red.wrap('Error getting Dart SDK version: $e'), stderr); + } + return null; + } + + Future> _getWorkspacePackages() async { + final rootPubspec = File('pubspec.yaml'); + if (!rootPubspec.existsSync()) { + log(red.wrap('Root pubspec.yaml not found!'), stderr); + return []; + } + + try { + final content = await rootPubspec.readAsString(); + final yaml = loadYaml(content) as YamlMap; + final workspace = yaml['workspace'] as YamlList?; + if (workspace != null) { + return workspace.nodes.map((node) => node.value as String).toList(); + } + } catch (e) { + log(red.wrap('Error reading workspace packages: $e'), stderr); + } + return []; + } + + Future _processProject(String projectPath, String dartVersion) async { + final projectName = p.basename(projectPath); + final projectDir = Directory(projectPath); + final issues = []; + + if (!projectDir.existsSync()) { + log(red.wrap('Project directory not found: $projectPath'), stderr); + return false; + } + + log(styleBold.wrap('\n========================================='), stdout); + log(styleBold.wrap('Processing project: $projectName'), stdout); + log(styleBold.wrap('=========================================')!, stdout); + + log( + blue.wrap('Updating SDK constraints to use Dart $dartVersion'), + stdout, + ); + if (!_isDryRun) { + if (!await _updateSdkConstraints(projectPath, dartVersion)) { + log( + red.wrap('Failed to update SDK constraints for $projectName'), + stderr, + ); + return false; + } + } + + final commands = [ + Command('dart analyze', 'Running dart analyze...', 'dart', [ + 'analyze', + '--fatal-infos', + '--fatal-warnings', + ]), + Command('dart format', 'Running dart format...', 'dart', [ + 'format', + '.', + ]), + ]; + + final testDir = Directory(p.join(projectPath, 'test')); + if (projectName != 'material_3_demo' && testDir.existsSync()) { + commands.add( + Command('flutter test', 'Running tests...', 'flutter', ['test']), + ); + } + + for (final command in commands) { + log(blue.wrap(command.description), stdout); + + final (didPass, output) = await _runCommand( + command.executable, + command.arguments, + workingDirectory: projectPath, + ); + + if (!didPass) { + log( + red.wrap('${command.displayName} failed for $projectName'), + stderr, + ); + + if (command.displayName == 'pub upgrade' || + command.displayName == 'pub get' && + output.contains('Failed to update packages.')) { + issues.add(output); + } + + if (command.displayName == 'dart analyze' && + output.contains('issue found.') || + output.contains('issues found.')) { + issues.add(output); + } + + if (command.displayName == 'flutter test' && + !output.contains('All tests passed!')) { + issues.add(output); + } + } + } + + if (issues.isNotEmpty) { + logToFile('- Issues found in $projectName'); + for (final issue in issues) { + if (_isOnlyWhitespace(issue)) continue; + logToFile('-- $issue'); + } + } + + log(green.wrap('Successfully processed $projectName'), stdout); + return true; + } + + Future _updateSdkConstraints( + String projectDir, + String versionString, + ) async { + final pubspecFile = File(p.join(projectDir, 'pubspec.yaml')); + if (!pubspecFile.existsSync()) { + log(red.wrap('pubspec.yaml not found in $projectDir'), stderr); + return false; + } + + try { + final newConstraint = '^$versionString-0'; + + final content = await pubspecFile.readAsString(); + final editor = YamlEditor(content); + editor.update(['environment', 'sdk'], newConstraint); + + await pubspecFile.writeAsString(editor.toString()); + log( + blue.wrap( + 'Updated Dart SDK constraint in $projectDir to: $newConstraint', + ), + stdout, + ); + return true; + } catch (e) { + log( + red.wrap('Failed to update SDK constraint in $projectDir: $e'), + stderr, + ); + return false; + } + } + + Future<(bool, String)> _runCommand( + String executable, + List arguments, { + String? workingDirectory, + }) async { + final commandString = '$executable ${arguments.join(' ')}'; + if (_isDryRun) { + log( + yellow.wrap( + ' [DRY RUN] Would execute: `$commandString` in `${workingDirectory ?? '.'}`', + ), + stdout, + ); + return (true, ''); + } + + final process = await Process.start( + executable, + arguments, + workingDirectory: workingDirectory, + runInShell: true, + ); + + StringBuffer output = StringBuffer(''); + + final stdoutFuture = process.stdout + .transform(SystemEncoding().decoder) + .forEach((line) { + log(line, stdout); + if (!_isOnlyWhitespace(line)) { + output.writeln(line.trim().padLeft(2)); + } + }); + + final stderrFuture = process.stderr + .transform(SystemEncoding().decoder) + .forEach((line) { + log(red.wrap(line), stderr); + if (!_isOnlyWhitespace(line)) { + output.writeln(line.trim().padLeft(2)); + } + }); + + await Future.wait([stdoutFuture, stderrFuture]); + + return (await process.exitCode == 0, output.toString()); + } + + void _printSummary(int total, List failed) { + log( + styleBold.wrap('\n=========================================')!, + stdout, + ); + log(styleBold.wrap('Update Summary')!, stdout); + log(styleBold.wrap('=========================================')!, stdout); + log(blue.wrap('Total projects processed: $total'), stdout); + log(green.wrap('Successful: ${total - failed.length}'), stdout); + + if (failed.isNotEmpty) { + log(red.wrap('Failed: ${failed.length}'), stderr); + log(red.wrap('Failed projects: ${failed.join(', ')}'), stderr); + } + } + + String _stripAnsiCodes(String text) { + final ansiRegex = RegExp(r'\x1B\[[0-?]*[ -/]*[@-~]'); + return text.replaceAll(ansiRegex, ''); + } + + bool _isOnlyWhitespace(String text) { + return text.trim().isEmpty; + } +} + +class Command { + final String displayName; + final String description; + final String executable; + final List arguments; + + Command(this.displayName, this.description, this.executable, this.arguments); +} diff --git a/veggieseasons/README.md b/veggieseasons/README.md new file mode 100644 index 0000000..791cef3 --- /dev/null +++ b/veggieseasons/README.md @@ -0,0 +1,3 @@ +# Sample deprecated + +The `veggieseasons` sample has been deprecated. It now lives in the [flutter/demos](https://github.com/flutter/demos) repository, where it will no longer be maintained. \ No newline at end of file diff --git a/web_embedding/README.md b/web_embedding/README.md new file mode 100644 index 0000000..da897af --- /dev/null +++ b/web_embedding/README.md @@ -0,0 +1,33 @@ +# 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. + +Check the `README.md` of each example for more details on how to run it, and the +"Points of Interest" it may contain. + +## Community Contributions + +Members of the community have graciously ported and contributed the following examples +of Flutter Web embedding into other web frameworks: + +| Author | Host Framework | Code | +|--------|----------------|------| +| [@p-mazhnik][] | **React JS** | [p-mazhnik/flutter-embedding cra-flutter][] | +| [@p-mazhnik][] | **React Native** | [p-mazhnik/flutter-embedding expo-flutter][] | + + +_(All contributions are welcome! Please, [create an issue][] or open a PR to let us know +how you've embedded a Flutter Web app with your favorite web framework_ +(Vue? Svelte? Ember? Aurelia? jQuery? MooTools? Prototype?), _so it can be added to the table above!)_ + + [@p-mazhnik]: https://github.com/p-mazhnik + [p-mazhnik/flutter-embedding cra-flutter]: https://github.com/p-mazhnik/flutter-embedding/tree/main/cra-flutter + [p-mazhnik/flutter-embedding expo-flutter]: https://github.com/p-mazhnik/flutter-embedding/tree/main/expo-flutter + [create an issue]: https://github.com/flutter/samples/issues/new + + diff --git a/web_embedding/element_embedding_demo/.gitignore b/web_embedding/element_embedding_demo/.gitignore new file mode 100644 index 0000000..734c863 --- /dev/null +++ b/web_embedding/element_embedding_demo/.gitignore @@ -0,0 +1,48 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# Keeping the repo +.metadata +pubspec.lock + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/web_embedding/element_embedding_demo/README.md b/web_embedding/element_embedding_demo/README.md new file mode 100644 index 0000000..4e0fcbd --- /dev/null +++ b/web_embedding/element_embedding_demo/README.md @@ -0,0 +1,25 @@ +# element_embedding_demo + +This package contains the application used to demonstrate the +upcoming Flutter web feature: "Element Embedding". + +This was first shown on the Flutter Forward event in Nairobi (Kenya), by Tim +Sneath. [See the replay here](https://www.youtube.com/watch?v=zKQYGKAe5W8&t=5799s). + +## Running the demo + +The demo is a Flutter web app, so it can be run as: + +```terminal +$ flutter run -d chrome +``` + +## Points of Interest + +* Check the new JS Interop: + * Look at `lib/main.dart`, find the `@js.JSExport()` annotation. + * Find the JS code that interacts with Dart in `web/js/demo-js-interop.js`. +* See how the Flutter web application is embedded into the page now: + * Find `hostElement` in `web/index.html`. + +_(Built by @ditman, @kevmoo and @malloc-error)_ diff --git a/web_embedding/element_embedding_demo/lib/main.dart b/web_embedding/element_embedding_demo/lib/main.dart new file mode 100644 index 0000000..3b57be2 --- /dev/null +++ b/web_embedding/element_embedding_demo/lib/main.dart @@ -0,0 +1,319 @@ +import 'dart:async'; +import 'dart:js_interop' as js; +import 'dart:js_interop_unsafe' as js_util; + +import 'package:flutter/material.dart'; + +void main() { + runApp(const MyApp()); +} + +enum DemoScreen { counter, textField, custom } + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +@js.JSExport() +class _MyAppState extends State { + final _streamController = StreamController.broadcast(); + DemoScreen _currentDemoScreen = DemoScreen.counter; + int _counterScreenCount = 0; + + @override + void initState() { + super.initState(); + final export = js.createJSInteropWrapper(this); + js.globalContext['_appState'] = export; + js.globalContext.callMethod('_stateSet'.toJS); + } + + @override + void dispose() { + _streamController.close(); + super.dispose(); + } + + @js.JSExport() + void increment() { + if (_currentDemoScreen == DemoScreen.counter) { + setState(() { + _counterScreenCount++; + _streamController.add(null); + }); + } + } + + @js.JSExport() + void addHandler(void Function() handler) { + _streamController.stream.listen((event) { + handler(); + }); + } + + @js.JSExport() + int get count => _counterScreenCount; + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Element embedding', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), + ), + debugShowCheckedModeBanner: false, + home: demoScreenRouter(_currentDemoScreen), + ); + } + + Widget demoScreenRouter(DemoScreen which) { + switch (which) { + case DemoScreen.counter: + return CounterDemo( + title: 'Counter', + numToDisplay: _counterScreenCount, + incrementHandler: increment, + ); + case DemoScreen.textField: + return const TextFieldDemo(title: 'Note to Self'); + case DemoScreen.custom: + return const CustomDemo(title: 'Character Counter'); + } + } + + @js.JSExport() + void changeDemoScreenTo(String screenString) { + setState(() { + switch (screenString) { + case 'counter': + _currentDemoScreen = DemoScreen.counter; + case 'textField': + _currentDemoScreen = DemoScreen.textField; + case 'custom': + _currentDemoScreen = DemoScreen.custom; + default: + _currentDemoScreen = DemoScreen.counter; + } + }); + } +} + +class CounterDemo extends StatefulWidget { + final String title; + final int numToDisplay; + final VoidCallback incrementHandler; + + const CounterDemo({ + super.key, + required this.title, + required this.numToDisplay, + required this.incrementHandler, + }); + + @override + State createState() => _CounterDemoState(); +} + +class _CounterDemoState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(widget.title)), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('You have pushed the button this many times:'), + Text( + '${widget.numToDisplay}', + style: Theme.of(context).textTheme.headlineMedium, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: widget.incrementHandler, + tooltip: 'Increment', + child: const Icon(Icons.add), + ), + ); + } +} + +class TextFieldDemo extends StatelessWidget { + const TextFieldDemo({super.key, required this.title}); + final String title; + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(title)), + body: const Center( + child: Padding( + padding: EdgeInsets.all(14.0), + child: TextField( + maxLines: null, + decoration: InputDecoration( + border: OutlineInputBorder(), + // hintText: 'Text goes here!', + ), + ), + ), + ), + ); + } +} + +class CustomDemo extends StatefulWidget { + final String title; + + const CustomDemo({super.key, required this.title}); + + @override + State createState() => _CustomDemoState(); +} + +class _CustomDemoState extends State { + final double textFieldHeight = 80; + final Color colorPrimary = const Color(0xff027dfd); + // const Color(0xffd43324); + // const Color(0xff6200ee); + // const Color.fromARGB(255, 255, 82, 44); + final TextEditingController _textController = TextEditingController(); + late FocusNode textFocusNode; + + int totalCharCount = 0; + + @override + void initState() { + super.initState(); + textFocusNode = FocusNode(); + textFocusNode.requestFocus(); + } + + @override + void dispose() { + _textController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + toolbarHeight: MediaQuery.of(context).size.height - textFieldHeight, + flexibleSpace: Container( + color: colorPrimary, + height: MediaQuery.of(context).size.height - textFieldHeight, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'COUNT WITH DASH!', + style: TextStyle(color: Colors.white, fontSize: 18), + ), + const SizedBox(height: 26), + Container( + width: 98, + height: 98, + decoration: BoxDecoration( + border: Border.all(width: 2, color: Colors.white), + shape: BoxShape.circle, + ), + child: Center( + child: Container( + width: 90, + height: 90, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/dash.png'), + fit: BoxFit.cover, + ), + color: Colors.white, + shape: BoxShape.circle, + ), + ), + ), + ), + const SizedBox(height: 20), + Text( + totalCharCount.toString(), + style: const TextStyle(color: Colors.white, fontSize: 52), + ), + // const Text( + // 'characters typed', + // style: TextStyle(color: Colors.white, fontSize: 14), + // ), + ], + ), + ), + ), + body: Column( + children: [ + SizedBox( + height: textFieldHeight, + child: Center( + child: Padding( + padding: const EdgeInsets.only(left: 18, right: 18), + child: Row( + children: [ + Expanded( + child: TextField( + controller: _textController, + focusNode: textFocusNode, + onSubmitted: (value) { + textFocusNode.requestFocus(); + }, + onChanged: (value) { + handleChange(); + }, + maxLines: 1, + decoration: const InputDecoration( + border: OutlineInputBorder(), + ), + ), + ), + const SizedBox(width: 12), + Center( + child: Container( + width: 42, + height: 42, + decoration: BoxDecoration( + color: colorPrimary, + shape: BoxShape.circle, + ), + child: IconButton( + icon: const Icon(Icons.refresh), + color: Colors.white, + onPressed: () { + handleClear(); + }, + ), + ), + ), + ], + ), + ), + ), + ), + ], + ), + ); + } + + void handleChange() { + setState(() { + totalCharCount = _textController.value.text.toString().length; + }); + } + + void handleClear() { + setState(() { + _textController.clear(); + totalCharCount = 0; + }); + textFocusNode.requestFocus(); + } +} diff --git a/web_embedding/element_embedding_demo/pubspec.yaml b/web_embedding/element_embedding_demo/pubspec.yaml new file mode 100644 index 0000000..bdb5f6c --- /dev/null +++ b/web_embedding/element_embedding_demo/pubspec.yaml @@ -0,0 +1,24 @@ +name: element_embedding_demo +description: A small app to be embedded into a HTML element (see web/index.html) +publish_to: 'none' +version: 1.0.0+1 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + analysis_defaults: + path: ../../analysis_defaults + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 + +flutter: + uses-material-design: true + assets: + - assets/dash.png diff --git a/web_embedding/element_embedding_demo/web/css/style.css b/web_embedding/element_embedding_demo/web/css/style.css new file mode 100644 index 0000000..9ada86b --- /dev/null +++ b/web_embedding/element_embedding_demo/web/css/style.css @@ -0,0 +1,263 @@ +@font-face { + font-family: "DM Sans"; + src: url(../fonts/DMSans-Regular.ttf); + font-weight: normal; +} + +@font-face { + font-family: "DM Sans"; + src: url(../fonts/DMSans-Bold.ttf); + font-weight: 700; +} + +/** Reset */ +* { + box-sizing: border-box; + font-family: "DM Sans", sans-serif; +} +html, body { + margin: 0; + padding: 0; + min-height: 100vh; +} + +body { + background-color: #fff; + background-image: radial-gradient( + ellipse at bottom, + #fafafa 5%, + transparent 60% + ), + linear-gradient(136deg, transparent, #eee 290%), + linear-gradient(115deg, #fafafa, transparent 40%), + linear-gradient(180deg, transparent 0, #ddd 70%), + radial-gradient(ellipse at -70% -180%, transparent 80%, #eee 0), + radial-gradient(ellipse at bottom, #71c7ee 40%, transparent 80%), + radial-gradient(ellipse at 5% 340%, transparent 80%, #ddd 0); + background-repeat: no-repeat; + color: #555; +} + +/** Layout **/ +body { display: flex; flex-direction: column; } +section.contents { + flex: 1 1 auto; + flex-direction: row; + display: flex; +} +section.contents aside { + flex: 0; + display: flex; + flex-direction: column; + order: -1; +} +section.contents aside fieldset { + display: flex; + flex-flow: wrap; + justify-content: space-between; + align-items: flex-end; +} +section.contents aside .align-top { + align-self: flex-start; +} +section.contents article { + flex: 1; + margin-top: 50px; + display: flex; + justify-content: center; + overflow: hidden; +} + +/** Title */ +h1 { + font-weight: 700; + font-size: 48px; + padding: 0; + line-height: .9em; + letter-spacing: -2px; + margin: 0 0 30px 0; +} + +/** Controls for the demo (left column) */ +#demo_controls { + background: linear-gradient(90deg, rgba(255,255,255,1) 10%, rgba(255,255,255,0) 100%); + padding: 40px 20px 0px 20px; + z-index: 10; +} +#demo_controls fieldset { + padding: 0; + border: none; + width: 210px; +} +#demo_controls legend { + text-align: center; + font-size: 20px; + line-height: 40px; + margin-bottom: 3px; +} +#demo_controls select.screen { + display: block; + width: 120px; + padding: 4px; + text-align: center; + margin-bottom: 10px; +} +#demo_controls input { + display: block; + width: 100px; + margin: 0 0 10px 0; + text-align: center; +} +/** Keep controls that */ +#demo_controls .tight input { + margin: 0px; +} +#demo_controls input[type="button"] { + line-height: 10px; + font-size: 14px; + border-radius: 15px; + border: 1px solid #aaa; + border-style: outset; + background-color: #fff; + height: 30px; + color: #555; + transition: all 100ms ease-in-out; + cursor: pointer; +} +#demo_controls input[type="button"]:hover { + /* .active:hover background-color: #96B6E3;*/ + border-color: #1c68d4; + background-color: #1c68d4; + color: white; +} +#demo_controls input[type="button"].active { + border-color: #1c68d4; + background-color: #1c68d4; + color: white; +} +#demo_controls input#value { + font-size: 32px; + line-height: 1em; + min-height: 30px; + color: #888; +} +#demo_controls input#increment { + /* Center vertically next to taller input#value */ + position: relative; + top: -6px; +} +#demo_controls .disabled { + pointer-events: none; + opacity: .5; +} + +/** The style for the DIV where flutter will be rendered, and the CSS fx */ +#flutter_target { + border: 1px solid #aaa; + width: 320px; + height: 480px; + border-radius: 0px; + transition: all 150ms ease-in; + align-self: center; +} +#flutter_target.resize { + width: 480px; + height: 336px; +} +#flutter_target.spin { animation: spin 6400ms ease-in-out infinite; } +#flutter_target.shadow { position: relative; } +#flutter_target.shadow::before { + content: ""; + position: absolute; + display: block; + width: 100%; + top: calc(100% - 1px); + left: 0; + height: 1px; + background-color: black; + border-radius: 50%; + z-index: -1; + transform: rotateX(80deg); + box-shadow: 0px 0px 60px 38px rgb(0 0 0 / 25%); +} +#flutter_target.mirror { + -webkit-box-reflect: below 0px linear-gradient(to bottom, rgba(0,0,0,0.0), rgba(0,0,0,0.4)); +} + +@keyframes spin { + 0% { + transform: perspective(1000px) rotateY(0deg); + animation-timing-function: ease-in; + } + 15% { + transform: perspective(1000px) rotateY(165deg); + animation-timing-function: linear; + } + 75% { + transform: perspective(1000px) rotateY(195deg); + animation-timing-function: linear; + } + 90% { + transform: perspective(1000px) rotateY(359deg); + animation-timing-function: ease-out; + } + 100% { + transform: perspective(1000px) rotateY(359deg); + animation-timing-function: linear; + } +} + +/** "Handheld"/Device mode container */ +#handheld::before { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background: url(../icons/unsplash-x9WGMWwp1NM.png) no-repeat; + background-size: 1000px; + background-position: top right; + opacity: 1; + transition: opacity 200ms ease-out; +} + +#handheld::after { + content: ""; + position: absolute; + display: block; + width: 77px; + height: 67px; + top: 534px; + right: 573px; + background: url(../icons/nail.png) no-repeat; + background-size: 77px; + opacity: 1; + transition: opacity 200ms ease-out; +} + +#handheld.hidden::before, +#handheld.hidden::after { + opacity: 0; +} + +#flutter_target.handheld { + position: absolute; + right: 0px; + transform-origin: 0px 0px 0px; + transform: rotate(-14.1deg) scale(0.80) translate(-539px, -45px); + width: 316px; + height: 678px; + border-radius: 34px; + border: 1px solid #000; + overflow: hidden; + align-self: initial; +} + +.imageAttribution { + position: absolute; + bottom: 6px; + right: 6px; + font-size: 10px; +} +.imageAttribution, .imageAttribution a { color: #fff; } \ No newline at end of file diff --git a/web_embedding/element_embedding_demo/web/favicon.ico b/web_embedding/element_embedding_demo/web/favicon.ico new file mode 100644 index 0000000..f8a147b Binary files /dev/null and b/web_embedding/element_embedding_demo/web/favicon.ico differ diff --git a/web_embedding/element_embedding_demo/web/fonts/DMSans-Bold.ttf b/web_embedding/element_embedding_demo/web/fonts/DMSans-Bold.ttf new file mode 100644 index 0000000..e70172a Binary files /dev/null and b/web_embedding/element_embedding_demo/web/fonts/DMSans-Bold.ttf differ diff --git a/web_embedding/element_embedding_demo/web/fonts/DMSans-Regular.ttf b/web_embedding/element_embedding_demo/web/fonts/DMSans-Regular.ttf new file mode 100644 index 0000000..cad73f0 Binary files /dev/null and b/web_embedding/element_embedding_demo/web/fonts/DMSans-Regular.ttf differ diff --git a/web_embedding/element_embedding_demo/web/fonts/OFL.txt b/web_embedding/element_embedding_demo/web/fonts/OFL.txt new file mode 100644 index 0000000..9de4906 --- /dev/null +++ b/web_embedding/element_embedding_demo/web/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2014-2017 Indian Type Foundry (info@indiantypefoundry.com). Copyright 2019 Google LLC. + +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. diff --git a/web_embedding/element_embedding_demo/web/icons/Icon-192.png b/web_embedding/element_embedding_demo/web/icons/Icon-192.png new file mode 100644 index 0000000..210951d Binary files /dev/null and b/web_embedding/element_embedding_demo/web/icons/Icon-192.png differ diff --git a/web_embedding/element_embedding_demo/web/icons/Icon-512.png b/web_embedding/element_embedding_demo/web/icons/Icon-512.png new file mode 100644 index 0000000..25618cb Binary files /dev/null and b/web_embedding/element_embedding_demo/web/icons/Icon-512.png differ diff --git a/web_embedding/element_embedding_demo/web/icons/Icon-maskable-192.png b/web_embedding/element_embedding_demo/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..210951d Binary files /dev/null and b/web_embedding/element_embedding_demo/web/icons/Icon-maskable-192.png differ diff --git a/web_embedding/element_embedding_demo/web/icons/Icon-maskable-512.png b/web_embedding/element_embedding_demo/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..25618cb Binary files /dev/null and b/web_embedding/element_embedding_demo/web/icons/Icon-maskable-512.png differ diff --git a/web_embedding/element_embedding_demo/web/icons/favicon.png b/web_embedding/element_embedding_demo/web/icons/favicon.png new file mode 100644 index 0000000..289117d Binary files /dev/null and b/web_embedding/element_embedding_demo/web/icons/favicon.png differ diff --git a/web_embedding/element_embedding_demo/web/icons/nail.png b/web_embedding/element_embedding_demo/web/icons/nail.png new file mode 100644 index 0000000..0a4450e Binary files /dev/null and b/web_embedding/element_embedding_demo/web/icons/nail.png differ diff --git a/web_embedding/element_embedding_demo/web/index.html b/web_embedding/element_embedding_demo/web/index.html new file mode 100644 index 0000000..784fd50 --- /dev/null +++ b/web_embedding/element_embedding_demo/web/index.html @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + Element embedding + + + + + +
+
+
+
+ + +
+ + + + + + diff --git a/web_embedding/element_embedding_demo/web/js/demo-css-fx.js b/web_embedding/element_embedding_demo/web/js/demo-css-fx.js new file mode 100644 index 0000000..b857cec --- /dev/null +++ b/web_embedding/element_embedding_demo/web/js/demo-css-fx.js @@ -0,0 +1,82 @@ +// Manages toggling the VFX of the buttons +(function () { + "use strict"; + + let handheld; + + let fxButtons = document.querySelector("#fx"); + let flutterTarget = document.querySelector("#flutter_target"); + + let attribution = document.createElement("span"); + attribution.className = "imageAttribution"; + attribution.innerHTML = "Photo by Nathana RebouΓ§as on Unsplash"; + + // (Re)moves the flutterTarget inside a div#handheld. + function handleHandHeld(fx) { + resetRotation(); + if (!handheld) { + handheld = document.createElement("div"); + handheld.id = "handheld"; + handheld.classList.add("hidden"); + // Inject before the flutterTarget + flutterTarget.parentNode.insertBefore(handheld, flutterTarget); + handheld.append(flutterTarget); + handheld.append(attribution); + window.setTimeout(function () { + handheld.classList.remove("hidden"); + }, 100); + // Disable all effects on the flutter container + flutterTarget.className = ""; + setOtherFxEnabled(false); + } else { + handheld.classList.add("hidden"); + window.setTimeout(function () { + handheld.parentNode.insertBefore(flutterTarget, handheld); + handheld.remove(); + handheld = null; + }, 210); + setOtherFxEnabled(true); + } + window.requestAnimationFrame(function () { + // Let the browser flush the DOM... + flutterTarget.classList.toggle(fx); + }); + } + + // Sets a rotation style on the flutterTarget (in degrees). + function handleRotation(degrees) { + flutterTarget.style.transform = `perspective(1000px) rotateY(${degrees}deg)`; + } + + // Removes the inline style from the flutterTarget. + function resetRotation() { + flutterTarget.style = null; + } + + // Enables/disables the buttons that are not compatible with the "handheld" mode. + function setOtherFxEnabled(enabled) { + fxButtons.querySelectorAll('input').forEach((btn) => { + if (btn.dataset.fx != 'handheld') { + btn.classList.toggle('disabled', !enabled); + } + }); + } + + // Handles clicks on the buttons inside #fx. + fxButtons.addEventListener("click", (event) => { + let fx = event.target.dataset.fx; + if (fx === "handheld") { + handleHandHeld(fx); + return; + } + flutterTarget.classList.toggle(fx); + }); + + fxButtons.addEventListener("input", (event) => { + if (event.target.id === "rotation") { + flutterTarget.classList.toggle("spin", false); + handleRotation(event.target.value); + } + }) + +})(); diff --git a/web_embedding/element_embedding_demo/web/js/demo-js-interop.js b/web_embedding/element_embedding_demo/web/js/demo-js-interop.js new file mode 100644 index 0000000..e35164e --- /dev/null +++ b/web_embedding/element_embedding_demo/web/js/demo-js-interop.js @@ -0,0 +1,43 @@ +// Sets up a channel to JS-interop with Flutter +(function() { + "use strict"; + // This function will be called from Flutter when it prepares the JS-interop. + window._stateSet = function () { + window._stateSet = function () { + console.log("Call _stateSet only once!"); + }; + + // The state of the flutter app, see `class _MyAppState` in lib/main.dart. + let appState = window._appState; + + let valueField = document.querySelector("#value"); + let updateState = function () { + valueField.value = appState.count; + }; + + // Register a callback to update the HTML field from Flutter. + appState.addHandler(updateState); + + // Render the first value (0). + updateState(); + + let incrementButton = document.querySelector("#increment"); + incrementButton.addEventListener("click", (event) => { + appState.increment(); + }); + + let screenSelector = document.querySelector("#screen-selector"); + screenSelector.addEventListener("change", (event) => { + appState.changeDemoScreenTo(event.target.value); + setJsInteropControlsEnabled(event.target.value === 'counter'); + }); + + // Enables/disables the Value/Increment controls. + function setJsInteropControlsEnabled(enabled) { + let elements = document.querySelectorAll("#increment, label[for='value']"); + elements.forEach((el) => { + el.classList.toggle('disabled', !enabled); + }) + } + }; +}()); diff --git a/web_embedding/element_embedding_demo/web/manifest.json b/web_embedding/element_embedding_demo/web/manifest.json new file mode 100644 index 0000000..c2e2f08 --- /dev/null +++ b/web_embedding/element_embedding_demo/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "element_embedding_demo", + "short_name": "element_embedding", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "An example of how to embed a Flutter Web app into any HTML Element of a page.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/web_embedding/ng-flutter/.gitignore b/web_embedding/ng-flutter/.gitignore new file mode 100644 index 0000000..775ec8c --- /dev/null +++ b/web_embedding/ng-flutter/.gitignore @@ -0,0 +1,43 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +package-lock.json +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings + +# System files +.DS_Store +Thumbs.db diff --git a/web_embedding/ng-flutter/README.md b/web_embedding/ng-flutter/README.md new file mode 100644 index 0000000..d80e38d --- /dev/null +++ b/web_embedding/ng-flutter/README.md @@ -0,0 +1,177 @@ +# ng-flutter + +This Angular project is a simple example of how Angular and Flutter +web apps could be integrated, and have them interop. + +## Points of Interest + +### Angular + +This repository is a quite standard Angular app. The following changes were made +to be able to use (and interop) with a Flutter web application: + +* `package.json` has a custom `prebuild` script that builds the + Flutter web app, so Angular can find it later. +* `flutter.js` is added as a `"scripts"` entry in `angular.json`. + Angular takes care of minimizing and injecting it as any other script. +* The rest of the flutter app `flutter/build/web/` is registered + as an `"assets"` entry in `angular.json`, and moved to `/flutter`. +* The `ng-flutter` component takes care of embedding Flutter web, and yielding + control to Angular through an `appLoaded` `EventEmitter`. The object yielded + by this emitter is a state controller exposed by flutter via a JS custom + event! + +### Flutter + +The embedded Flutter application lives in the `flutter` directory of this repo. +That application is a standard web app, that doesn't need to be aware that it's +going to be embedded in another framework. + +* Flutter uses new `@staticInterop` methods to allow certain Dart functions to + be called from JavaScript. +* Look at how `createDartExport` and `broadcastAppEvent` work together to make + the `_state` controller of the Flutter app available to Angular! + +## How to build the app + +### Requirements + +If you want to build and run this demo on your machine, you'll need +a moderately recent version of Angular: + +```console +$ ng version + +Angular CLI: 17.0.0 +Node: 20.9.0 +Package Manager: npm 10.1.0 +OS: linux x64 +``` + +And Flutter: + +``` +$ flutter --version + +Flutter 3.13.9 β€’ channel stable +Framework β€’ revision d211f42860 (2 weeks ago) β€’ 2023-10-25 13:42:25 -0700 +Engine β€’ revision 0545f8705d +Tools β€’ Dart 3.1.5 β€’ DevTools 2.25.0 +``` + +**Ensure `npm`, `ng` and `flutter` are present in your `$PATH`.** + +### Building the app + +This repository is a moderately standard Angular app. It integrates +Flutter web by making it part of the Angular `assets`. + +In order to build this app, first fetch its `npm` dependencies: + +```console +$ npm install + +added 963 packages, and audited 964 packages in 17s + +93 packages are looking for funding + run `npm fund` for details + +found 0 vulnerabilities +``` + +Then run the `build` script. It'll take care of building Flutter +automatically: + +```console +$ npm run build + +> ng-flutter@0.0.0 prebuild + +... Flutter web build output ... + +Compiling lib/main.dart for the Web... + +> ng-flutter@0.0.0 build +> ng build + +... Angular build output ... + +βœ” Browser application bundle generation complete. +βœ” Copying assets complete. +βœ” Index html generation complete. +``` + +### Local Angular development + +Once you've reached this point, you should be able to work with +your Angular application normally, for example to run a local web +server: + +```console +$ npm run start + +> ng-flutter@0.0.0 start +> ng serve + +βœ” Browser application bundle generation complete. + +Initial Chunk Files | Names | Raw Size +vendor.js | vendor | 4.38 MB | + +... Angular build output... + +** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ ** + + +βœ” Compiled successfully. +``` + +Navigate to `http://localhost:4200/`. The application will automatically reload +if you change any of its Angular source files. + +### Local Flutter web development + +The Flutter app lives inside the `flutter` directory, and can be +developed independently. Just do any changes on Flutter web as you'd +normally do. It even includes a small `web/index.html` so you can see +changes to your app without running the whole Angular setup. + +> **Note** +> For now, Angular does _not_ auto-detect changes to your Flutter web +app, so once you're happy with your Flutter web app, make sure to +call `npm run build` so everything rebuilds and gets placed into its +correct location. + +### Deploying the app + +After `npm run build`, you should have a deployable Angular + Flutter +web app in the `dist` directory of this Angular project. + +Your built app can can be deployed anywhere, but do check +[Firebase hosting](https://firebase.google.com/docs/hosting) for a +super-easy deployment experience! + +## Troubleshooting + +### Flutter + +Ensure your flutter app is properly rebuilt after any changes. + +* Run `npm run build` to re-build the Flutter app. + +If you encounter error messages like: + +``` +Error: Can't resolve 'flutter/build/web/flutter.js' in '/my/checkout/of/ng-flutter' +``` + +You definitely need to run `npm run build`! + +## Reach out to the team(s)! + +Have you had any problem not covered in this README? Do you want +to see other embedding examples? + +Let us know by [creating an issue](https://github.com/flutter/samples/issues/new) or opening a new pull request. + +Thanks! diff --git a/web_embedding/ng-flutter/angular.json b/web_embedding/ng-flutter/angular.json new file mode 100644 index 0000000..7189fdb --- /dev/null +++ b/web_embedding/ng-flutter/angular.json @@ -0,0 +1,114 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "ng-flutter": { + "projectType": "application", + "schematics": {}, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/ng-flutter", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": [ + "zone.js" + ], + "tsConfig": "tsconfig.app.json", + "assets": [ + "src/favicon.ico", + "src/assets", + { + "input": "flutter/build/web/", + "glob": "**/*", + "output": "/flutter/" + } + ], + "styles": [ + "@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.css" + ], + "scripts": [ + { + "input": "flutter/build/web/flutter.js", + "inject": true, + "bundleName": "flutter" + } + ] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "buildTarget": "ng-flutter:build:production" + }, + "development": { + "buildTarget": "ng-flutter:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "buildTarget": "ng-flutter:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "polyfills": [ + "zone.js", + "zone.js/testing" + ], + "tsConfig": "tsconfig.spec.json", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.css" + ], + "scripts": [] + } + } + } + } + }, + "cli": { + "analytics": "0ff9b6e8-2034-4f87-9ac7-46dbd612ebad" + } +} diff --git a/web_embedding/ng-flutter/flutter/.gitignore b/web_embedding/ng-flutter/flutter/.gitignore new file mode 100644 index 0000000..24476c5 --- /dev/null +++ b/web_embedding/ng-flutter/flutter/.gitignore @@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/web_embedding/ng-flutter/flutter/.metadata b/web_embedding/ng-flutter/flutter/.metadata new file mode 100644 index 0000000..35b0a49 --- /dev/null +++ b/web_embedding/ng-flutter/flutter/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: f41ae4f4c925336400b11dc02986c1b4d78a173c + channel: master + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: f41ae4f4c925336400b11dc02986c1b4d78a173c + base_revision: f41ae4f4c925336400b11dc02986c1b4d78a173c + - platform: android + create_revision: f41ae4f4c925336400b11dc02986c1b4d78a173c + base_revision: f41ae4f4c925336400b11dc02986c1b4d78a173c + - platform: ios + create_revision: f41ae4f4c925336400b11dc02986c1b4d78a173c + base_revision: f41ae4f4c925336400b11dc02986c1b4d78a173c + - platform: linux + create_revision: f41ae4f4c925336400b11dc02986c1b4d78a173c + base_revision: f41ae4f4c925336400b11dc02986c1b4d78a173c + - platform: macos + create_revision: f41ae4f4c925336400b11dc02986c1b4d78a173c + base_revision: f41ae4f4c925336400b11dc02986c1b4d78a173c + - platform: web + create_revision: f41ae4f4c925336400b11dc02986c1b4d78a173c + base_revision: f41ae4f4c925336400b11dc02986c1b4d78a173c + - platform: windows + create_revision: f41ae4f4c925336400b11dc02986c1b4d78a173c + base_revision: f41ae4f4c925336400b11dc02986c1b4d78a173c + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/web_embedding/ng-flutter/flutter/README.md b/web_embedding/ng-flutter/flutter/README.md new file mode 100644 index 0000000..46dd171 --- /dev/null +++ b/web_embedding/ng-flutter/flutter/README.md @@ -0,0 +1,16 @@ +# ng_companion + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/web_embedding/ng-flutter/flutter/analysis_options.yaml b/web_embedding/ng-flutter/flutter/analysis_options.yaml new file mode 100644 index 0000000..ad6e362 --- /dev/null +++ b/web_embedding/ng-flutter/flutter/analysis_options.yaml @@ -0,0 +1,30 @@ +include: ../../../analysis_options.yaml + +analyzer: + exclude: + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** + language: + strict-casts: true + strict-inference: true + strict-raw-types: true + +linter: + rules: + - avoid_types_on_closure_parameters + - avoid_void_async + - cancel_subscriptions + - close_sinks + - directives_ordering + - package_prefixed_library_names + - test_types_in_equals + - throw_in_finally + - unawaited_futures + - unnecessary_breaks + - unnecessary_statements + - use_super_parameters diff --git a/web_embedding/ng-flutter/flutter/assets/dash.png b/web_embedding/ng-flutter/flutter/assets/dash.png new file mode 100644 index 0000000..3ea815d Binary files /dev/null and b/web_embedding/ng-flutter/flutter/assets/dash.png differ diff --git a/web_embedding/ng-flutter/flutter/lib/main.dart b/web_embedding/ng-flutter/flutter/lib/main.dart new file mode 100644 index 0000000..d550b44 --- /dev/null +++ b/web_embedding/ng-flutter/flutter/lib/main.dart @@ -0,0 +1,68 @@ +import 'dart:js_interop' show createJSInteropWrapper; + +import 'package:flutter/material.dart'; + +import 'pages/counter.dart'; +import 'pages/dash.dart'; +import 'pages/text.dart'; + +import 'src/js_interop.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + final ValueNotifier _screen = ValueNotifier( + DemoScreen.counter, + ); + final ValueNotifier _counter = ValueNotifier(0); + final ValueNotifier _text = ValueNotifier(''); + + late final DemoAppStateManager _state = DemoAppStateManager( + screen: _screen, + counter: _counter, + text: _text, + ); + + @override + void initState() { + super.initState(); + final export = createJSInteropWrapper(_state); + + // Emit this through the root object of the flutter app :) + broadcastAppEvent('flutter-initialized', export); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Element embedding', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), + ), + home: ValueListenableBuilder( + valueListenable: _screen, + builder: (context, value, child) => demoScreenRouter(value), + ), + ); + } + + Widget demoScreenRouter(DemoScreen which) => switch (which) { + DemoScreen.counter => CounterDemo(counter: _counter), + DemoScreen.text => TextFieldDemo(text: _text), + DemoScreen.dash => DashDemo(text: _text), + }; +} diff --git a/web_embedding/ng-flutter/flutter/lib/pages/counter.dart b/web_embedding/ng-flutter/flutter/lib/pages/counter.dart new file mode 100644 index 0000000..6644f97 --- /dev/null +++ b/web_embedding/ng-flutter/flutter/lib/pages/counter.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; + +class CounterDemo extends StatefulWidget { + const CounterDemo({super.key, required this.counter}); + + final ValueNotifier counter; + + @override + State createState() => _CounterDemoState(); +} + +class _CounterDemoState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: const Text('Counter'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('You have pushed the button this many times:'), + ValueListenableBuilder( + valueListenable: widget.counter, + builder: (context, value, child) => Text( + '$value', + style: Theme.of(context).textTheme.headlineMedium, + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + widget.counter.value++; + }, + tooltip: 'Increment', + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/web_embedding/ng-flutter/flutter/lib/pages/dash.dart b/web_embedding/ng-flutter/flutter/lib/pages/dash.dart new file mode 100644 index 0000000..3e97a92 --- /dev/null +++ b/web_embedding/ng-flutter/flutter/lib/pages/dash.dart @@ -0,0 +1,150 @@ +import 'package:flutter/material.dart'; + +class DashDemo extends StatefulWidget { + const DashDemo({super.key, required this.text}); + + final ValueNotifier text; + + @override + State createState() => _DashDemoState(); +} + +class _DashDemoState extends State { + final double textFieldHeight = 80; + final Color colorPrimary = Colors.blue.shade700; + late final TextEditingController textController; + + int _totalCharCount = 0; + + @override + void initState() { + super.initState(); + // Initial value of the text box. + _totalCharCount = widget.text.value.length; + textController = TextEditingController.fromValue( + TextEditingValue( + text: widget.text.value, + selection: TextSelection.collapsed(offset: widget.text.value.length), + ), + ); + // Report changes. + textController.addListener(_onTextControllerChange); + // Listen to changes from the outside. + widget.text.addListener(_onTextStateChanged); + } + + void _onTextControllerChange() { + widget.text.value = textController.text; + setState(() { + _totalCharCount = textController.text.length; + }); + } + + void _onTextStateChanged() { + textController.value = TextEditingValue( + text: widget.text.value, + selection: TextSelection.collapsed(offset: widget.text.value.length), + ); + } + + @override + void dispose() { + super.dispose(); + textController.dispose(); + widget.text.removeListener(_onTextStateChanged); + } + + void _handleClear() { + textController.value = TextEditingValue( + text: '', + selection: TextSelection.collapsed(offset: widget.text.value.length), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + Expanded( + child: Container( + width: double.infinity, + color: colorPrimary, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'COUNT WITH DASH!', + style: Theme.of( + context, + ).textTheme.titleLarge!.copyWith(color: Colors.white), + ), + // Bordered dash avatar + Padding( + padding: const EdgeInsets.all(12), + child: ClipOval( + child: Container( + color: Colors.white, + padding: const EdgeInsets.all(2), + child: ClipOval( + child: Container( + color: colorPrimary, + padding: const EdgeInsets.all(2), + child: const CircleAvatar( + radius: 45, + backgroundColor: Colors.white, + foregroundImage: AssetImage('assets/dash.png'), + ), + ), + ), + ), + ), + ), + Text( + '$_totalCharCount', + style: Theme.of( + context, + ).textTheme.displayLarge!.copyWith(color: Colors.white), + ), + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + Expanded( + child: TextField( + autofocus: true, + controller: textController, + maxLines: 1, + decoration: const InputDecoration( + border: OutlineInputBorder(), + hintText: 'Type something!', + ), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 12), + child: Ink( + decoration: ShapeDecoration( + color: colorPrimary, + shape: const CircleBorder(), + ), + child: IconButton( + icon: const Icon(Icons.refresh), + color: Colors.white, + onPressed: _handleClear, + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/web_embedding/ng-flutter/flutter/lib/pages/text.dart b/web_embedding/ng-flutter/flutter/lib/pages/text.dart new file mode 100644 index 0000000..7f5c6aa --- /dev/null +++ b/web_embedding/ng-flutter/flutter/lib/pages/text.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; + +class TextFieldDemo extends StatefulWidget { + const TextFieldDemo({super.key, required this.text}); + final ValueNotifier text; + + @override + State createState() => _TextFieldDemoState(); +} + +class _TextFieldDemoState extends State { + late final TextEditingController textController; + + @override + void initState() { + super.initState(); + // Initial value of the text box. + textController = TextEditingController.fromValue( + TextEditingValue( + text: widget.text.value, + selection: TextSelection.collapsed(offset: widget.text.value.length), + ), + ); + // Report changes. + textController.addListener(_onTextControllerChange); + // Listen to changes from the outside. + widget.text.addListener(_onTextStateChanged); + } + + void _onTextControllerChange() { + widget.text.value = textController.text; + } + + void _onTextStateChanged() { + textController.value = TextEditingValue( + text: widget.text.value, + selection: TextSelection.collapsed(offset: widget.text.value.length), + ); + } + + @override + void dispose() { + super.dispose(); + textController.dispose(); + widget.text.removeListener(_onTextStateChanged); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: const Text('Text Field'), + ), + body: Center( + child: Padding( + padding: const EdgeInsets.all(14.0), + child: TextField( + controller: textController, + maxLines: null, + decoration: const InputDecoration( + border: OutlineInputBorder(), + hintText: 'Type something!', + ), + ), + ), + ), + ); + } +} diff --git a/web_embedding/ng-flutter/flutter/lib/src/js_interop.dart b/web_embedding/ng-flutter/flutter/lib/src/js_interop.dart new file mode 100644 index 0000000..2cd16ec --- /dev/null +++ b/web_embedding/ng-flutter/flutter/lib/src/js_interop.dart @@ -0,0 +1,5 @@ +/// Exposes useful functions to interop with JS from our Flutter app. +library; + +export 'js_interop/counter_state_manager.dart'; +export 'js_interop/helper.dart' show broadcastAppEvent; diff --git a/web_embedding/ng-flutter/flutter/lib/src/js_interop/counter_state_manager.dart b/web_embedding/ng-flutter/flutter/lib/src/js_interop/counter_state_manager.dart new file mode 100644 index 0000000..6990fe0 --- /dev/null +++ b/web_embedding/ng-flutter/flutter/lib/src/js_interop/counter_state_manager.dart @@ -0,0 +1,83 @@ +import 'dart:js_interop'; + +import 'package:flutter/foundation.dart'; + +enum DemoScreen { + counter('counter'), + text('text'), + dash('dash'); + + const DemoScreen(String screen) : _screen = screen; + final String _screen; + + @override + String toString() => _screen; +} + +/// This is the bit of state that JS is able to see. +/// +/// It contains getters/setters/operations and a mechanism to +/// subscribe to change notifications from an incoming [notifier]. +@JSExport() +class DemoAppStateManager { + // Creates a DemoAppStateManager wrapping a ValueNotifier. + DemoAppStateManager({ + required ValueNotifier screen, + required ValueNotifier counter, + required ValueNotifier text, + }) : _counter = counter, + _text = text, + _screen = screen; + + final ValueNotifier _screen; + final ValueNotifier _counter; + final ValueNotifier _text; + + // _counter + int getClicks() { + return _counter.value; + } + + void setClicks(int value) { + _counter.value = value; + } + + void incrementClicks() { + _counter.value++; + } + + void decrementClicks() { + _counter.value--; + } + + // _text + void setText(String text) { + _text.value = text; + } + + String getText() { + return _text.value; + } + + // _screen + void setScreen(String screen) { + _screen.value = DemoScreen.values.byName(screen); + } + + String getScreen() { + return _screen.value.toString(); + } + + // Allows clients to subscribe to changes to the wrapped value. + void onClicksChanged(VoidCallback f) { + _counter.addListener(f); + } + + void onTextChanged(VoidCallback f) { + _text.addListener(f); + } + + void onScreenChanged(VoidCallback f) { + _screen.addListener(f); + } +} diff --git a/web_embedding/ng-flutter/flutter/lib/src/js_interop/helper.dart b/web_embedding/ng-flutter/flutter/lib/src/js_interop/helper.dart new file mode 100644 index 0000000..05b4c28 --- /dev/null +++ b/web_embedding/ng-flutter/flutter/lib/src/js_interop/helper.dart @@ -0,0 +1,16 @@ +import 'dart:js_interop'; +import 'package:web/web.dart'; + +/// Locates the root of the flutter app (for now, the first element that has +/// a flt-renderer tag), and dispatches a JS event named [name] with [data]. +void broadcastAppEvent(String name, JSObject data) { + final HTMLElement? root = + document.querySelector('[flt-renderer]') as HTMLElement?; + assert(root != null, 'Flutter root element cannot be found!'); + + final eventDetails = CustomEventInit(detail: data); + eventDetails.bubbles = true; + eventDetails.composed = true; + + root!.dispatchEvent(CustomEvent(name, eventDetails)); +} diff --git a/web_embedding/ng-flutter/flutter/pubspec.yaml b/web_embedding/ng-flutter/flutter/pubspec.yaml new file mode 100644 index 0000000..692990b --- /dev/null +++ b/web_embedding/ng-flutter/flutter/pubspec.yaml @@ -0,0 +1,25 @@ +name: ng_companion +description: A flutter app with a counter that can be manipulated from JS. +publish_to: none +version: 1.0.0 +resolution: workspace + +environment: + sdk: ^3.9.0-0 + +dependencies: + flutter: + sdk: flutter + web: ^1.0.0 + +dev_dependencies: + analysis_defaults: + path: ../../../analysis_defaults + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 + +flutter: + uses-material-design: true + assets: + - assets/ diff --git a/web_embedding/ng-flutter/flutter/web/favicon.png b/web_embedding/ng-flutter/flutter/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/web_embedding/ng-flutter/flutter/web/favicon.png differ diff --git a/web_embedding/ng-flutter/flutter/web/flutter_bootstrap.js b/web_embedding/ng-flutter/flutter/web/flutter_bootstrap.js new file mode 100644 index 0000000..e69de29 diff --git a/web_embedding/ng-flutter/flutter/web/icons/Icon-192.png b/web_embedding/ng-flutter/flutter/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/web_embedding/ng-flutter/flutter/web/icons/Icon-192.png differ diff --git a/web_embedding/ng-flutter/flutter/web/icons/Icon-512.png b/web_embedding/ng-flutter/flutter/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/web_embedding/ng-flutter/flutter/web/icons/Icon-512.png differ diff --git a/web_embedding/ng-flutter/flutter/web/icons/Icon-maskable-192.png b/web_embedding/ng-flutter/flutter/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/web_embedding/ng-flutter/flutter/web/icons/Icon-maskable-192.png differ diff --git a/web_embedding/ng-flutter/flutter/web/icons/Icon-maskable-512.png b/web_embedding/ng-flutter/flutter/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/web_embedding/ng-flutter/flutter/web/icons/Icon-maskable-512.png differ diff --git a/web_embedding/ng-flutter/flutter/web/index.html b/web_embedding/ng-flutter/flutter/web/index.html new file mode 100644 index 0000000..bb8a10b --- /dev/null +++ b/web_embedding/ng-flutter/flutter/web/index.html @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + ng_companion + + + + + + + + + + diff --git a/web_embedding/ng-flutter/flutter/web/manifest.json b/web_embedding/ng-flutter/flutter/web/manifest.json new file mode 100644 index 0000000..7717b50 --- /dev/null +++ b/web_embedding/ng-flutter/flutter/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "ng_companion", + "short_name": "ng_companion", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/web_embedding/ng-flutter/package.json b/web_embedding/ng-flutter/package.json new file mode 100644 index 0000000..9ab7b9d --- /dev/null +++ b/web_embedding/ng-flutter/package.json @@ -0,0 +1,42 @@ +{ + "name": "ng-flutter", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "prebuild": "pushd flutter && flutter clean && flutter build web && popd", + "start": "ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test" + }, + "private": true, + "dependencies": { + "@angular/animations": "^22.0.2", + "@angular/cdk": "^22.0.0", + "@angular/common": "^22.0.1", + "@angular/compiler": "^22.0.2", + "@angular/core": "^22.0.2", + "@angular/forms": "^22.0.0", + "@angular/material": "^22.0.2", + "@angular/platform-browser": "^22.0.1", + "@angular/platform-browser-dynamic": "^22.0.0", + "@angular/router": "^22.0.1", + "rxjs": "~7.8.1", + "tslib": "^2.6.2", + "zone.js": "~0.15.0" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^22.0.1", + "@angular/cli": "~22.0.2", + "@angular/compiler-cli": "^22.0.1", + "@types/jasmine": "~6.0.0", + "jasmine-core": "~7.0.1", + "karma": "~6.4.2", + "karma-chrome-launcher": "~3.2.0", + "karma-coverage": "~2.2.0", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.1.0", + "typescript": "~7.0.2" + }, + "sideEffects": false +} diff --git a/web_embedding/ng-flutter/src/app/app.component.spec.ts b/web_embedding/ng-flutter/src/app/app.component.spec.ts new file mode 100644 index 0000000..50fecba --- /dev/null +++ b/web_embedding/ng-flutter/src/app/app.component.spec.ts @@ -0,0 +1,35 @@ +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + RouterTestingModule + ], + declarations: [ + AppComponent + ], + }).compileComponents(); + }); + + it('should create the app', () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title 'ng-flutter'`, () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app.title).toEqual('ng-flutter'); + }); + + it('should render title', () => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.nativeElement as HTMLElement; + expect(compiled.querySelector('.content span')?.textContent).toContain('ng-flutter app is running!'); + }); +}); diff --git a/web_embedding/ng-flutter/src/app/app.component.ts b/web_embedding/ng-flutter/src/app/app.component.ts new file mode 100644 index 0000000..c70cb9d --- /dev/null +++ b/web_embedding/ng-flutter/src/app/app.component.ts @@ -0,0 +1,176 @@ +import { ChangeDetectorRef, Component } from '@angular/core'; +import { NgFlutterComponent } from './ng-flutter/ng-flutter.component'; +import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { CommonModule } from '@angular/common'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatIconModule } from '@angular/material/icon'; +import { MatListModule } from '@angular/material/list'; +import { MatCardModule } from '@angular/material/card'; +import { MatSliderModule } from '@angular/material/slider'; +import { MatButtonModule } from '@angular/material/button'; +import { MatSelectModule } from '@angular/material/select'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; + +@Component({ + standalone: true, + selector: 'app-root', + template: ` + + + Angular 🀝 Flutter + + + + + + +
+

Effects

+
+ + + + +
+
+ +
+

JS Interop

+ + Screen + + Counter + TextField + Custom App + + + @if (this.flutterState?.getScreen() === 'counter') { + + Clicks + + + } @else { + + Text + + @if (this.flutterState?.getText()) { + + } + + } +
+
+
+ + +
+ +
+
+
+`, + styles: [` + :host{ + display: flex; + height: 100%; + flex-direction: column; + } + .toolbar-spacer { + flex: 1 1 auto; + } + .sidenav-container { + flex: 1; + } + .sidenav { + width: 300px; + padding: 10px; + } + .button-list { + display: flex; + flex-wrap: wrap; + gap: 5px; + margin-bottom: 20px; + } + .button-list button { + min-width: 130px; + } + .sidenav-content { + display: flex; + justify-content: center; + align-items: center; + } + .flutter-app { + border: 1px solid #eee; + border-radius: 5px; + height: 480px; + width: 320px; + transition: all 150ms ease-in-out; + overflow: hidden; + } + `], + imports: [ + NgFlutterComponent, + MatToolbarModule, + MatSidenavModule, + MatSidenavModule, + MatIconModule, + CommonModule, + MatListModule, + MatCardModule, + MatSliderModule, + MatButtonModule, + MatFormFieldModule, + MatSelectModule, + MatInputModule, + ], +}) +export class AppComponent { + title = 'ng-flutter'; + flutterState?: any; + + constructor(private changeDetectorRef: ChangeDetectorRef, private breakpointObserver: BreakpointObserver) { } + + onFlutterAppLoaded(state: any) { + this.flutterState = state; + this.flutterState.onClicksChanged(() => { this.onCounterChanged() }); + this.flutterState.onTextChanged(() => { this.onTextChanged() }); + } + + onCounterSet(event: Event) { + let clicks = parseInt((event.target as HTMLInputElement).value, 10) || 0; + this.flutterState.setClicks(clicks); + } + + onTextSet(event: Event) { + this.flutterState.setText((event.target as HTMLInputElement).value || ''); + } + + // I need to force a change detection here. When clicking on the "Decrement" + // button, everything works fine, but clicking on Flutter doesn't trigger a + // repaint (even though this method is called) + onCounterChanged() { + this.changeDetectorRef.detectChanges(); + } + + onTextChanged() { + this.changeDetectorRef.detectChanges(); + } +} diff --git a/web_embedding/ng-flutter/src/app/ng-flutter/ng-flutter.component.spec.ts b/web_embedding/ng-flutter/src/app/ng-flutter/ng-flutter.component.spec.ts new file mode 100644 index 0000000..b0720c4 --- /dev/null +++ b/web_embedding/ng-flutter/src/app/ng-flutter/ng-flutter.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NgFlutterComponent } from './ng-flutter.component'; + +describe('NgFlutterComponent', () => { + let component: NgFlutterComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ NgFlutterComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(NgFlutterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/web_embedding/ng-flutter/src/app/ng-flutter/ng-flutter.component.ts b/web_embedding/ng-flutter/src/app/ng-flutter/ng-flutter.component.ts new file mode 100644 index 0000000..1895060 --- /dev/null +++ b/web_embedding/ng-flutter/src/app/ng-flutter/ng-flutter.component.ts @@ -0,0 +1,65 @@ +import { Component, AfterViewInit, SimpleChanges, ViewChild, ElementRef, Input, EventEmitter, Output } from '@angular/core'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; + +// The global _flutter namespace +declare var _flutter: any; +declare var window: { + _debug: any +}; + +@Component({ + selector: 'ng-flutter', + standalone: true, + template: ` +
+
+ +
+
+ `, + styles: [` + :host div { + width: 100%; + height: 100%; + } + .spinner { + display: flex; + justify-content: center; + align-items: center; + }`, + ], + imports: [ + MatProgressSpinnerModule, + ], +}) +export class NgFlutterComponent implements AfterViewInit { + // The target that will host the Flutter app. + @ViewChild('flutterTarget') flutterTarget!: ElementRef; + + @Input() src: String = 'main.dart.js'; + @Input() assetBase: String = ''; + @Output() appLoaded: EventEmitter = new EventEmitter(); + + ngAfterViewInit(): void { + const target: HTMLElement = this.flutterTarget.nativeElement; + + _flutter.loader.loadEntrypoint({ + entrypointUrl: this.src, + onEntrypointLoaded: async (engineInitializer: any) => { + let appRunner = await engineInitializer.initializeEngine({ + hostElement: target, + assetBase: this.assetBase, + }); + await appRunner.runApp(); + } + }); + + target.addEventListener("flutter-initialized", (event: Event) => { + let state = (event as CustomEvent).detail; + window._debug = state; + this.appLoaded.emit(state); + }, { + once: true, + }); + } +} diff --git a/web_embedding/ng-flutter/src/assets/.gitkeep b/web_embedding/ng-flutter/src/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/web_embedding/ng-flutter/src/favicon.ico b/web_embedding/ng-flutter/src/favicon.ico new file mode 100644 index 0000000..997406a Binary files /dev/null and b/web_embedding/ng-flutter/src/favicon.ico differ diff --git a/web_embedding/ng-flutter/src/index.html b/web_embedding/ng-flutter/src/index.html new file mode 100644 index 0000000..765e24b --- /dev/null +++ b/web_embedding/ng-flutter/src/index.html @@ -0,0 +1,17 @@ + + + + + NgFlutter + + + + + + + + + + + + diff --git a/web_embedding/ng-flutter/src/main.ts b/web_embedding/ng-flutter/src/main.ts new file mode 100644 index 0000000..bde2b83 --- /dev/null +++ b/web_embedding/ng-flutter/src/main.ts @@ -0,0 +1,14 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { provideRouter, Routes } from '@angular/router'; +import { AppComponent } from './app/app.component'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { importProvidersFrom } from '@angular/core'; + +const appRoutes: Routes = []; + +bootstrapApplication(AppComponent, { + providers: [ + provideRouter(appRoutes), + importProvidersFrom(BrowserAnimationsModule) + ] +}) diff --git a/web_embedding/ng-flutter/src/styles.css b/web_embedding/ng-flutter/src/styles.css new file mode 100644 index 0000000..efe0dc1 --- /dev/null +++ b/web_embedding/ng-flutter/src/styles.css @@ -0,0 +1,54 @@ +html, body { height: 100%; } +body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } + +/* FX */ +.fx-resize { + width: 480px !important; + height: 320px !important; +} +.fx-spin { animation: spin 6400ms ease-in-out infinite; } +.fx-shadow { position: relative; overflow: visible !important; } +.fx-shadow::before { + content: ""; + position: absolute; + display: block; + width: 100%; + top: calc(100% - 1px); + left: 0; + height: 1px; + background-color: black; + border-radius: 50%; + z-index: -1; + transform: rotateX(80deg); + box-shadow: 0px 0px 60px 38px rgb(0 0 0 / 25%); +} +.fx-mirror { + -webkit-box-reflect: below 0px linear-gradient(to bottom, rgba(0,0,0,0.0), rgba(0,0,0,0.4)); +} + +@keyframes spin { + 0% { + transform: perspective(1000px) rotateY(0deg); + animation-timing-function: ease-in-out; + } + 10% { + transform: perspective(1000px) rotateY(0deg); + animation-timing-function: ease-in-out; + } + 40% { + transform: perspective(1000px) rotateY(180deg); + animation-timing-function: ease-in-out; + } + 60% { + transform: perspective(1000px) rotateY(180deg); + animation-timing-function: ease-in-out; + } + 90% { + transform: perspective(1000px) rotateY(359deg); + animation-timing-function: ease-in-out; + } + 100% { + transform: perspective(1000px) rotateY(360deg); + animation-timing-function: ease-in-out; + } +} \ No newline at end of file diff --git a/web_embedding/ng-flutter/tsconfig.app.json b/web_embedding/ng-flutter/tsconfig.app.json new file mode 100644 index 0000000..374cc9d --- /dev/null +++ b/web_embedding/ng-flutter/tsconfig.app.json @@ -0,0 +1,14 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "files": [ + "src/main.ts" + ], + "include": [ + "src/**/*.d.ts" + ] +} diff --git a/web_embedding/ng-flutter/tsconfig.json b/web_embedding/ng-flutter/tsconfig.json new file mode 100644 index 0000000..ed966d4 --- /dev/null +++ b/web_embedding/ng-flutter/tsconfig.json @@ -0,0 +1,33 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "sourceMap": true, + "declaration": false, + "downlevelIteration": true, + "experimentalDecorators": true, + "moduleResolution": "node", + "importHelpers": true, + "target": "ES2022", + "module": "ES2022", + "useDefineForClassFields": false, + "lib": [ + "ES2022", + "dom" + ] + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/web_embedding/ng-flutter/tsconfig.spec.json b/web_embedding/ng-flutter/tsconfig.spec.json new file mode 100644 index 0000000..be7e9da --- /dev/null +++ b/web_embedding/ng-flutter/tsconfig.spec.json @@ -0,0 +1,14 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": [ + "jasmine" + ] + }, + "include": [ + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +}