diff --git a/.github/GIT_RULES.md b/.github/GIT_RULES.md new file mode 100644 index 000000000..584dacbfc --- /dev/null +++ b/.github/GIT_RULES.md @@ -0,0 +1,212 @@ +## Commit Rules: +This commits rules is set to ensure all the developers follows a uniform way of writing commits so that it is easy to read the changes made and also automate versioning. +The commits are based on [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) which is a widely followed convention for commits. While writing a commit message to any changes made to code base, make sure it reflects the changes and follows the conventions i.e: + +``` +change_type(package_name): short description/subject line... + +Long Description... +``` + +Here, + +change_type means the type of changes like `feat`,`fix` and so on. + +package_name means which packages this changes is for. This is optional. + +short discription/subject line means about the changes + +Long Description are optional. Add it if its required to reflect the changes in more detail. + +For eg: + +Lets say the changes is about implementing a new feature which is about new social login mechanism in `deriv_auth` package. + +``` +feat(deriv_auth): add UI for sign in page + +- create reusable text field using global app theme +- add bloc for sign in logic implementation +- add google services for authentication +``` +More changes types: +| Changes Types | Meaning | Description | +| ------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------- | +| feat | Features | A new feature | +| fix | Bug Fixes | A bug fix | +| chore | Chores | Other changes that don’t modify src or test files | +| docs | Docume­ntation | Docume­ntation only changes | +| revert | Reverts | Reverts a previous commit | +| refactor | Code Refact­oring | A code change that neither fixes a bug nor adds a feature | +| test | Tests | Adding missing tests or correcting existing tests | +| style | Styles | Changes that do not affect the meaning of the code (white­ -space, format­ting, missing semi-c­olons, etc) | +| perf | Perfor­mance Improv­ements | A code change that improves perfor­mance | +| build | Builds | Changes that affect the build system or external depend­encies (example scopes: gulp, broccoli, npm) | +| ci | Continuous Integr­ations | Changes to our CI config­uration files and scripts (example scopes: Travis, Circle, Browse­rStack, SauceLabs) | + +Optional: If you would like to pre populate your commit box with the commit template then you can do it by adding `.gitmessage` inside `.github` folder to commit template for that project with the following command.(Note: This won't work if you write commit via terminal.) + +`git config commit.template "YOUR_PROJECT_PATH/.github/.gitmessage"` + +### Examples +* ``` + feat: add email notifications on new direct messages + ``` +* ``` + feat(shopping cart): add the amazing button + ``` +* ``` + feat!: remove ticket list endpoint + + refers to JIRA-1337 + + BREAKING CHANGES: ticket enpoints no longer supports list all entites. + ``` +* ``` + fix(api): handle empty message in request body + ``` +* ``` + fix(api): fix wrong calculation of request body checksum + ``` +* ``` + fix: add missing parameter to service call + + The error occurred because of . + ``` +* ``` + perf: decrease memory footprint for determine uniqe visitors by using HyperLogLog + ``` +* ``` + build: update dependencies + ``` +* ``` + build(release): `bump version to 1.0.0 + ``` +* ``` + refactor: implement fibonacci number calculation as recursion + ``` +* ``` + style: remove empty line + ``` +### Breaking Changes Indicator +Breaking changes should be indicated by an + ! +before the `:` in the subject line e.g. `feat(api)!: remove status endpoint` +* Is an **optional** part of the format + + +

 

+ +## Semantic versioning: +
1 . 4 . 3 + 2
+
Major . Minor . Patch + build
+

 

+ +Major Release: +If a ```“breaking change”``` is introduced, the major release number must be increased + +``` +feat(api)!: remove status endpoint +fix!: bug fix with breaking change +``` + +Minor Release: +New features have been introduced, which are backwards compatible ```no “breaking changes”``` + + +``` +feat: new feature +``` + +Patch Release: +Bug fixes ```no “breaking changes”```
+``` +fix: something in code +refactor: code changes that doesn't fix or add anything +``` + +Build: This number is optional and can be used to differentiate between different builds of the same version. +
+ +no change | | build bump: +``` +build, chore, ci, docs, style, perf, test +``` +

 

+ +## PR Rules: + +This Rules is set to create a uniform way of submitting Pull requests where all the necessary information for the changes are listed in the title, or description. There is a standard template for creating PR. When you are creating a PR to any repo always make sure: + +- you have titled it following conventional pattern and also included all the necessary information in it. + for eg: + Title: `feat(deriv_auth): [MOBC-299] Add ability to sign in with google` + + Here,
The title defines: + + - what type the changes/PR is about like feature, bug fixes, documentations, refactor.
+ - which package/app is this PR for.(optional)
+ - the clickup card id - short title that describes the changes
+ +- you have added detailed description that gives enough information about the PR. +- you have commits only relevant to your changes and not other's commit which shouldn't be there. + + Also, it is always better to squash commits that are only relevant to any particular PR. Such as changes requested through PR review. If these changes does not reflects any really meaning or value in the main project history then it is always better to squash such commits into one. It helps to maintain a clean commit history. + + **The template looks something like this:** + + + + **Clickup link:** + **Fixes issue:** # + + This PR contains the following changes: + + + + - [ ] ✨ New feature (non-breaking change which adds functionality) + - [ ] 🛠️ Bug fix (non-breaking change which fixes an issue) + - [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change) + - [ ] 🧹 Code refactor + - [ ] ✅ Build configuration change + - [ ] 📝 Documentation + - [ ] 🗑️ Chore + + ### Developers Note (Optional) + + + + ## Pre-launch Checklist (For PR creator) + + As a creator of this PR: + + + + - [ ] ✍️ I have included clickup id and package/app_name in the PR title. + - [ ] 👁️ I have gone through the code and removed any temporary changes (commented lines, prints, debug statements etc.). + - [ ] ⚒️ I have fixed any errors/warnings shown by the analyzer/linter. + - [ ] 📝 I have added documentation, comments and logging wherever required. + - [ ] 🧪 I have added necessary tests for these changes. + - [ ] 🔎 I have ensured all existing tests are passing. + + ## Reviewers + + + + ## Pre-launch Checklist (For Reviewers) + + As a reviewer I ensure that: + + - [ ] ✴️ This PR follows the standard PR template. + - [ ] ✴️ The information in this PR properly reflects the code changes. + - [ ] 🧪 All the necessary tests for this PR's are passing. + + ## Pre-launch Checklist (For QA) + + - [ ] 👌 It passes the acceptance criteria. + + ## Pre-launch Checklist (For Maintainer) + + - [ ] [MAINTAINER_NAME] I make sure this PR fulfills its purpose. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..97a4ff65f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,69 @@ +--- +name: ⚠️ Bug Report +about: Create a report to help us improve our packages, including regressions +title: '⚠️ [Package Name] - Brief description of the issue' +labels: 'bug' +assignees: '' + +--- + +### Prerequisites to Check + +- Have you checked that this issue hasn't been already reported? +- Are you using the latest version of our package? +- Have you checked the documentation for this feature/issue? + +### Issue Type + +- [ ] General Bug +- [ ] Regression + +If this is a regression, please provide: +- Previous package version where the feature worked: +- Current package version where the regression occurs: + +### Package Version and Flutter Environment + +- Package Version: +- Flutter (Output of `flutter doctor`): + +### Affected Application(s) + +- [ ] Deriv Go +- [ ] Deriv P2P +- [ ] Both +- [ ] Not Applicable + +### Description + +Briefly describe the issue you are experiencing. Include any error messages or screenshots that can help understand the problem. For regressions, describe what functionality is affected and how the behavior has changed from previous versions. + +### Steps to Reproduce + +1. Step one to reproduce +2. Step two... +3. (and so on) + +### Expected Behavior + +What did you expect to happen? For regressions, describe what the correct behavior should be, as it was in the previous working versions. + +### Actual Behavior + +What actually happened? Explain the current behavior that you consider a bug or a regression. + +### Code Snippet + +```dart +// If possible, please provide a code snippet that demonstrates the issue. +``` + +### Can this issue be consistently reproduced? + +- [ ] Yes +- [ ] No +- [ ] Not applicable + +### Additional Information + +Any additional information, configuration, or data that might be necessary to reproduce the issue. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/documentation_update_request.md b/.github/ISSUE_TEMPLATE/documentation_update_request.md new file mode 100644 index 000000000..f53d2f70a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation_update_request.md @@ -0,0 +1,35 @@ +--- +name: 📚 Documentation Update Request +about: Suggest improvements or report issues in the documentation +title: '📚 [Docs][Package Name] Summary of the request' +labels: 'documentation' +assignees: '' + +--- + +### Documentation Scope + +- [ ] General Documentation (repository-level README.md) +- [ ] Specific Package Documentation (Please ensure the package name is included in the title above) + +### Documentation Location + +Provide a link or reference to the documentation page or section in question. If this is about a specific package's documentation, please include the path or link to the specific `README.md` file. + +### Is this an issue, improvement, or expansion request? + +- [ ] Issue - The documentation is incorrect or misleading +- [ ] Improvement - The documentation could be clearer or more detailed +- [ ] Expansion - Additional information or topics are needed + +### Describe the problem or your suggestion + +A clear and concise description of the issue you found or the improvement you are suggesting. If you are reporting an inaccuracy, please describe what is wrong or misleading. + +### Proposed Changes + +If you have specific ideas on how to improve or correct the documentation, please outline them here. Include text snippets, markdown, or any other content that you think should be included or modified. + +### Additional context + +Add any other context about the problem or your suggestion here, such as why the change is important or how it would benefit users of the documentation. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..02b170d3b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,40 @@ +--- +name: 🚀 Feature Request +about: Suggest a new feature or a new package +title: '🚀 [Feature/Package] Short description' +labels: 'enhancement' +assignees: '' + +--- + +### Request Type + +- [ ] New Feature in Existing Package +- [ ] New Package Request + +### Is your feature request related to a problem? Please describe. + +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +### Describe the solution you'd like + +A clear and concise description of what you want to happen. If you are proposing a new package, please explain the purpose and potential functionalities. + +### Describe alternatives you've considered + +A clear and concise description of any alternative solutions or features you've considered. + +### Affected Package(s) + +If this is about a new feature in an existing package, please specify the package(s). If this is a new package request, mention 'N/A' or 'New Package'. + +### Affected Application(s) + +- [ ] Deriv Go +- [ ] Deriv P2P +- [ ] Both +- [ ] Not Applicable + +### Additional context + +Add any other context, mockups, diagrams, or code snippets about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/performance_issue.md b/.github/ISSUE_TEMPLATE/performance_issue.md new file mode 100644 index 000000000..688420cb5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/performance_issue.md @@ -0,0 +1,44 @@ +--- +name: ⚡ Performance Issue +about: Report a performance problem or bottleneck in our packages +title: '⚡ [Performance][Package Name] Brief description of the issue' +labels: 'performance' +assignees: '' + +--- + +### Affected Package(s) + +Please specify the package(s) this performance issue is related to. + +### Performance Issue Summary + +Provide a brief summary of the performance issue you're experiencing. Include any specific symptoms or error messages you're seeing. + +### Steps to Reproduce + +1. Describe the specific actions that lead to the performance issue. +2. Provide a minimal code snippet or setup that replicates the issue, if possible. + +### Expected vs. Actual Performance + +- Expected Performance: Describe what you would expect to happen under normal circumstances. +- Actual Performance: Describe the actual performance issue observed, including any quantifiable metrics or observations. + +### Environment Details + +- Flutter version (output of `flutter doctor`): +``` +// flutter doctor output +``` +- Device/Emulator specifications: +- OS version: +- Any other relevant environment details: + +### Performance Analysis (if any) + +If you have conducted any performance analysis or profiling, please include the results or summarize the findings. Screenshots, logs, or other attachments can be added below. + +### Additional context + +Add any other context about the problem here, such as the impact of the performance issue on your application or any workarounds you have tried. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..e708d44da --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,55 @@ + + +**Clickup link:** +**Fixes issue:** # + +This PR contains the following changes: + + + +- [ ] ✨ New feature (non-breaking change which adds functionality) +- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue) +- [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change) +- [ ] 🧹 Code refactor +- [ ] ✅ Build configuration change +- [ ] 📝 Documentation +- [ ] 🗑️ Chore + +### Developers Note (Optional) + + + +## Pre-launch Checklist (For PR creator) + +As a creator of this PR: + + + +- [ ] ✍️ I have included clickup id and package/app_name in the PR title. +- [ ] 👁️ I have gone through the code and removed any temporary changes (commented lines, prints, debug statements etc.). +- [ ] ⚒️ I have fixed any errors/warnings shown by the analyzer/linter. +- [ ] 📝 I have added documentation, comments and logging wherever required. +- [ ] 🧪 I have added necessary tests for these changes. +- [ ] 🔎 I have ensured all existing tests are passing. + +## Reviewers + + + +## Pre-launch Checklist (For Reviewers) + +As a reviewer I ensure that: + +- [ ] ✴️ This PR follows the standard PR template. +- [ ] 🪩 The information in this PR properly reflects the code changes. +- [ ] 🧪 All the necessary tests for this PR's are passing. + +## Pre-launch Checklist (For QA) + +- [ ] 👌 It passes the acceptance criteria. + +## Pre-launch Checklist (For Maintainer) + +- [ ] [MAINTAINER_NAME] I make sure this PR fulfills its purpose. diff --git a/.github/actions/send_slack_notifications/action.yml b/.github/actions/send_slack_notifications/action.yml new file mode 100644 index 000000000..05fafe60b --- /dev/null +++ b/.github/actions/send_slack_notifications/action.yml @@ -0,0 +1,49 @@ +name: send_slack_notifications +description: Send Slack notifications +inputs: + SLACK_WEBHOOK_PACKAGE_UPDATE: + description: "Slack webhook URL" + required: true + PR_TITLE: + description: "Pull request title" + required: true + TAGS: + description: "New tags released" + required: true +runs: + using: composite + steps: + - name: Send Slack Notification on Package Update + env: + SLACK_WEBHOOK_PACKAGE_UPDATE: ${{ inputs.SLACK_WEBHOOK_PACKAGE_UPDATE }} + PR_TITLE: ${{ inputs.PR_TITLE }} + TAGS: ${{ inputs.TAGS }} + run: | + curl -X POST -H 'Content-type: application/json' \ + --data "{ + \"blocks\": [ + { + \"type\": \"section\", + \"text\": { + \"type\": \"mrkdwn\", + \"text\": \" *New Package Update*\" + } + }, + { + \"type\": \"section\", + \"text\": { + \"type\": \"mrkdwn\", + \"text\": \"*$PR_TITLE*\" + } + }, + { + \"type\": \"section\", + \"text\": { + \"type\": \"mrkdwn\", + \"text\": \"*Packages released:*\n- *$TAGS*\n*Changelog:* https://github.com/regentmarkets/flutter-deriv-packages/blob/master/CHANGELOG.md\" + } + } + ] + }" \ + $SLACK_WEBHOOK_PACKAGE_UPDATE + shell: bash diff --git a/.github/workflows/all_packages.yaml b/.github/workflows/all_packages.yaml new file mode 100644 index 000000000..f7282a35f --- /dev/null +++ b/.github/workflows/all_packages.yaml @@ -0,0 +1,46 @@ +name: all_packages + +on: + push: + branches: + - master + - dev + pull_request: + branches: + - "*" + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + analyze_and_test: + runs-on: ubuntu-latest + steps: + - name: Git Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + + - name: Setup Flutter + uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa + with: + channel: "stable" + flutter-version: "3.24.1" + cache: true + + - name: Set SSH Key + uses: webfactory/ssh-agent@fd34b8dee206fe74b288a5e61bc95fba2f1911eb + with: + ssh-private-key: | + ${{ secrets.SSH_PRIVATE_KEY }} + ${{ secrets.SSH_CHART_PRIVATE_KEY }} + + - name: Install Melos and run pub get + uses: bluefireteam/melos-action@dd3c344d731938d2ab2567a261f54a19a68b5f6a + with: + melos-version: "3.0.1" + + - name: Run Analyze + run: melos run analyze + + - name: Run Test + run: melos run test --no-select diff --git a/.github/workflows/check_versioning_pr.yml b/.github/workflows/check_versioning_pr.yml new file mode 100644 index 000000000..d75d7d500 --- /dev/null +++ b/.github/workflows/check_versioning_pr.yml @@ -0,0 +1,62 @@ +name: versioning_pr_exists + +on: + pull_request: + types: [opened, edited, reopened, synchronize] + +jobs: + check-version-pr: + runs-on: ubuntu-latest + if: ${{ ! startsWith(github.event.pull_request.title , 'chore(version)') }} + steps: + - name: Checkout repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + + - name: Set up Node.js + uses: actions/setup-node@eff380dfbcf941bf8832e4acb788cebe13dfd758 + with: + node-version: "14" + + - name: Check if PR with title chore(version) exists + id: check-pr + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea + with: + github-token: ${{ secrets.PAT }} + script: | + const { data: pullRequests } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + per_page: 100 + }); + + const existingPR = pullRequests.find(pr => pr.title === 'chore(version): bump version and update changelog'); + + if (existingPR) { + core.setOutput("VERSIONING_PR_EXISTS", true); + console.log("Version bump pull request exists. Please merge that pr first to pass the check.: #${existingPR.number}"); + } else { + core.setOutput("VERSIONING_PR_EXISTS", false); + console.log("No version bump pull request found."); + } + + - name: Delete comment if exists + uses: izhangzhihao/delete-comment@d075704468e1cf74e60944d9f335351213c34d85 + with: + github_token: ${{ secrets.PAT }} + delete_user_name: mobile-apps-deriv + issue_number: ${{ github.event.number }} + + - name: Comment if versioning PR exists + if: ${{ contains(steps.check-pr.outputs.VERSIONING_PR_EXISTS, 'true') }} + uses: marocchino/sticky-pull-request-comment@4b7290acd5c5b99ef9995db30e52150e705d2475 + with: + GITHUB_TOKEN: ${{ secrets.PAT }} + message: | + **Merge Is BLOCKED : we still have a chore(version) pr open, please merge that pr first ** + + - name: Fail if versioning PR exists + if: ${{ contains(steps.check-pr.outputs.VERSIONING_PR_EXISTS, 'true') }} + run: | + echo "A PR with the title 'chore(version)' already exists. Exiting." + exit 1 diff --git a/.github/workflows/issue_created.yml b/.github/workflows/issue_created.yml new file mode 100644 index 000000000..78ca4977a --- /dev/null +++ b/.github/workflows/issue_created.yml @@ -0,0 +1,45 @@ +name: issue_created + +on: + issues: + types: [opened] + +env: + issue_title: ${{ github.event.issue.title }} + +jobs: + send-issue-created-slack-message: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + + - name: Setup jq + run: sudo apt-get install jq + + - name: Send Slack Notification on Github Issue Opened + run: | + LABELS=$(echo '${{ toJson(github.event.issue.labels.*.name) }}' | jq -r 'join(", ")') + curl -X POST -H 'Content-type: application/json' \ + --data "{ + \"text\": \"Issue Created\", + \"blocks\": [ + { + \"type\": \"section\", + \"text\": { + \"type\": \"mrkdwn\", + \"text\": \" *New Github Issue*\" + } + }, + { + \"type\": \"section\", + \"text\": { + \"type\": \"mrkdwn\", + \"text\": \"*Issue Creator:* ${{ github.event.issue.user.login }}\n*Issue Title:* $issue_title\n*Issue URL:* ${{ github.event.issue.html_url }}\n*Issue Labels:* $LABELS\" + } + } + ] + }" \ + ${{ secrets.SLACK_WEBHOOK_ISSUES }} + shell: bash + diff --git a/.github/workflows/localization.yml b/.github/workflows/localization.yml new file mode 100644 index 000000000..7107bf766 --- /dev/null +++ b/.github/workflows/localization.yml @@ -0,0 +1,48 @@ +name: Localization Update + +on: + pull_request: + branches: + - master + types: [closed] + +jobs: + update-localizations: + defaults: + run: + working-directory: ./packages/deriv_localizations + if: github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'crowdin' + runs-on: ubuntu-latest + steps: + - name: Git Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + with: + ssh-key: ${{ secrets.SSH_PRIVATE_KEY }} + + - name: Setup Flutter + uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa + with: + channel: "stable" + flutter-version: "3.10.2" + cache: true + + - name: Setup Config + run: | + git config --global user.name 'mobile-apps-deriv' + git config --global user.email 'mobileapps@regentmarkets.com' + + - name: Make the script files executable + run: chmod +x l10n.sh + - name: Run the scripts + run: | + ./l10n.sh + env: + GITHUB_TOKEN: ${{ secrets.PAT }} + + - name: Create Pull Request. + uses: peter-evans/create-pull-request@76c6f5c20e2111bfee3cd30fae52a25e410f5efc + with: + token: ${{ secrets.PAT }} + title: "refactor(deriv_localizations): Crowdin Localization Generated" + branch: create-pull-request/localisation + base: master diff --git a/.github/workflows/pr_title.yaml b/.github/workflows/pr_title.yaml new file mode 100644 index 000000000..de2f476e3 --- /dev/null +++ b/.github/workflows/pr_title.yaml @@ -0,0 +1,19 @@ +name: pr_title + +on: + pull_request: + types: + - opened + - edited + - synchronize + +permissions: + pull-requests: read + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@e9fabac35e210fea40ca5b14c0da95a099eff26f + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/version.yaml b/.github/workflows/version.yaml new file mode 100644 index 000000000..37d18284a --- /dev/null +++ b/.github/workflows/version.yaml @@ -0,0 +1,93 @@ +name: version + +on: + pull_request: + types: + - closed + branches: + - master + +jobs: + version_and_tag: + runs-on: ubuntu-latest + if: > + github.event.pull_request.merged == true && + !startsWith(github.event.pull_request.title, 'chore') && + !startsWith(github.event.pull_request.title, 'ci') && + !startsWith(github.event.pull_request.title, 'add') + steps: + - name: Git Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + with: + ssh-key: ${{ secrets.SSH_PRIVATE_KEY }} + fetch-depth: 0 + + - name: Setup Git User + uses: fregante/setup-git-user@77c1b5542f14ab6db4b8462d6857e31deb988b09 + + - name: Setup Flutter + uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa + with: + channel: "stable" + flutter-version: "3.10.2" + cache: true + + - name: Setup Melos + uses: bluefireteam/melos-action@dd3c344d731938d2ab2567a261f54a19a68b5f6a + with: + melos-version: "3.2.0" + run-bootstrap: false + + - name: Create git tag based on version + run: melos version --all --yes + + - name: Get new tags + id: new-tags + run: | + local_tags=$(git tag) + remote_tags=$(git ls-remote --tags origin | cut -d/ -f3) + new_tags=$(comm -23 <(sort <<<"$local_tags") <(sort <<<"$remote_tags")) + + # Format new tags into a single line with '\n' between tags + formatted_tags=$(echo -e "$new_tags" | tr '\n' ' ') + + # Append the formatted tags to the file + echo -e "NEW_TAGS=$formatted_tags" >> "$GITHUB_OUTPUT" + shell: bash + + - name: Push tag + id: push-tag + run: | + output=$(git push --tags 2>&1) + + if [[ $output == *"Everything up-to-date"* ]]; then + echo "PUSH_OUTPUT=No package updated" >> $GITHUB_OUTPUT + else + echo "PUSH_OUTPUT=Packages updated" >> $GITHUB_OUTPUT + fi + shell: bash + + - name: Make the script files executable + if: ${{ contains(steps.push-tag.outputs.PUSH_OUTPUT, 'Packages updated') }} + run: chmod +x readme.sh + working-directory: ./scripts + + - name: Update README.md + if: ${{ contains(steps.push-tag.outputs.PUSH_OUTPUT, 'Packages updated') }} + run: bash readme.sh + working-directory: ./scripts + + - name: Send Slack Notification + uses: ./.github/actions/send_slack_notifications + if: ${{ contains(steps.push-tag.outputs.PUSH_OUTPUT, 'Packages updated') }} + with: + SLACK_WEBHOOK_PACKAGE_UPDATE: ${{ secrets.SLACK_WEBHOOK_PACKAGE_UPDATE }} + PR_TITLE: ${{ github.event.pull_request.title }} + TAGS: ${{ steps.new-tags.outputs.NEW_TAGS }} + + - name: Create Pull Request on updated changelog and pubspec file. + uses: peter-evans/create-pull-request@76c6f5c20e2111bfee3cd30fae52a25e410f5efc + with: + token: ${{ secrets.PAT }} + title: "chore(version): bump version and update changelog" + base: master diff --git a/.gitignore b/.gitignore index 6f645b362..86f284979 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ lib/basic_api/generated/*.json *.iws .idea/ +.fvm + # 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. @@ -24,10 +26,11 @@ lib/basic_api/generated/*.json .flutter-plugins .packages .pub-cache/ -pubspec.lock .pub/ /build/ coverage/ +/packages/**/pubspec_overrides.yaml +/packages/**/pubspec.lock gradlew.bat diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..59850cfea --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5265 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## 2024-11-13 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.1.8+1`](#deriv_mobile_chart_wrapper---v0181) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.1.8+1` + + - **FIX**: [DRGO-1367] Apply Chart Tool Globally for Indicators and Asset-Specific for Drawing Tools ([#900](https://github.com/regentmarkets/flutter-deriv-packages/issues/900)). ([8ea63c8e](https://github.com/regentmarkets/flutter-deriv-packages/commit/8ea63c8e0c3cc73f2420e7b0069b3b60b54bf9b4)) + + +## 2024-11-05 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_store_launcher` - `v0.0.2`](#deriv_store_launcher---v002) + +--- + +#### `deriv_store_launcher` - `v0.0.2` + + - **FEAT**(deriv_store_launcher): [DRGO-1284] Added new functionalities(launch app, ios support, isAppInstalled function) to deriv_store_launcher. ([#875](https://github.com/regentmarkets/flutter-deriv-packages/issues/875)). ([27066759](https://github.com/regentmarkets/flutter-deriv-packages/commit/27066759d5cb1f25e1ed8b942aa28b8e99666054)) + + +## 2024-11-01 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`update_checker` - `v3.1.1`](#update_checker---v311) + +--- + +#### `update_checker` - `v3.1.1` + + - **FIX**(update_checker): [DRGO-1343] handle "failed to fetch and activate remote config" error ([#871](https://github.com/regentmarkets/flutter-deriv-packages/issues/871)). ([06c68591](https://github.com/regentmarkets/flutter-deriv-packages/commit/06c68591aaa9146789d173f220761efa1eab0d50)) + + +## 2024-10-29 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`analytics` - `v4.1.0`](#analytics---v410) + - [`deriv_expandable_bottom_sheet` - `v0.0.2`](#deriv_expandable_bottom_sheet---v002) + - [`deriv_grouped_listview` - `v0.0.2`](#deriv_grouped_listview---v002) + - [`deriv_mobile_chart_wrapper` - `v0.1.8`](#deriv_mobile_chart_wrapper---v018) + - [`deriv_rudderstack` - `v1.2.0`](#deriv_rudderstack---v120) + - [`deriv_theme` - `v2.8.0`](#deriv_theme---v280) + - [`deriv_ui` - `v0.1.1`](#deriv_ui---v011) + - [`update_checker` - `v3.1.0`](#update_checker---v310) + - [`deriv_passkeys` - `v0.0.5+11`](#deriv_passkeys---v00511) + - [`deriv_auth` - `v7.0.7`](#deriv_auth---v707) + - [`deriv_numpad` - `v1.1.11`](#deriv_numpad---v1111) + - [`deriv_widgetbook` - `v0.0.2+35`](#deriv_widgetbook---v00235) + - [`deriv_date_range_picker` - `v0.0.1+12`](#deriv_date_range_picker---v00112) + - [`deriv_language_selector` - `v0.0.3+14`](#deriv_language_selector---v00314) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_passkeys` - `v0.0.5+11` + - `deriv_auth` - `v7.0.7` + - `deriv_numpad` - `v1.1.11` + - `deriv_widgetbook` - `v0.0.2+35` + - `deriv_date_range_picker` - `v0.0.1+12` + - `deriv_language_selector` - `v0.0.3+14` + +--- + +#### `analytics` - `v4.1.0` + + - **FEAT**(analytics): Add indicator events ([#846](https://github.com/regentmarkets/flutter-deriv-packages/issues/846)). ([1d30ddc9](https://github.com/regentmarkets/flutter-deriv-packages/commit/1d30ddc9c7510e280cc90fc7e1308b945d5758ed)) + +#### `deriv_expandable_bottom_sheet` - `v0.0.2` + + - **FEAT**(analytics): Add indicator events ([#846](https://github.com/regentmarkets/flutter-deriv-packages/issues/846)). ([1d30ddc9](https://github.com/regentmarkets/flutter-deriv-packages/commit/1d30ddc9c7510e280cc90fc7e1308b945d5758ed)) + +#### `deriv_grouped_listview` - `v0.0.2` + + - **FEAT**(analytics): Add indicator events ([#846](https://github.com/regentmarkets/flutter-deriv-packages/issues/846)). ([1d30ddc9](https://github.com/regentmarkets/flutter-deriv-packages/commit/1d30ddc9c7510e280cc90fc7e1308b945d5758ed)) + +#### `deriv_mobile_chart_wrapper` - `v0.1.8` + + - **FEAT**(analytics): Add indicator events ([#846](https://github.com/regentmarkets/flutter-deriv-packages/issues/846)). ([1d30ddc9](https://github.com/regentmarkets/flutter-deriv-packages/commit/1d30ddc9c7510e280cc90fc7e1308b945d5758ed)) + +#### `deriv_rudderstack` - `v1.2.0` + + - **FEAT**(analytics): Add indicator events ([#846](https://github.com/regentmarkets/flutter-deriv-packages/issues/846)). ([1d30ddc9](https://github.com/regentmarkets/flutter-deriv-packages/commit/1d30ddc9c7510e280cc90fc7e1308b945d5758ed)) + +#### `deriv_theme` - `v2.8.0` + + - **FEAT**(analytics): Add indicator events ([#846](https://github.com/regentmarkets/flutter-deriv-packages/issues/846)). ([1d30ddc9](https://github.com/regentmarkets/flutter-deriv-packages/commit/1d30ddc9c7510e280cc90fc7e1308b945d5758ed)) + +#### `deriv_ui` - `v0.1.1` + + - **FEAT**(analytics): Add indicator events ([#846](https://github.com/regentmarkets/flutter-deriv-packages/issues/846)). ([1d30ddc9](https://github.com/regentmarkets/flutter-deriv-packages/commit/1d30ddc9c7510e280cc90fc7e1308b945d5758ed)) + +#### `update_checker` - `v3.1.0` + + - **FEAT**(analytics): Add indicator events ([#846](https://github.com/regentmarkets/flutter-deriv-packages/issues/846)). ([1d30ddc9](https://github.com/regentmarkets/flutter-deriv-packages/commit/1d30ddc9c7510e280cc90fc7e1308b945d5758ed)) + + +## 2024-10-25 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.1.7+4`](#deriv_mobile_chart_wrapper---v0174) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.1.7+4` + + - **REFACTOR**(mobile_chart_wrapper): [DRGO-1222]update chart version ([#894](https://github.com/regentmarkets/flutter-deriv-packages/issues/894)). ([10f931f2](https://github.com/regentmarkets/flutter-deriv-packages/commit/10f931f25eef529cb5168b15143e3364d04f63ad)) + + +## 2024-10-23 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.1.7+3`](#deriv_mobile_chart_wrapper---v0173) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.1.7+3` + + - **FIX**(deriv_mobile_chart_wrapper): [DRGO-193] remove unimplemented settings button ([#890](https://github.com/regentmarkets/flutter-deriv-packages/issues/890)). ([095f96aa](https://github.com/regentmarkets/flutter-deriv-packages/commit/095f96aa10723358142f59dd71af29b98202d69f)) + + +## 2024-10-23 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.7.2`](#deriv_localizations---v172) + - [`deriv_mobile_chart_wrapper` - `v0.1.7+2`](#deriv_mobile_chart_wrapper---v0172) + - [`deriv_passkeys` - `v0.0.5+10`](#deriv_passkeys---v00510) + - [`deriv_auth` - `v7.0.6`](#deriv_auth---v706) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_passkeys` - `v0.0.5+10` + - `deriv_auth` - `v7.0.6` + +--- + +#### `deriv_localizations` - `v1.7.2` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#887](https://github.com/regentmarkets/flutter-deriv-packages/issues/887)). ([ba1b75b8](https://github.com/regentmarkets/flutter-deriv-packages/commit/ba1b75b85e103a46efab2a5224f04a280b282ee1)) + +#### `deriv_mobile_chart_wrapper` - `v0.1.7+2` + + - **REFACTOR**: [DRGO-193] Wire up of line drawing tool ([#883](https://github.com/regentmarkets/flutter-deriv-packages/issues/883)). ([a498619c](https://github.com/regentmarkets/flutter-deriv-packages/commit/a498619c2ce6913b26e4dbd1f4d1857064862508)) + + +## 2024-10-22 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.7.1`](#deriv_localizations---v171) + - [`deriv_mobile_chart_wrapper` - `v0.1.7+1`](#deriv_mobile_chart_wrapper---v0171) + - [`deriv_passkeys` - `v0.0.5+9`](#deriv_passkeys---v0059) + - [`deriv_auth` - `v7.0.5`](#deriv_auth---v705) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_mobile_chart_wrapper` - `v0.1.7+1` + - `deriv_passkeys` - `v0.0.5+9` + - `deriv_auth` - `v7.0.5` + +--- + +#### `deriv_localizations` - `v1.7.1` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#881](https://github.com/regentmarkets/flutter-deriv-packages/issues/881)). ([ec78f45b](https://github.com/regentmarkets/flutter-deriv-packages/commit/ec78f45b46dfe02e99995c10699289193f145a75)) + + +## 2024-10-22 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.1.7`](#deriv_mobile_chart_wrapper---v017) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.1.7` + + - **FEAT**(deriv_mobile_chart_wrapper): [DRGO-75] Add_bottom_sheet_landing_logic ([#841](https://github.com/regentmarkets/flutter-deriv-packages/issues/841)). ([5cf2429e](https://github.com/regentmarkets/flutter-deriv-packages/commit/5cf2429e959d9fa7093999bb4c20daf973cf8920)) + + +## 2024-10-22 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.7.0`](#deriv_localizations---v170) + - [`deriv_passkeys` - `v0.0.5+8`](#deriv_passkeys---v0058) + - [`deriv_mobile_chart_wrapper` - `v0.1.6+1`](#deriv_mobile_chart_wrapper---v0161) + - [`deriv_auth` - `v7.0.4`](#deriv_auth---v704) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_passkeys` - `v0.0.5+8` + - `deriv_mobile_chart_wrapper` - `v0.1.6+1` + - `deriv_auth` - `v7.0.4` + +--- + +#### `deriv_localizations` - `v1.7.0` + + - **FEAT**(deriv_localizations): Update localizations for `deriv_mobile_chart_wrapper` ([#830](https://github.com/regentmarkets/flutter-deriv-packages/issues/830)). ([4adde10d](https://github.com/regentmarkets/flutter-deriv-packages/commit/4adde10d42b8cf9f2540782634c9f8527c17620d)) + + +## 2024-10-22 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.1.6`](#deriv_mobile_chart_wrapper---v016) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.1.6` + + - **FEAT**(deriv_mobile_chart_wrapper): Update pubspec.yaml ([#877](https://github.com/regentmarkets/flutter-deriv-packages/issues/877)). ([79518161](https://github.com/regentmarkets/flutter-deriv-packages/commit/7951816199178bcdc1eb507b784e713da9f60e8d)) + + +## 2024-10-22 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_feature_flag` - `v0.1.2`](#deriv_feature_flag---v012) + +--- + +#### `deriv_feature_flag` - `v0.1.2` + + - **FEAT**(deriv_feature_flag): set attributes ([#874](https://github.com/regentmarkets/flutter-deriv-packages/issues/874)). ([bb92d976](https://github.com/regentmarkets/flutter-deriv-packages/commit/bb92d9764408613905da8451ce97689db09c6991)) + + +## 2024-10-18 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v7.0.3`](#deriv_auth---v703) + +--- + +#### `deriv_auth` - `v7.0.3` + + - **FIX**(deriv_auth): Change the token to token getter for more flexibility ([#852](https://github.com/regentmarkets/flutter-deriv-packages/issues/852)). ([09ab8c56](https://github.com/regentmarkets/flutter-deriv-packages/commit/09ab8c56f6e4616ef8c162ea00d6ccb58e5a0f1f)) + + +## 2024-10-16 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_cipher` - `v0.0.2`](#deriv_cipher---v002) + +--- + +#### `deriv_cipher` - `v0.0.2` + + - **FEAT**(deriv_cipher): add cipher package ([#836](https://github.com/regentmarkets/flutter-deriv-packages/issues/836)). ([d64a6473](https://github.com/regentmarkets/flutter-deriv-packages/commit/d64a64736ad3dadb3fd4237e370f5a81f0c6f646)) + + +## 2024-10-16 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.1.5`](#deriv_mobile_chart_wrapper---v015) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.1.5` + + - **FEAT**(deriv_mobile_chart_wrapper): Add the ability to hide indicators. ([#866](https://github.com/regentmarkets/flutter-deriv-packages/issues/866)). ([2cf1b4f6](https://github.com/regentmarkets/flutter-deriv-packages/commit/2cf1b4f6e255b1058996015513ad39aca4b29b71)) + + +## 2024-10-14 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.1.4`](#deriv_mobile_chart_wrapper---v014) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.1.4` + + - **FEAT**(deriv_mobile_chart_wrapper): DRGO-896 Add_counting_logic_to_bottom_sheet ([#835](https://github.com/regentmarkets/flutter-deriv-packages/issues/835)). ([2195701d](https://github.com/regentmarkets/flutter-deriv-packages/commit/2195701d6f76e356c7e158a240cf1951ea70047c)) + + +## 2024-10-11 + +### Changes + +--- + +Packages with breaking changes: + + - [`analytics` - `v4.0.0`](#analytics---v400) + +Packages with other changes: + + - [`deriv_auth` - `v7.0.2`](#deriv_auth---v702) + - [`deriv_passkeys` - `v0.0.5+7`](#deriv_passkeys---v0057) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v7.0.2` + - `deriv_passkeys` - `v0.0.5+7` + +--- + +#### `analytics` - `v4.0.0` + + - **BREAKING** **FEAT**(analytics): add auto trade rudderstack events ([#845](https://github.com/regentmarkets/flutter-deriv-packages/issues/845)). ([8c15aafd](https://github.com/regentmarkets/flutter-deriv-packages/commit/8c15aafda41428c9d19272117b0a5a49b16b4154)) + + +## 2024-10-10 + +### Changes + +--- + +Packages with breaking changes: + + - [`deriv_app_performance` - `v0.1.0`](#deriv_app_performance---v010) + - [`update_checker` - `v3.0.0`](#update_checker---v300) + +Packages with other changes: + + - There are no other changes in this release. + +--- + +#### `deriv_app_performance` - `v0.1.0` + + - **BREAKING** **FIX**(deriv_app_performance): [DRGO-1247] Ramin/update dependencies ([#862](https://github.com/regentmarkets/flutter-deriv-packages/issues/862)). ([b0e7120b](https://github.com/regentmarkets/flutter-deriv-packages/commit/b0e7120bd1afc0b3244e14d0c251525005ee67c5)) + +#### `update_checker` - `v3.0.0` + + - **BREAKING** **FIX**(deriv_app_performance): [DRGO-1247] Ramin/update dependencies ([#862](https://github.com/regentmarkets/flutter-deriv-packages/issues/862)). ([b0e7120b](https://github.com/regentmarkets/flutter-deriv-packages/commit/b0e7120bd1afc0b3244e14d0c251525005ee67c5)) + + +## 2024-10-10 + +### Changes + +--- + +Packages with breaking changes: + + - [`analytics` - `v3.0.0`](#analytics---v300) + +Packages with other changes: + + - [`deriv_auth` - `v7.0.1`](#deriv_auth---v701) + - [`deriv_passkeys` - `v0.0.5+6`](#deriv_passkeys---v0056) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v7.0.1` + - `deriv_passkeys` - `v0.0.5+6` + +--- + +#### `analytics` - `v3.0.0` + + - **BREAKING** **FEAT**(analytics): [DRGO-1247] Ramin/update for flutter 3.24 ([#838](https://github.com/regentmarkets/flutter-deriv-packages/issues/838)). ([cfdf2d81](https://github.com/regentmarkets/flutter-deriv-packages/commit/cfdf2d812b4d4227f72b434f7db9ea182081ae6b)) + + +## 2024-10-10 + +### Changes + +--- + +Packages with breaking changes: + + - [`deriv_auth` - `v7.0.0`](#deriv_auth---v700) + - [`update_checker` - `v2.0.0`](#update_checker---v200) + +Packages with other changes: + + - There are no other changes in this release. + +--- + +#### `deriv_auth` - `v7.0.0` + + - **BREAKING** **FIX**(deriv_auth): [DRGO-1247] update some depedencies ([#858](https://github.com/regentmarkets/flutter-deriv-packages/issues/858)). ([6a511b39](https://github.com/regentmarkets/flutter-deriv-packages/commit/6a511b39e91b95747fe594f40d6214b0da39d2e2)) + +#### `update_checker` - `v2.0.0` + + - **BREAKING** **FIX**(deriv_auth): [DRGO-1247] update some depedencies ([#858](https://github.com/regentmarkets/flutter-deriv-packages/issues/858)). ([6a511b39](https://github.com/regentmarkets/flutter-deriv-packages/commit/6a511b39e91b95747fe594f40d6214b0da39d2e2)) + + +## 2024-10-10 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_date_range_picker` - `v0.0.1+11`](#deriv_date_range_picker---v00111) + - [`deriv_mobile_chart_wrapper` - `v0.1.3+6`](#deriv_mobile_chart_wrapper---v0136) + - [`deriv_numpad` - `v1.1.10`](#deriv_numpad---v1110) + - [`deriv_widgetbook` - `v0.0.2+34`](#deriv_widgetbook---v00234) + +--- + +#### `deriv_date_range_picker` - `v0.0.1+11` + + - **FIX**(deriv_date_range_picker): iupgrade intl version ([#859](https://github.com/regentmarkets/flutter-deriv-packages/issues/859)). ([c500b57d](https://github.com/regentmarkets/flutter-deriv-packages/commit/c500b57d1558b10d7d60603d8de36af88db3dfb7)) + +#### `deriv_mobile_chart_wrapper` - `v0.1.3+6` + + - **FIX**(deriv_date_range_picker): iupgrade intl version ([#859](https://github.com/regentmarkets/flutter-deriv-packages/issues/859)). ([c500b57d](https://github.com/regentmarkets/flutter-deriv-packages/commit/c500b57d1558b10d7d60603d8de36af88db3dfb7)) + +#### `deriv_numpad` - `v1.1.10` + + - **FIX**(deriv_date_range_picker): iupgrade intl version ([#859](https://github.com/regentmarkets/flutter-deriv-packages/issues/859)). ([c500b57d](https://github.com/regentmarkets/flutter-deriv-packages/commit/c500b57d1558b10d7d60603d8de36af88db3dfb7)) + +#### `deriv_widgetbook` - `v0.0.2+34` + + - **FIX**(deriv_date_range_picker): iupgrade intl version ([#859](https://github.com/regentmarkets/flutter-deriv-packages/issues/859)). ([c500b57d](https://github.com/regentmarkets/flutter-deriv-packages/commit/c500b57d1558b10d7d60603d8de36af88db3dfb7)) + + +## 2024-10-10 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_http_client` - `v2.0.2`](#deriv_http_client---v202) + - [`deriv_passkeys` - `v0.0.5+5`](#deriv_passkeys---v0055) + - [`deriv_web_view` - `v0.2.2+5`](#deriv_web_view---v0225) + - [`deriv_auth` - `v6.8.5`](#deriv_auth---v685) + - [`deriv_ui` - `v0.1.0+6`](#deriv_ui---v0106) + - [`deriv_mobile_chart_wrapper` - `v0.1.3+5`](#deriv_mobile_chart_wrapper---v0135) + - [`deriv_widgetbook` - `v0.0.2+33`](#deriv_widgetbook---v00233) + - [`deriv_language_selector` - `v0.0.3+13`](#deriv_language_selector---v00313) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_web_view` - `v0.2.2+5` + - `deriv_auth` - `v6.8.5` + - `deriv_ui` - `v0.1.0+6` + - `deriv_mobile_chart_wrapper` - `v0.1.3+5` + - `deriv_widgetbook` - `v0.0.2+33` + - `deriv_language_selector` - `v0.0.3+13` + +--- + +#### `deriv_http_client` - `v2.0.2` + + - **REFACTOR**(deriv_http_client): upgrade flutter_system_proxy dep ([#854](https://github.com/regentmarkets/flutter-deriv-packages/issues/854)). ([30f8bc5d](https://github.com/regentmarkets/flutter-deriv-packages/commit/30f8bc5d87a40b17f344608b855bbd6261b94696)) + +#### `deriv_passkeys` - `v0.0.5+5` + + - **REFACTOR**(deriv_passkeys): update deriv_api dep ([#856](https://github.com/regentmarkets/flutter-deriv-packages/issues/856)). ([ede54c8c](https://github.com/regentmarkets/flutter-deriv-packages/commit/ede54c8ce7f371189b460fae5f0e3e95f4fb7817)) + + +## 2024-10-10 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_http_client` - `v2.0.1`](#deriv_http_client---v201) + - [`deriv_web_view` - `v0.2.2+4`](#deriv_web_view---v0224) + - [`deriv_passkeys` - `v0.0.5+4`](#deriv_passkeys---v0054) + - [`deriv_auth` - `v6.8.4`](#deriv_auth---v684) + - [`deriv_ui` - `v0.1.0+5`](#deriv_ui---v0105) + - [`deriv_mobile_chart_wrapper` - `v0.1.3+4`](#deriv_mobile_chart_wrapper---v0134) + - [`deriv_widgetbook` - `v0.0.2+32`](#deriv_widgetbook---v00232) + - [`deriv_language_selector` - `v0.0.3+12`](#deriv_language_selector---v00312) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_web_view` - `v0.2.2+4` + - `deriv_passkeys` - `v0.0.5+4` + - `deriv_auth` - `v6.8.4` + - `deriv_ui` - `v0.1.0+5` + - `deriv_mobile_chart_wrapper` - `v0.1.3+4` + - `deriv_widgetbook` - `v0.0.2+32` + - `deriv_language_selector` - `v0.0.3+12` + +--- + +#### `deriv_http_client` - `v2.0.1` + + - **REFACTOR**(deriv_http_client): upgrade flutter_system_proxy dep ([#854](https://github.com/regentmarkets/flutter-deriv-packages/issues/854)). ([30f8bc5d](https://github.com/regentmarkets/flutter-deriv-packages/commit/30f8bc5d87a40b17f344608b855bbd6261b94696)) + + +## 2024-10-02 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.6.1`](#deriv_localizations---v161) + - [`deriv_mobile_chart_wrapper` - `v0.1.3+3`](#deriv_mobile_chart_wrapper---v0133) + - [`deriv_auth` - `v6.8.3`](#deriv_auth---v683) + - [`deriv_passkeys` - `v0.0.5+3`](#deriv_passkeys---v0053) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_mobile_chart_wrapper` - `v0.1.3+3` + - `deriv_auth` - `v6.8.3` + - `deriv_passkeys` - `v0.0.5+3` + +--- + +#### `deriv_localizations` - `v1.6.1` + + - **REFACTOR**(deriv_localizations): update intl version ([#849](https://github.com/regentmarkets/flutter-deriv-packages/issues/849)). ([0adb30fc](https://github.com/regentmarkets/flutter-deriv-packages/commit/0adb30fcdcba69f4bd71ede781a7db7490976827)) + + +## 2024-10-02 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_ui` - `v0.1.0+4`](#deriv_ui---v0104) + - [`deriv_mobile_chart_wrapper` - `v0.1.3+2`](#deriv_mobile_chart_wrapper---v0132) + - [`deriv_widgetbook` - `v0.0.2+31`](#deriv_widgetbook---v00231) + - [`deriv_language_selector` - `v0.0.3+11`](#deriv_language_selector---v00311) + - [`deriv_passkeys` - `v0.0.5+2`](#deriv_passkeys---v0052) + - [`deriv_auth` - `v6.8.2`](#deriv_auth---v682) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_mobile_chart_wrapper` - `v0.1.3+2` + - `deriv_widgetbook` - `v0.0.2+31` + - `deriv_language_selector` - `v0.0.3+11` + - `deriv_passkeys` - `v0.0.5+2` + - `deriv_auth` - `v6.8.2` + +--- + +#### `deriv_ui` - `v0.1.0+4` + + - **REFACTOR**(deriv_ui): update intl version ([#847](https://github.com/regentmarkets/flutter-deriv-packages/issues/847)). ([cee558af](https://github.com/regentmarkets/flutter-deriv-packages/commit/cee558afd16aeecb8888fed8baa6da569269f3e3)) + + +## 2024-09-26 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.8.1`](#deriv_auth---v681) + - [`deriv_logger` - `v0.0.3+1`](#deriv_logger---v0031) + - [`deriv_passkeys` - `v0.0.5+1`](#deriv_passkeys---v0051) + +--- + +#### `deriv_auth` - `v6.8.1` + + - **FIX**: Updating Flutter Deriv API version to latest ([#843](https://github.com/regentmarkets/flutter-deriv-packages/issues/843)). ([db461495](https://github.com/regentmarkets/flutter-deriv-packages/commit/db46149557a9a93eb86a6691a8c7831bf709e8f1)) + +#### `deriv_logger` - `v0.0.3+1` + + - **FIX**: Updating Flutter Deriv API version to latest ([#843](https://github.com/regentmarkets/flutter-deriv-packages/issues/843)). ([db461495](https://github.com/regentmarkets/flutter-deriv-packages/commit/db46149557a9a93eb86a6691a8c7831bf709e8f1)) + +#### `deriv_passkeys` - `v0.0.5+1` + + - **FIX**: Updating Flutter Deriv API version to latest ([#843](https://github.com/regentmarkets/flutter-deriv-packages/issues/843)). ([db461495](https://github.com/regentmarkets/flutter-deriv-packages/commit/db46149557a9a93eb86a6691a8c7831bf709e8f1)) + + +## 2024-09-26 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.8.1`](#deriv_auth---v681) + - [`deriv_logger` - `v0.0.3+1`](#deriv_logger---v0031) + - [`deriv_passkeys` - `v0.0.5+1`](#deriv_passkeys---v0051) + +--- + +#### `deriv_auth` - `v6.8.1` + + - **FIX**: Updating Flutter Deriv API version to latest ([#843](https://github.com/regentmarkets/flutter-deriv-packages/issues/843)). ([db461495](https://github.com/regentmarkets/flutter-deriv-packages/commit/db46149557a9a93eb86a6691a8c7831bf709e8f1)) + +#### `deriv_logger` - `v0.0.3+1` + + - **FIX**: Updating Flutter Deriv API version to latest ([#843](https://github.com/regentmarkets/flutter-deriv-packages/issues/843)). ([db461495](https://github.com/regentmarkets/flutter-deriv-packages/commit/db46149557a9a93eb86a6691a8c7831bf709e8f1)) + +#### `deriv_passkeys` - `v0.0.5+1` + + - **FIX**: Updating Flutter Deriv API version to latest ([#843](https://github.com/regentmarkets/flutter-deriv-packages/issues/843)). ([db461495](https://github.com/regentmarkets/flutter-deriv-packages/commit/db46149557a9a93eb86a6691a8c7831bf709e8f1)) + + +## 2024-09-26 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.8.0`](#deriv_auth---v680) + - [`deriv_passkeys` - `v0.0.5`](#deriv_passkeys---v005) + +--- + +#### `deriv_auth` - `v6.8.0` + + - **FIX**: [86c0e0nez] add disability check while filtering accounts ([#837](https://github.com/regentmarkets/flutter-deriv-packages/issues/837)). ([e64dfa54](https://github.com/regentmarkets/flutter-deriv-packages/commit/e64dfa54d774e79c3c88f63f3112c7eb0c0cb6b8)) + - **FEAT**(deriv_auth): [DERG-1396] akhil/1396/multi_user_level_authentication_poc_master ([#574](https://github.com/regentmarkets/flutter-deriv-packages/issues/574)). ([97ac8004](https://github.com/regentmarkets/flutter-deriv-packages/commit/97ac8004370762ed38ed0608e64699a020406b8e)) + +#### `deriv_passkeys` - `v0.0.5` + + - **FEAT**(deriv_auth): [DERG-1396] akhil/1396/multi_user_level_authentication_poc_master ([#574](https://github.com/regentmarkets/flutter-deriv-packages/issues/574)). ([97ac8004](https://github.com/regentmarkets/flutter-deriv-packages/commit/97ac8004370762ed38ed0608e64699a020406b8e)) + + +## 2024-09-25 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.7.51`](#deriv_auth---v6751) + - [`deriv_passkeys` - `v0.0.4+6`](#deriv_passkeys---v0046) + - [`deriv_rudderstack` - `v1.1.1`](#deriv_rudderstack---v111) + - [`deriv_theme` - `v2.7.1`](#deriv_theme---v271) + - [`update_checker` - `v1.5.2`](#update_checker---v152) + - [`deriv_ui` - `v0.1.0+3`](#deriv_ui---v0103) + - [`deriv_mobile_chart_wrapper` - `v0.1.3+1`](#deriv_mobile_chart_wrapper---v0131) + - [`deriv_widgetbook` - `v0.0.2+30`](#deriv_widgetbook---v00230) + - [`deriv_expandable_bottom_sheet` - `v0.0.1+10`](#deriv_expandable_bottom_sheet---v00110) + - [`deriv_date_range_picker` - `v0.0.1+10`](#deriv_date_range_picker---v00110) + - [`deriv_numpad` - `v1.1.9`](#deriv_numpad---v119) + - [`deriv_language_selector` - `v0.0.3+10`](#deriv_language_selector---v00310) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_ui` - `v0.1.0+3` + - `deriv_mobile_chart_wrapper` - `v0.1.3+1` + - `deriv_widgetbook` - `v0.0.2+30` + - `deriv_expandable_bottom_sheet` - `v0.0.1+10` + - `deriv_date_range_picker` - `v0.0.1+10` + - `deriv_numpad` - `v1.1.9` + - `deriv_language_selector` - `v0.0.3+10` + +--- + +#### `deriv_auth` - `v6.7.51` + + - **FIX**: [86c0e0nez] add disability check while filtering accounts ([#837](https://github.com/regentmarkets/flutter-deriv-packages/issues/837)). ([e64dfa54](https://github.com/regentmarkets/flutter-deriv-packages/commit/e64dfa54d774e79c3c88f63f3112c7eb0c0cb6b8)) + - **FIX**: [86c0e0nez] filter supported accounts while logging in ([#833](https://github.com/regentmarkets/flutter-deriv-packages/issues/833)). ([534e982c](https://github.com/regentmarkets/flutter-deriv-packages/commit/534e982c809b5e0e9380366a3f32a05f1ef2cf10)) + +#### `deriv_passkeys` - `v0.0.4+6` + + - **REFACTOR**(deriv_passkeys): Add call back to call after pass key flow finished ([#831](https://github.com/regentmarkets/flutter-deriv-packages/issues/831)). ([444e963e](https://github.com/regentmarkets/flutter-deriv-packages/commit/444e963e949334ae81b170c73c1a35afad7a1e0e)) + +#### `deriv_rudderstack` - `v1.1.1` + + - **REFACTOR**(deriv_passkeys): Add call back to call after pass key flow finished ([#831](https://github.com/regentmarkets/flutter-deriv-packages/issues/831)). ([444e963e](https://github.com/regentmarkets/flutter-deriv-packages/commit/444e963e949334ae81b170c73c1a35afad7a1e0e)) + +#### `deriv_theme` - `v2.7.1` + + - **REFACTOR**(deriv_passkeys): Add call back to call after pass key flow finished ([#831](https://github.com/regentmarkets/flutter-deriv-packages/issues/831)). ([444e963e](https://github.com/regentmarkets/flutter-deriv-packages/commit/444e963e949334ae81b170c73c1a35afad7a1e0e)) + +#### `update_checker` - `v1.5.2` + + - **REFACTOR**(deriv_passkeys): Add call back to call after pass key flow finished ([#831](https://github.com/regentmarkets/flutter-deriv-packages/issues/831)). ([444e963e](https://github.com/regentmarkets/flutter-deriv-packages/commit/444e963e949334ae81b170c73c1a35afad7a1e0e)) + + +## 2024-09-24 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.1.3`](#deriv_mobile_chart_wrapper---v013) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.1.3` + + - **FEAT**(deriv_mobile_chart_wrapper): [DRGO-73] Added line drawing guide banner ([#826](https://github.com/regentmarkets/flutter-deriv-packages/issues/826)). ([b2b018ec](https://github.com/regentmarkets/flutter-deriv-packages/commit/b2b018ec78d9f2de0dcc71b2a76f1343581f4f03)) + + +## 2024-09-20 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.1.2`](#deriv_mobile_chart_wrapper---v012) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.1.2` + + - **FEAT**(deriv_mobile_chart_wrapper): [DRGO-81] Functionality to add dots for drawing tool. ([#810](https://github.com/regentmarkets/flutter-deriv-packages/issues/810)). ([05d5c85f](https://github.com/regentmarkets/flutter-deriv-packages/commit/05d5c85f9b5c204ec54eb3828b262fe7c0293ac0)) + + +## 2024-09-19 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`analytics` - `v2.3.0`](#analytics---v230) + - [`deriv_passkeys` - `v0.0.4+5`](#deriv_passkeys---v0045) + - [`deriv_auth` - `v6.7.50`](#deriv_auth---v6750) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_passkeys` - `v0.0.4+5` + - `deriv_auth` - `v6.7.50` + +--- + +#### `analytics` - `v2.3.0` + + - **FEAT**(analytics): Amend tracking events Rudderstack ([#821](https://github.com/regentmarkets/flutter-deriv-packages/issues/821)). ([bd197376](https://github.com/regentmarkets/flutter-deriv-packages/commit/bd197376cf450ec375a9759c9511563a253a0c64)) + + +## 2024-09-19 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.7.49`](#deriv_auth---v6749) + +--- + +#### `deriv_auth` - `v6.7.49` + + - **FIX**(deriv_auth): unassigned token for account created on web ([#756](https://github.com/regentmarkets/flutter-deriv-packages/issues/756)). ([abbb8905](https://github.com/regentmarkets/flutter-deriv-packages/commit/abbb8905263517c32c1e990fdc9dbfd2fb38ae9b)) + + +## 2024-09-18 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.1.1`](#deriv_mobile_chart_wrapper---v011) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.1.1` + + - **FEAT**(deriv_mobile_chart_wrapper): [DRGO-74] Add drawing tools bottom sheet UI ([#815](https://github.com/regentmarkets/flutter-deriv-packages/issues/815)). ([ebd9ab92](https://github.com/regentmarkets/flutter-deriv-packages/commit/ebd9ab92707630df3bc185aab5a503399df786b4)) + + +## 2024-09-13 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_ui` - `v0.1.0+2`](#deriv_ui---v0102) + - [`deriv_language_selector` - `v0.0.3+9`](#deriv_language_selector---v0039) + - [`deriv_widgetbook` - `v0.0.2+29`](#deriv_widgetbook---v00229) + - [`deriv_passkeys` - `v0.0.4+4`](#deriv_passkeys---v0044) + - [`deriv_auth` - `v6.7.48`](#deriv_auth---v6748) + - [`deriv_mobile_chart_wrapper` - `v0.1.0+4`](#deriv_mobile_chart_wrapper---v0104) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_language_selector` - `v0.0.3+9` + - `deriv_widgetbook` - `v0.0.2+29` + - `deriv_passkeys` - `v0.0.4+4` + - `deriv_auth` - `v6.7.48` + - `deriv_mobile_chart_wrapper` - `v0.1.0+4` + +--- + +#### `deriv_ui` - `v0.1.0+2` + + - **FIX**(deriv-ui): [DRGO-126]call the passed onDispose method inside dispose method ([#819](https://github.com/regentmarkets/flutter-deriv-packages/issues/819)). ([2e29a1ee](https://github.com/regentmarkets/flutter-deriv-packages/commit/2e29a1eeb7dbb5c77bc04e1e7f7ee12081a1fd2d)) + + +## 2024-09-11 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.1.0+3`](#deriv_mobile_chart_wrapper---v0103) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.1.0+3` + + - **FIX**(deriv_mobile_chart_wrapper): fix text alignment ([#816](https://github.com/regentmarkets/flutter-deriv-packages/issues/816)). ([8fd35364](https://github.com/regentmarkets/flutter-deriv-packages/commit/8fd35364760d7bc1b101f9414a2d9d340a1b5a32)) + + +## 2024-09-10 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.6.0`](#deriv_localizations---v160) + - [`deriv_passkeys` - `v0.0.4+3`](#deriv_passkeys---v0043) + - [`deriv_mobile_chart_wrapper` - `v0.1.0+2`](#deriv_mobile_chart_wrapper---v0102) + - [`deriv_auth` - `v6.7.47`](#deriv_auth---v6747) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_passkeys` - `v0.0.4+3` + - `deriv_mobile_chart_wrapper` - `v0.1.0+2` + - `deriv_auth` - `v6.7.47` + +--- + +#### `deriv_localizations` - `v1.6.0` + + - **FEAT**(deriv_localizations): Update localizations generated file to get the new strings ([#808](https://github.com/regentmarkets/flutter-deriv-packages/issues/808)). ([28ae98bc](https://github.com/regentmarkets/flutter-deriv-packages/commit/28ae98bcd78ab725c3f35d6a88175c0e85be4c94)) + + +## 2024-09-09 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.1.0+1`](#deriv_mobile_chart_wrapper---v0101) + - [`deriv_ui` - `v0.1.0+1`](#deriv_ui---v0101) + - [`deriv_language_selector` - `v0.0.3+8`](#deriv_language_selector---v0038) + - [`deriv_widgetbook` - `v0.0.2+28`](#deriv_widgetbook---v00228) + - [`deriv_passkeys` - `v0.0.4+2`](#deriv_passkeys---v0042) + - [`deriv_auth` - `v6.7.46`](#deriv_auth---v6746) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_language_selector` - `v0.0.3+8` + - `deriv_widgetbook` - `v0.0.2+28` + - `deriv_passkeys` - `v0.0.4+2` + - `deriv_auth` - `v6.7.46` + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.1.0+1` + + - **FIX**(deriv_ui): fix value selector issues. ([#800](https://github.com/regentmarkets/flutter-deriv-packages/issues/800)). ([9c783d58](https://github.com/regentmarkets/flutter-deriv-packages/commit/9c783d58d52e753ede1ee40a9da4ce038ed1c991)) + +#### `deriv_ui` - `v0.1.0+1` + + - **FIX**(deriv_ui): fix value selector issues. ([#800](https://github.com/regentmarkets/flutter-deriv-packages/issues/800)). ([9c783d58](https://github.com/regentmarkets/flutter-deriv-packages/commit/9c783d58d52e753ede1ee40a9da4ce038ed1c991)) + + +## 2024-09-09 + +### Changes + +--- + +Packages with breaking changes: + + - [`deriv_mobile_chart_wrapper` - `v0.1.0`](#deriv_mobile_chart_wrapper---v010) + - [`deriv_ui` - `v0.1.0`](#deriv_ui---v010) + +Packages with other changes: + + - [`deriv_language_selector` - `v0.0.3+7`](#deriv_language_selector---v0037) + - [`deriv_widgetbook` - `v0.0.2+27`](#deriv_widgetbook---v00227) + - [`deriv_passkeys` - `v0.0.4+1`](#deriv_passkeys---v0041) + - [`deriv_auth` - `v6.7.45`](#deriv_auth---v6745) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_language_selector` - `v0.0.3+7` + - `deriv_widgetbook` - `v0.0.2+27` + - `deriv_passkeys` - `v0.0.4+1` + - `deriv_auth` - `v6.7.45` + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.1.0` + + - **BREAKING** **REFACTOR**(deriv_ui): make deriv bottom sheet height dynamic and fix color selector issues ([#797](https://github.com/regentmarkets/flutter-deriv-packages/issues/797)). ([636a7185](https://github.com/regentmarkets/flutter-deriv-packages/commit/636a7185ae3ce461647f7deb8f62c55acaad3a65)) + +#### `deriv_ui` - `v0.1.0` + + - **BREAKING** **REFACTOR**(deriv_ui): make deriv bottom sheet height dynamic and fix color selector issues ([#797](https://github.com/regentmarkets/flutter-deriv-packages/issues/797)). ([636a7185](https://github.com/regentmarkets/flutter-deriv-packages/commit/636a7185ae3ce461647f7deb8f62c55acaad3a65)) + + +## 2024-09-09 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.0.15`](#deriv_mobile_chart_wrapper---v0015) + - [`deriv_passkeys` - `v0.0.4`](#deriv_passkeys---v004) + - [`deriv_ui` - `v0.0.16`](#deriv_ui---v0016) + - [`deriv_auth` - `v6.7.44`](#deriv_auth---v6744) + - [`deriv_language_selector` - `v0.0.3+6`](#deriv_language_selector---v0036) + - [`deriv_widgetbook` - `v0.0.2+26`](#deriv_widgetbook---v00226) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.7.44` + - `deriv_language_selector` - `v0.0.3+6` + - `deriv_widgetbook` - `v0.0.2+26` + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.0.15` + + - **FEAT**(deriv_mobile_chart_wrapper): wire up indicators ([#760](https://github.com/regentmarkets/flutter-deriv-packages/issues/760)). ([4ff1747b](https://github.com/regentmarkets/flutter-deriv-packages/commit/4ff1747b76e168710768be84d851276db5884c29)) + +#### `deriv_passkeys` - `v0.0.4` + + - **FEAT**(deriv_mobile_chart_wrapper): wire up indicators ([#760](https://github.com/regentmarkets/flutter-deriv-packages/issues/760)). ([4ff1747b](https://github.com/regentmarkets/flutter-deriv-packages/commit/4ff1747b76e168710768be84d851276db5884c29)) + +#### `deriv_ui` - `v0.0.16` + + - **FEAT**(deriv_mobile_chart_wrapper): wire up indicators ([#760](https://github.com/regentmarkets/flutter-deriv-packages/issues/760)). ([4ff1747b](https://github.com/regentmarkets/flutter-deriv-packages/commit/4ff1747b76e168710768be84d851276db5884c29)) + + +## 2024-09-02 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.0.14`](#deriv_mobile_chart_wrapper---v0014) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.0.14` + + - **FEAT**(deriv_mobile_chart_wrapper): Created a new icon button widget for drawing tools ([#792](https://github.com/regentmarkets/flutter-deriv-packages/issues/792)). ([fe1a19c1](https://github.com/regentmarkets/flutter-deriv-packages/commit/fe1a19c10ea88323911a5507ad37b25ba71aedaa)) + + +## 2024-09-02 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_app_performance` - `v0.0.1+1`](#deriv_app_performance---v0011) + +--- + +#### `deriv_app_performance` - `v0.0.1+1` + + - **FIX**(deriv_app_performance): return the trace object on start and stop tracing ([#782](https://github.com/regentmarkets/flutter-deriv-packages/issues/782)). ([7f146ac7](https://github.com/regentmarkets/flutter-deriv-packages/commit/7f146ac7a9cc71ca0bb79f5523a4ceb77d2df25b)) + + +## 2024-08-30 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_theme` - `v2.7.0`](#deriv_theme---v270) + - [`deriv_mobile_chart_wrapper` - `v0.0.13+3`](#deriv_mobile_chart_wrapper---v00133) + - [`deriv_widgetbook` - `v0.0.2+25`](#deriv_widgetbook---v00225) + - [`deriv_ui` - `v0.0.15+1`](#deriv_ui---v00151) + - [`deriv_date_range_picker` - `v0.0.1+9`](#deriv_date_range_picker---v0019) + - [`deriv_numpad` - `v1.1.8`](#deriv_numpad---v118) + - [`deriv_expandable_bottom_sheet` - `v0.0.1+9`](#deriv_expandable_bottom_sheet---v0019) + - [`deriv_passkeys` - `v0.0.3+28`](#deriv_passkeys---v00328) + - [`deriv_auth` - `v6.7.43`](#deriv_auth---v6743) + - [`deriv_language_selector` - `v0.0.3+5`](#deriv_language_selector---v0035) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_mobile_chart_wrapper` - `v0.0.13+3` + - `deriv_widgetbook` - `v0.0.2+25` + - `deriv_ui` - `v0.0.15+1` + - `deriv_date_range_picker` - `v0.0.1+9` + - `deriv_numpad` - `v1.1.8` + - `deriv_expandable_bottom_sheet` - `v0.0.1+9` + - `deriv_passkeys` - `v0.0.3+28` + - `deriv_auth` - `v6.7.43` + - `deriv_language_selector` - `v0.0.3+5` + +--- + +#### `deriv_theme` - `v2.7.0` + + - **FEAT**(deriv_theme): add IDV colors to theme class. ([#796](https://github.com/regentmarkets/flutter-deriv-packages/issues/796)). ([50f48bda](https://github.com/regentmarkets/flutter-deriv-packages/commit/50f48bda345f75e01bf083a1c3233755951dd25c)) + + +## 2024-08-28 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_live_chat` - `v0.0.2`](#deriv_live_chat---v002) + +--- + +#### `deriv_live_chat` - `v0.0.2` + + - **FEAT**(deriv_live_chat): Add reloadChatView method ([#791](https://github.com/regentmarkets/flutter-deriv-packages/issues/791)). ([f180cc56](https://github.com/regentmarkets/flutter-deriv-packages/commit/f180cc5602801c1a3e81a247053c1b8c9121a532)) + + +## 2024-08-27 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_passkeys` - `v0.0.3+27`](#deriv_passkeys---v00327) + - [`deriv_auth` - `v6.7.42`](#deriv_auth---v6742) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.7.42` + +--- + +#### `deriv_passkeys` - `v0.0.3+27` + + - **REFACTOR**(deriv_passkeys): replace token with account entity ([#785](https://github.com/regentmarkets/flutter-deriv-packages/issues/785)). ([600d7e44](https://github.com/regentmarkets/flutter-deriv-packages/commit/600d7e44c4318501d180640e5a9c21f5357f6022)) + + +## 2024-08-27 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_ui` - `v0.0.15`](#deriv_ui---v0015) + - [`deriv_mobile_chart_wrapper` - `v0.0.13+2`](#deriv_mobile_chart_wrapper---v00132) + - [`deriv_widgetbook` - `v0.0.2+24`](#deriv_widgetbook---v00224) + - [`deriv_passkeys` - `v0.0.3+26`](#deriv_passkeys---v00326) + - [`deriv_auth` - `v6.7.41`](#deriv_auth---v6741) + - [`deriv_language_selector` - `v0.0.3+4`](#deriv_language_selector---v0034) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_mobile_chart_wrapper` - `v0.0.13+2` + - `deriv_widgetbook` - `v0.0.2+24` + - `deriv_passkeys` - `v0.0.3+26` + - `deriv_auth` - `v6.7.41` + - `deriv_language_selector` - `v0.0.3+4` + +--- + +#### `deriv_ui` - `v0.0.15` + + - **FEAT**(Deriv_UI): [TRHM-618] added keys to deriv UI package ([#748](https://github.com/regentmarkets/flutter-deriv-packages/issues/748)). ([6e6e13b9](https://github.com/regentmarkets/flutter-deriv-packages/commit/6e6e13b9a87b8edaa4c10fcfe7ca2697afc7f601)) + + +## 2024-08-27 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_ui` - `v0.0.14`](#deriv_ui---v0014) + - [`deriv_mobile_chart_wrapper` - `v0.0.13+1`](#deriv_mobile_chart_wrapper---v00131) + - [`deriv_widgetbook` - `v0.0.2+23`](#deriv_widgetbook---v00223) + - [`deriv_passkeys` - `v0.0.3+25`](#deriv_passkeys---v00325) + - [`deriv_auth` - `v6.7.40`](#deriv_auth---v6740) + - [`deriv_language_selector` - `v0.0.3+3`](#deriv_language_selector---v0033) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_mobile_chart_wrapper` - `v0.0.13+1` + - `deriv_widgetbook` - `v0.0.2+23` + - `deriv_passkeys` - `v0.0.3+25` + - `deriv_auth` - `v6.7.40` + - `deriv_language_selector` - `v0.0.3+3` + +--- + +#### `deriv_ui` - `v0.0.14` + + - **FEAT**: [DRGO-126] Add onDispose method to deriv bottomsheet and add key to bottmsheet handle ([#787](https://github.com/regentmarkets/flutter-deriv-packages/issues/787)). ([3d39f310](https://github.com/regentmarkets/flutter-deriv-packages/commit/3d39f3107d4c76b94d2db8228e2b55e4e4f90a47)) + + +## 2024-08-23 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.0.13`](#deriv_mobile_chart_wrapper---v0013) + - [`deriv_theme` - `v2.6.0`](#deriv_theme---v260) + - [`deriv_widgetbook` - `v0.0.2+22`](#deriv_widgetbook---v00222) + - [`deriv_numpad` - `v1.1.7`](#deriv_numpad---v117) + - [`deriv_ui` - `v0.0.13+5`](#deriv_ui---v00135) + - [`deriv_date_range_picker` - `v0.0.1+8`](#deriv_date_range_picker---v0018) + - [`deriv_expandable_bottom_sheet` - `v0.0.1+8`](#deriv_expandable_bottom_sheet---v0018) + - [`deriv_passkeys` - `v0.0.3+24`](#deriv_passkeys---v00324) + - [`deriv_auth` - `v6.7.39`](#deriv_auth---v6739) + - [`deriv_language_selector` - `v0.0.3+2`](#deriv_language_selector---v0032) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_widgetbook` - `v0.0.2+22` + - `deriv_numpad` - `v1.1.7` + - `deriv_ui` - `v0.0.13+5` + - `deriv_date_range_picker` - `v0.0.1+8` + - `deriv_expandable_bottom_sheet` - `v0.0.1+8` + - `deriv_passkeys` - `v0.0.3+24` + - `deriv_auth` - `v6.7.39` + - `deriv_language_selector` - `v0.0.3+2` + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.0.13` + + - **FEAT**(deriv_mobile_chart_wrapper): add drawing tools repo to chart wrapper ([#784](https://github.com/regentmarkets/flutter-deriv-packages/issues/784)). ([8df713c9](https://github.com/regentmarkets/flutter-deriv-packages/commit/8df713c958299820b94190f4f1a665efcfa8401b)) + +#### `deriv_theme` - `v2.6.0` + + - **FEAT**(deriv_theme): add IDV status badge colors. ([#777](https://github.com/regentmarkets/flutter-deriv-packages/issues/777)). ([d292701f](https://github.com/regentmarkets/flutter-deriv-packages/commit/d292701f00c1f8122a06d77860cdb528712aa694)) + + +## 2024-08-20 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_theme` - `v2.5.0`](#deriv_theme---v250) + - [`deriv_widgetbook` - `v0.0.2+21`](#deriv_widgetbook---v00221) + - [`deriv_numpad` - `v1.1.6`](#deriv_numpad---v116) + - [`deriv_ui` - `v0.0.13+4`](#deriv_ui---v00134) + - [`deriv_mobile_chart_wrapper` - `v0.0.12+5`](#deriv_mobile_chart_wrapper---v00125) + - [`deriv_date_range_picker` - `v0.0.1+7`](#deriv_date_range_picker---v0017) + - [`deriv_passkeys` - `v0.0.3+23`](#deriv_passkeys---v00323) + - [`deriv_auth` - `v6.7.38`](#deriv_auth---v6738) + - [`deriv_expandable_bottom_sheet` - `v0.0.1+7`](#deriv_expandable_bottom_sheet---v0017) + - [`deriv_language_selector` - `v0.0.3+1`](#deriv_language_selector---v0031) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_widgetbook` - `v0.0.2+21` + - `deriv_numpad` - `v1.1.6` + - `deriv_ui` - `v0.0.13+4` + - `deriv_mobile_chart_wrapper` - `v0.0.12+5` + - `deriv_date_range_picker` - `v0.0.1+7` + - `deriv_passkeys` - `v0.0.3+23` + - `deriv_auth` - `v6.7.38` + - `deriv_expandable_bottom_sheet` - `v0.0.1+7` + - `deriv_language_selector` - `v0.0.3+1` + +--- + +#### `deriv_theme` - `v2.5.0` + + - **FEAT**(deriv_theme): add IDV status badge colors. ([#777](https://github.com/regentmarkets/flutter-deriv-packages/issues/777)). ([d292701f](https://github.com/regentmarkets/flutter-deriv-packages/commit/d292701f00c1f8122a06d77860cdb528712aa694)) + + +## 2024-08-16 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_live_chat` - `v0.0.1+3`](#deriv_live_chat---v0013) + +--- + +#### `deriv_live_chat` - `v0.0.1+3` + + - **FIX**(deriv_live_chat): Clear the session completely on a clear callback. ([#780](https://github.com/regentmarkets/flutter-deriv-packages/issues/780)). ([3b80c767](https://github.com/regentmarkets/flutter-deriv-packages/commit/3b80c76702956fcb4868b7fa8a75f3fa9e852209)) + + +## 2024-08-16 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_language_selector` - `v0.0.3`](#deriv_language_selector---v003) + - [`deriv_auth` - `v6.7.37`](#deriv_auth---v6737) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.7.37` + +--- + +#### `deriv_language_selector` - `v0.0.3` + + - **FEAT**(deriv_language_selector): Add a callback for language change. ([#778](https://github.com/regentmarkets/flutter-deriv-packages/issues/778)). ([21f6b8de](https://github.com/regentmarkets/flutter-deriv-packages/commit/21f6b8dee167ee1234bb1ee0a22b766305d5660a)) + + +## 2024-08-15 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.7.36`](#deriv_auth---v6736) + +--- + +#### `deriv_auth` - `v6.7.36` + + - **FIX**(deriv_auth): validation_enhancement ([#775](https://github.com/regentmarkets/flutter-deriv-packages/issues/775)). ([fc4bbb1f](https://github.com/regentmarkets/flutter-deriv-packages/commit/fc4bbb1f9384334dced645e8213b8b5dade8f05d)) + + +## 2024-08-14 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.7.35`](#deriv_auth---v6735) + - [`deriv_ui` - `v0.0.13+3`](#deriv_ui---v00133) + - [`deriv_mobile_chart_wrapper` - `v0.0.12+4`](#deriv_mobile_chart_wrapper---v00124) + - [`deriv_widgetbook` - `v0.0.2+20`](#deriv_widgetbook---v00220) + - [`deriv_passkeys` - `v0.0.3+22`](#deriv_passkeys---v00322) + - [`deriv_language_selector` - `v0.0.2+18`](#deriv_language_selector---v00218) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_mobile_chart_wrapper` - `v0.0.12+4` + - `deriv_widgetbook` - `v0.0.2+20` + - `deriv_passkeys` - `v0.0.3+22` + - `deriv_language_selector` - `v0.0.2+18` + +--- + +#### `deriv_auth` - `v6.7.35` + + - **REFACTOR**(deriv_auth): Update_single_entry_feature ([#577](https://github.com/regentmarkets/flutter-deriv-packages/issues/577)). ([462d7bd6](https://github.com/regentmarkets/flutter-deriv-packages/commit/462d7bd6bdf60536fa632be3b95fae0ba377f142)) + +#### `deriv_ui` - `v0.0.13+3` + + - **REFACTOR**(deriv_auth): Update_single_entry_feature ([#577](https://github.com/regentmarkets/flutter-deriv-packages/issues/577)). ([462d7bd6](https://github.com/regentmarkets/flutter-deriv-packages/commit/462d7bd6bdf60536fa632be3b95fae0ba377f142)) + + +## 2024-08-13 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`analytics` - `v2.2.2`](#analytics---v222) + - [`deriv_passkeys` - `v0.0.3+21`](#deriv_passkeys---v00321) + - [`deriv_auth` - `v6.7.34`](#deriv_auth---v6734) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_passkeys` - `v0.0.3+21` + - `deriv_auth` - `v6.7.34` + +--- + +#### `analytics` - `v2.2.2` + + - **REFACTOR**(analytics): Amend virtual n real tracking events ([#771](https://github.com/regentmarkets/flutter-deriv-packages/issues/771)). ([fc8850f0](https://github.com/regentmarkets/flutter-deriv-packages/commit/fc8850f01c1bb0a6805b41b3ff10e2a6e33b6e0f)) + + +## 2024-08-13 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.5.6`](#deriv_localizations---v156) + - [`deriv_mobile_chart_wrapper` - `v0.0.12+3`](#deriv_mobile_chart_wrapper---v00123) + - [`deriv_passkeys` - `v0.0.3+20`](#deriv_passkeys---v00320) + - [`deriv_auth` - `v6.7.33`](#deriv_auth---v6733) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_mobile_chart_wrapper` - `v0.0.12+3` + - `deriv_passkeys` - `v0.0.3+20` + - `deriv_auth` - `v6.7.33` + +--- + +#### `deriv_localizations` - `v1.5.6` + + - **FIX**(deriv_localizations): add strings for chart indicators ([#768](https://github.com/regentmarkets/flutter-deriv-packages/issues/768)). ([e4d52121](https://github.com/regentmarkets/flutter-deriv-packages/commit/e4d5212170996d14f834cc285f047ae821da7a71)) + + +## 2024-08-12 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`analytics` - `v2.2.1`](#analytics---v221) + - [`deriv_passkeys` - `v0.0.3+19`](#deriv_passkeys---v00319) + - [`deriv_auth` - `v6.7.32`](#deriv_auth---v6732) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_passkeys` - `v0.0.3+19` + - `deriv_auth` - `v6.7.32` + +--- + +#### `analytics` - `v2.2.1` + + - **FIX**(analytics): ilya/TRHM-945/Amend_Virtual_n_Real_Tracking_Events ([#762](https://github.com/regentmarkets/flutter-deriv-packages/issues/762)). ([cbba6688](https://github.com/regentmarkets/flutter-deriv-packages/commit/cbba668827d72c971d1100ecafee5719d4617639)) + + +## 2024-08-12 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_ui` - `v0.0.13+2`](#deriv_ui---v00132) + - [`deriv_mobile_chart_wrapper` - `v0.0.12+2`](#deriv_mobile_chart_wrapper---v00122) + - [`deriv_widgetbook` - `v0.0.2+19`](#deriv_widgetbook---v00219) + - [`deriv_passkeys` - `v0.0.3+18`](#deriv_passkeys---v00318) + - [`deriv_auth` - `v6.7.31`](#deriv_auth---v6731) + - [`deriv_language_selector` - `v0.0.2+17`](#deriv_language_selector---v00217) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_mobile_chart_wrapper` - `v0.0.12+2` + - `deriv_widgetbook` - `v0.0.2+19` + - `deriv_passkeys` - `v0.0.3+18` + - `deriv_auth` - `v6.7.31` + - `deriv_language_selector` - `v0.0.2+17` + +--- + +#### `deriv_ui` - `v0.0.13+2` + + - **FIX**(deriv_auth): focus node issue in BaseTextField ([#765](https://github.com/regentmarkets/flutter-deriv-packages/issues/765)). ([025e1457](https://github.com/regentmarkets/flutter-deriv-packages/commit/025e145799e508db461b64a7535269946c9d7370)) + + +## 2024-08-12 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.7.30`](#deriv_auth---v6730) + - [`deriv_ui` - `v0.0.13+1`](#deriv_ui---v00131) + - [`deriv_mobile_chart_wrapper` - `v0.0.12+1`](#deriv_mobile_chart_wrapper---v00121) + - [`deriv_widgetbook` - `v0.0.2+18`](#deriv_widgetbook---v00218) + - [`deriv_passkeys` - `v0.0.3+17`](#deriv_passkeys---v00317) + - [`deriv_language_selector` - `v0.0.2+16`](#deriv_language_selector---v00216) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_mobile_chart_wrapper` - `v0.0.12+1` + - `deriv_widgetbook` - `v0.0.2+18` + - `deriv_passkeys` - `v0.0.3+17` + - `deriv_language_selector` - `v0.0.2+16` + +--- + +#### `deriv_auth` - `v6.7.30` + + - **FIX**(deriv_auth): email and password text field validation ([#761](https://github.com/regentmarkets/flutter-deriv-packages/issues/761)). ([c75d00c4](https://github.com/regentmarkets/flutter-deriv-packages/commit/c75d00c4ef105a9a5ff3cdb3a8546ee43d76e997)) + +#### `deriv_ui` - `v0.0.13+1` + + - **FIX**(deriv_auth): email and password text field validation ([#761](https://github.com/regentmarkets/flutter-deriv-packages/issues/761)). ([c75d00c4](https://github.com/regentmarkets/flutter-deriv-packages/commit/c75d00c4ef105a9a5ff3cdb3a8546ee43d76e997)) + + +## 2024-08-07 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.7.29`](#deriv_auth---v6729) + +--- + +#### `deriv_auth` - `v6.7.29` + + - **FIX**(deriv_auth): email text field validation ([#751](https://github.com/regentmarkets/flutter-deriv-packages/issues/751)). ([3e05c3fa](https://github.com/regentmarkets/flutter-deriv-packages/commit/3e05c3fa9315a75d19fbc3727ad9a161617e7fb8)) + + +## 2024-08-06 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.0.12`](#deriv_mobile_chart_wrapper---v0012) + - [`deriv_ui` - `v0.0.13`](#deriv_ui---v0013) + - [`deriv_language_selector` - `v0.0.2+15`](#deriv_language_selector---v00215) + - [`deriv_auth` - `v6.7.28`](#deriv_auth---v6728) + - [`deriv_passkeys` - `v0.0.3+16`](#deriv_passkeys---v00316) + - [`deriv_widgetbook` - `v0.0.2+17`](#deriv_widgetbook---v00217) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_language_selector` - `v0.0.2+15` + - `deriv_auth` - `v6.7.28` + - `deriv_passkeys` - `v0.0.3+16` + - `deriv_widgetbook` - `v0.0.2+17` + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.0.12` + + - **FEAT**(deriv_mobile_chart_wrapper): bolinger and macd settings page ui ([#727](https://github.com/regentmarkets/flutter-deriv-packages/issues/727)). ([4be589cf](https://github.com/regentmarkets/flutter-deriv-packages/commit/4be589cf84c3593cf29438ca6c6e5613f4eafa49)) + +#### `deriv_ui` - `v0.0.13` + + - **FEAT**(deriv_mobile_chart_wrapper): bolinger and macd settings page ui ([#727](https://github.com/regentmarkets/flutter-deriv-packages/issues/727)). ([4be589cf](https://github.com/regentmarkets/flutter-deriv-packages/commit/4be589cf84c3593cf29438ca6c6e5613f4eafa49)) + + +## 2024-08-06 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.0.11`](#deriv_mobile_chart_wrapper---v0011) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.0.11` + + - **FEAT**(deriv_mobile_chart_wrapper): Implement MA setting page. ([#728](https://github.com/regentmarkets/flutter-deriv-packages/issues/728)). ([fa66abe8](https://github.com/regentmarkets/flutter-deriv-packages/commit/fa66abe81af1d016359b7d30b0fa870e8ebda61b)) + + +## 2024-08-06 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_ui` - `v0.0.12`](#deriv_ui---v0012) + - [`deriv_mobile_chart_wrapper` - `v0.0.10+1`](#deriv_mobile_chart_wrapper---v00101) + - [`deriv_language_selector` - `v0.0.2+14`](#deriv_language_selector---v00214) + - [`deriv_auth` - `v6.7.27`](#deriv_auth---v6727) + - [`deriv_passkeys` - `v0.0.3+15`](#deriv_passkeys---v00315) + - [`deriv_widgetbook` - `v0.0.2+16`](#deriv_widgetbook---v00216) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_mobile_chart_wrapper` - `v0.0.10+1` + - `deriv_language_selector` - `v0.0.2+14` + - `deriv_auth` - `v6.7.27` + - `deriv_passkeys` - `v0.0.3+15` + - `deriv_widgetbook` - `v0.0.2+16` + +--- + +#### `deriv_ui` - `v0.0.12` + + - **FEAT**(deriv_ui): trigger versioning for PR [#746](https://github.com/regentmarkets/flutter-deriv-packages/issues/746) ([#752](https://github.com/regentmarkets/flutter-deriv-packages/issues/752)). ([50a603d4](https://github.com/regentmarkets/flutter-deriv-packages/commit/50a603d45bd756f42a82584f96adecc097e2e93d)) + + +## 2024-08-05 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.0.10`](#deriv_mobile_chart_wrapper---v0010) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.0.10` + + - **FEAT**(deriv_mobile_chart_wrapper): add description bottomsheet for settings page ([#749](https://github.com/regentmarkets/flutter-deriv-packages/issues/749)). ([6ffd5128](https://github.com/regentmarkets/flutter-deriv-packages/commit/6ffd512845276a0e240ea07ad3aa299a39d16b55)) + + +## 2024-08-05 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.0.9`](#deriv_mobile_chart_wrapper---v009) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.0.9` + + - **FEAT**(deriv_mobile_chart_wrapper): add isResetEnabled to indicator bottomsheet ([#737](https://github.com/regentmarkets/flutter-deriv-packages/issues/737)). ([7105c272](https://github.com/regentmarkets/flutter-deriv-packages/commit/7105c272ea8f58b185a2fd899a0c71ccec78655b)) + + +## 2024-08-02 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.5.5`](#deriv_localizations---v155) + - [`deriv_mobile_chart_wrapper` - `v0.0.8+3`](#deriv_mobile_chart_wrapper---v0083) + - [`deriv_passkeys` - `v0.0.3+14`](#deriv_passkeys---v00314) + - [`deriv_auth` - `v6.7.26`](#deriv_auth---v6726) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_mobile_chart_wrapper` - `v0.0.8+3` + - `deriv_passkeys` - `v0.0.3+14` + - `deriv_auth` - `v6.7.26` + +--- + +#### `deriv_localizations` - `v1.5.5` + + - **FIX**(deriv_localizations): update numpad range values ([#742](https://github.com/regentmarkets/flutter-deriv-packages/issues/742)). ([5ad26f52](https://github.com/regentmarkets/flutter-deriv-packages/commit/5ad26f52c897397bc544fe4b23ca805e90cba66a)) + + +## 2024-08-02 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.0.8+2`](#deriv_mobile_chart_wrapper---v0082) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.0.8+2` + + - **FIX**(deriv_mobile_chart_wrapper): update mistyped value. ([#741](https://github.com/regentmarkets/flutter-deriv-packages/issues/741)). ([f60c782e](https://github.com/regentmarkets/flutter-deriv-packages/commit/f60c782ef8aa39b806fe676d6f17820de749777e)) + + +## 2024-08-02 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.5.4`](#deriv_localizations---v154) + - [`deriv_mobile_chart_wrapper` - `v0.0.8+1`](#deriv_mobile_chart_wrapper---v0081) + - [`deriv_auth` - `v6.7.25`](#deriv_auth---v6725) + - [`deriv_passkeys` - `v0.0.3+13`](#deriv_passkeys---v00313) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_mobile_chart_wrapper` - `v0.0.8+1` + - `deriv_auth` - `v6.7.25` + - `deriv_passkeys` - `v0.0.3+13` + +--- + +#### `deriv_localizations` - `v1.5.4` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#739](https://github.com/regentmarkets/flutter-deriv-packages/issues/739)). ([f8ef615a](https://github.com/regentmarkets/flutter-deriv-packages/commit/f8ef615a61b5abd5c2595a844d02543ce2086a6c)) + + +## 2024-08-01 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.0.8`](#deriv_mobile_chart_wrapper---v008) + - [`deriv_ui` - `v0.0.11`](#deriv_ui---v0011) + - [`deriv_passkeys` - `v0.0.3+12`](#deriv_passkeys---v00312) + - [`deriv_language_selector` - `v0.0.2+13`](#deriv_language_selector---v00213) + - [`deriv_auth` - `v6.7.24`](#deriv_auth---v6724) + - [`deriv_widgetbook` - `v0.0.2+15`](#deriv_widgetbook---v00215) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_passkeys` - `v0.0.3+12` + - `deriv_language_selector` - `v0.0.2+13` + - `deriv_auth` - `v6.7.24` + - `deriv_widgetbook` - `v0.0.2+15` + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.0.8` + + - **FEAT**(mobile_chart_wrapper): add RSI settings page ([#729](https://github.com/regentmarkets/flutter-deriv-packages/issues/729)). ([7f99cb1e](https://github.com/regentmarkets/flutter-deriv-packages/commit/7f99cb1e9b666b45bf63b9f2bcac6f4f38af26bf)) + +#### `deriv_ui` - `v0.0.11` + + - **FEAT**(mobile_chart_wrapper): add RSI settings page ([#729](https://github.com/regentmarkets/flutter-deriv-packages/issues/729)). ([7f99cb1e](https://github.com/regentmarkets/flutter-deriv-packages/commit/7f99cb1e9b666b45bf63b9f2bcac6f4f38af26bf)) + + +## 2024-07-31 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.0.7+2`](#deriv_mobile_chart_wrapper---v0072) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.0.7+2` + + - **FIX**(deriv_mobile_chart_wraper): change from index to color in the on change callback ([#735](https://github.com/regentmarkets/flutter-deriv-packages/issues/735)). ([d1470d8c](https://github.com/regentmarkets/flutter-deriv-packages/commit/d1470d8c7ebb23f11531227a9fbb31bbcc068043)) + + +## 2024-07-31 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.0.7+1`](#deriv_mobile_chart_wrapper---v0071) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.0.7+1` + + - **FIX**(deriv_mobile_chart_wraper): improve color selector ([#732](https://github.com/regentmarkets/flutter-deriv-packages/issues/732)). ([45968888](https://github.com/regentmarkets/flutter-deriv-packages/commit/45968888edac04db08bafe44d6e8de87f447d6c5)) + + +## 2024-07-31 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.0.7`](#deriv_mobile_chart_wrapper---v007) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.0.7` + + - **FEAT**(deriv_mobile_chart_wrapper): Add the base setting page. ([#730](https://github.com/regentmarkets/flutter-deriv-packages/issues/730)). ([71bd6164](https://github.com/regentmarkets/flutter-deriv-packages/commit/71bd616447f68ec7eaf26ab9ecb40882d7dde0d7)) + + +## 2024-07-29 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.0.6+2`](#deriv_mobile_chart_wrapper---v0062) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.0.6+2` + + - **REFACTOR**(deriv_mobile_chart_wrapper): change back the chart dependency to dev ([#722](https://github.com/regentmarkets/flutter-deriv-packages/issues/722)). ([d09d364b](https://github.com/regentmarkets/flutter-deriv-packages/commit/d09d364b61589cdfe16fbd0f7254827e9a8d5157)) + + +## 2024-07-29 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.0.6+1`](#deriv_mobile_chart_wrapper---v0061) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.0.6+1` + + - **REFACTOR**(deriv_mobile_chart_wrapper): Improve colour selector. ([#724](https://github.com/regentmarkets/flutter-deriv-packages/issues/724)). ([a752dd8e](https://github.com/regentmarkets/flutter-deriv-packages/commit/a752dd8ed66d70878959418679ff2b9a7e28db25)) + + +## 2024-07-26 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.0.6`](#deriv_mobile_chart_wrapper---v006) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.0.6` + + - **FEAT**(deriv_mobile_chart_wrapper): update the chart package to get the new icons. ([#688](https://github.com/regentmarkets/flutter-deriv-packages/issues/688)). ([849afe72](https://github.com/regentmarkets/flutter-deriv-packages/commit/849afe72a4a52230de3d06ca1194ecf017f34e11)) + + +## 2024-07-26 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.0.5`](#deriv_mobile_chart_wrapper---v005) + - [`deriv_ui` - `v0.0.10`](#deriv_ui---v0010) + - [`deriv_passkeys` - `v0.0.3+11`](#deriv_passkeys---v00311) + - [`deriv_language_selector` - `v0.0.2+12`](#deriv_language_selector---v00212) + - [`deriv_auth` - `v6.7.23`](#deriv_auth---v6723) + - [`deriv_widgetbook` - `v0.0.2+14`](#deriv_widgetbook---v00214) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_passkeys` - `v0.0.3+11` + - `deriv_language_selector` - `v0.0.2+12` + - `deriv_auth` - `v6.7.23` + - `deriv_widgetbook` - `v0.0.2+14` + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.0.5` + + - **FEAT**(deriv_ui): add deriv bottom sheet. feat(deriv_mobile_chart_wrapper): add indicator description ([#713](https://github.com/regentmarkets/flutter-deriv-packages/issues/713)). ([50cfbe20](https://github.com/regentmarkets/flutter-deriv-packages/commit/50cfbe2008076a073331e555228b98a11a35f2ed)) + +#### `deriv_ui` - `v0.0.10` + + - **FEAT**(deriv_ui): add deriv bottom sheet. feat(deriv_mobile_chart_wrapper): add indicator description ([#713](https://github.com/regentmarkets/flutter-deriv-packages/issues/713)). ([50cfbe20](https://github.com/regentmarkets/flutter-deriv-packages/commit/50cfbe2008076a073331e555228b98a11a35f2ed)) + + +## 2024-07-26 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.0.4+4`](#deriv_mobile_chart_wrapper---v0044) + - [`deriv_ui` - `v0.0.9+1`](#deriv_ui---v0091) + - [`deriv_passkeys` - `v0.0.3+10`](#deriv_passkeys---v00310) + - [`deriv_language_selector` - `v0.0.2+11`](#deriv_language_selector---v00211) + - [`deriv_auth` - `v6.7.22`](#deriv_auth---v6722) + - [`deriv_widgetbook` - `v0.0.2+13`](#deriv_widgetbook---v00213) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_passkeys` - `v0.0.3+10` + - `deriv_language_selector` - `v0.0.2+11` + - `deriv_auth` - `v6.7.22` + - `deriv_widgetbook` - `v0.0.2+13` + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.0.4+4` + + - **FIX**(deriv_mobile_chart_wrapper): move core widgets from chart wrapper to deriv ui ([#712](https://github.com/regentmarkets/flutter-deriv-packages/issues/712)). ([d870e2c9](https://github.com/regentmarkets/flutter-deriv-packages/commit/d870e2c93f1157fae1692f836fc9c5bee85b8e21)) + +#### `deriv_ui` - `v0.0.9+1` + + - **FIX**(deriv_mobile_chart_wrapper): move core widgets from chart wrapper to deriv ui ([#712](https://github.com/regentmarkets/flutter-deriv-packages/issues/712)). ([d870e2c9](https://github.com/regentmarkets/flutter-deriv-packages/commit/d870e2c93f1157fae1692f836fc9c5bee85b8e21)) + + +## 2024-07-24 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_ui` - `v0.0.9`](#deriv_ui---v009) + - [`deriv_auth` - `v6.7.21`](#deriv_auth---v6721) + - [`deriv_mobile_chart_wrapper` - `v0.0.4+3`](#deriv_mobile_chart_wrapper---v0043) + - [`deriv_passkeys` - `v0.0.3+9`](#deriv_passkeys---v0039) + - [`deriv_language_selector` - `v0.0.2+10`](#deriv_language_selector---v00210) + - [`deriv_widgetbook` - `v0.0.2+12`](#deriv_widgetbook---v00212) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.7.21` + - `deriv_mobile_chart_wrapper` - `v0.0.4+3` + - `deriv_passkeys` - `v0.0.3+9` + - `deriv_language_selector` - `v0.0.2+10` + - `deriv_widgetbook` - `v0.0.2+12` + +--- + +#### `deriv_ui` - `v0.0.9` + + - **FEAT**(deriv_ui): add the ability to disable the dot button. ([#706](https://github.com/regentmarkets/flutter-deriv-packages/issues/706)). ([44db38e5](https://github.com/regentmarkets/flutter-deriv-packages/commit/44db38e5daa0c0941ada4eb698d7317a97209093)) + + +## 2024-07-24 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`analytics` - `v2.2.0`](#analytics---v220) + - [`deriv_auth` - `v6.7.20`](#deriv_auth---v6720) + - [`deriv_passkeys` - `v0.0.3+8`](#deriv_passkeys---v0038) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.7.20` + - `deriv_passkeys` - `v0.0.3+8` + +--- + +#### `analytics` - `v2.2.0` + + - **FEAT**(analytics): add trade page events to rudderstack events ([#691](https://github.com/regentmarkets/flutter-deriv-packages/issues/691)). ([b9e2b098](https://github.com/regentmarkets/flutter-deriv-packages/commit/b9e2b098fb76ea4f8f5f633c062a6cd20f4db6f0)) + + +## 2024-07-23 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.0.4+2`](#deriv_mobile_chart_wrapper---v0042) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.0.4+2` + + - **REFACTOR**(deriv_mobile_chart_wrapper): Ramin/integrate with new version of chart package ([#702](https://github.com/regentmarkets/flutter-deriv-packages/issues/702)). ([b4a38374](https://github.com/regentmarkets/flutter-deriv-packages/commit/b4a38374dd47e8bde9c1bdc02e93ad78a1e64bd1)) + + +## 2024-07-20 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.5.3`](#deriv_localizations---v153) + - [`deriv_mobile_chart_wrapper` - `v0.0.4+1`](#deriv_mobile_chart_wrapper---v0041) + - [`deriv_auth` - `v6.7.19`](#deriv_auth---v6719) + - [`deriv_passkeys` - `v0.0.3+7`](#deriv_passkeys---v0037) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_mobile_chart_wrapper` - `v0.0.4+1` + - `deriv_auth` - `v6.7.19` + - `deriv_passkeys` - `v0.0.3+7` + +--- + +#### `deriv_localizations` - `v1.5.3` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#708](https://github.com/regentmarkets/flutter-deriv-packages/issues/708)). ([8d8a9093](https://github.com/regentmarkets/flutter-deriv-packages/commit/8d8a90931441a4b0e9caeac437954010e4f8763b)) + + +## 2024-07-19 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_logger` - `v0.0.3`](#deriv_logger---v003) + - [`deriv_mobile_chart_wrapper` - `v0.0.4`](#deriv_mobile_chart_wrapper---v004) + +--- + +#### `deriv_logger` - `v0.0.3` + + - **FEAT**(deriv_logger): [DERG-2590] Add ability to inspect incoming and outgoing network payloads in UI ([#685](https://github.com/regentmarkets/flutter-deriv-packages/issues/685)). ([4cd38b2a](https://github.com/regentmarkets/flutter-deriv-packages/commit/4cd38b2a34288d049333c7cf0cbeb41b671ebc7c)) + +#### `deriv_mobile_chart_wrapper` - `v0.0.4` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + - **FIX**(deriv_mobile_chart_wrapper): update readme file ([#672](https://github.com/regentmarkets/flutter-deriv-packages/issues/672)). ([13e6b3f3](https://github.com/regentmarkets/flutter-deriv-packages/commit/13e6b3f35ba863098fd9785daaa8ccc7cb23b388)) + - **FEAT**(deriv_mobile_chart_wrapper): Add Indicator bottom sheet and categories_ ([#683](https://github.com/regentmarkets/flutter-deriv-packages/issues/683)). ([a32c7ed0](https://github.com/regentmarkets/flutter-deriv-packages/commit/a32c7ed0f61e1c9965cc7d92f12e64eacb0faf52)) + + +## 2024-07-18 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.0.3`](#deriv_mobile_chart_wrapper---v003) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.0.3` + + - **FEAT**(deriv_mobile_chart_wrapper): Add Indicator bottom sheet and categories_ ([#683](https://github.com/regentmarkets/flutter-deriv-packages/issues/683)). ([a32c7ed0](https://github.com/regentmarkets/flutter-deriv-packages/commit/a32c7ed0f61e1c9965cc7d92f12e64eacb0faf52)) + + +## 2024-07-18 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.5.2`](#deriv_localizations---v152) + - [`deriv_auth` - `v6.7.18`](#deriv_auth---v6718) + - [`deriv_mobile_chart_wrapper` - `v0.0.2+4`](#deriv_mobile_chart_wrapper---v0024) + - [`deriv_passkeys` - `v0.0.3+6`](#deriv_passkeys---v0036) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.7.18` + - `deriv_mobile_chart_wrapper` - `v0.0.2+4` + - `deriv_passkeys` - `v0.0.3+6` + +--- + +#### `deriv_localizations` - `v1.5.2` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#697](https://github.com/regentmarkets/flutter-deriv-packages/issues/697)). ([0aca8f2f](https://github.com/regentmarkets/flutter-deriv-packages/commit/0aca8f2fd4f0e3a16ab54a0bf040c1fabcff2324)) + + +## 2024-07-17 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.7.17`](#deriv_auth---v6717) + - [`deriv_passkeys` - `v0.0.3+5`](#deriv_passkeys---v0035) + +--- + +#### `deriv_auth` - `v6.7.17` + + - **REFACTOR**(version): updated the version of flutter deriv api ([#694](https://github.com/regentmarkets/flutter-deriv-packages/issues/694)). ([eac7e8cb](https://github.com/regentmarkets/flutter-deriv-packages/commit/eac7e8cba4e9310d30296e07a47731f08d4d7342)) + +#### `deriv_passkeys` - `v0.0.3+5` + + - **REFACTOR**(version): updated the version of flutter deriv api ([#694](https://github.com/regentmarkets/flutter-deriv-packages/issues/694)). ([eac7e8cb](https://github.com/regentmarkets/flutter-deriv-packages/commit/eac7e8cba4e9310d30296e07a47731f08d4d7342)) + + +## 2024-07-16 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_passkeys` - `v0.0.3+4`](#deriv_passkeys---v0034) + - [`deriv_auth` - `v6.7.16`](#deriv_auth---v6716) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.7.16` + +--- + +#### `deriv_passkeys` - `v0.0.3+4` + + - **FIX**(deriv_passkeys): fix some missing keys in passkey login page ([#692](https://github.com/regentmarkets/flutter-deriv-packages/issues/692)). ([d944a1c3](https://github.com/regentmarkets/flutter-deriv-packages/commit/d944a1c37f127f35143d9920532f76bc3487ebd4)) + + +## 2024-07-16 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.7.15`](#deriv_auth---v6715) + - [`deriv_language_selector` - `v0.0.2+9`](#deriv_language_selector---v0029) + - [`deriv_mobile_chart_wrapper` - `v0.0.2+3`](#deriv_mobile_chart_wrapper---v0023) + - [`deriv_passkeys` - `v0.0.3+3`](#deriv_passkeys---v0033) + - [`deriv_ui` - `v0.0.8+1`](#deriv_ui---v0081) + - [`deriv_widgetbook` - `v0.0.2+11`](#deriv_widgetbook---v00211) + - [`update_checker` - `v1.5.1`](#update_checker---v151) + +--- + +#### `deriv_auth` - `v6.7.15` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +#### `deriv_language_selector` - `v0.0.2+9` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +#### `deriv_mobile_chart_wrapper` - `v0.0.2+3` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +#### `deriv_passkeys` - `v0.0.3+3` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +#### `deriv_ui` - `v0.0.8+1` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +#### `deriv_widgetbook` - `v0.0.2+11` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +#### `update_checker` - `v1.5.1` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + + +## 2024-07-15 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_ui` - `v0.0.8`](#deriv_ui---v008) + - [`update_checker` - `v1.5.0`](#update_checker---v150) + - [`deriv_auth` - `v6.7.14`](#deriv_auth---v6714) + - [`deriv_mobile_chart_wrapper` - `v0.0.2+2`](#deriv_mobile_chart_wrapper---v0022) + - [`deriv_passkeys` - `v0.0.3+2`](#deriv_passkeys---v0032) + - [`deriv_widgetbook` - `v0.0.2+10`](#deriv_widgetbook---v00210) + - [`deriv_language_selector` - `v0.0.2+8`](#deriv_language_selector---v0028) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.7.14` + - `deriv_mobile_chart_wrapper` - `v0.0.2+2` + - `deriv_passkeys` - `v0.0.3+2` + - `deriv_widgetbook` - `v0.0.2+10` + - `deriv_language_selector` - `v0.0.2+8` + +--- + +#### `deriv_ui` - `v0.0.8` + + - **FEAT**(deriv_ui): [DERG 2450] Added Timeline Widget to Deriv UI ([#631](https://github.com/regentmarkets/flutter-deriv-packages/issues/631)). ([e34d78b3](https://github.com/regentmarkets/flutter-deriv-packages/commit/e34d78b303358cb5f91abab14a2a042ce3650b0f)) + +#### `update_checker` - `v1.5.0` + + - **FEAT**(deriv_ui): [DERG 2450] Added Timeline Widget to Deriv UI ([#631](https://github.com/regentmarkets/flutter-deriv-packages/issues/631)). ([e34d78b3](https://github.com/regentmarkets/flutter-deriv-packages/commit/e34d78b303358cb5f91abab14a2a042ce3650b0f)) + + +## 2024-07-11 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_passkeys` - `v0.0.3+1`](#deriv_passkeys---v0031) + - [`deriv_auth` - `v6.7.13`](#deriv_auth---v6713) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.7.13` + +--- + +#### `deriv_passkeys` - `v0.0.3+1` + + - **FIX**(deriv_passkeys): add keys to passkey login page ([#676](https://github.com/regentmarkets/flutter-deriv-packages/issues/676)). ([aa84a46d](https://github.com/regentmarkets/flutter-deriv-packages/commit/aa84a46dfb9cd22a335276c1ae0063ffee7852ef)) + + +## 2024-07-10 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_passkeys` - `v0.0.3`](#deriv_passkeys---v003) + - [`deriv_auth` - `v6.7.12`](#deriv_auth---v6712) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.7.12` + +--- + +#### `deriv_passkeys` - `v0.0.3` + + - **FEAT**(deriv_passkeys): [P2PS-3072] add keys to deriv passkey package effortless passkeys login page ([#677](https://github.com/regentmarkets/flutter-deriv-packages/issues/677)). ([39472704](https://github.com/regentmarkets/flutter-deriv-packages/commit/39472704a3d264bc5f64ba2ae75e29134f890590)) + + +## 2024-07-04 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.0.2+1`](#deriv_mobile_chart_wrapper---v0021) + - deriv_auth-v6.7.11 + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.0.2+1` + + - **FIX**(deriv_mobile_chart_wrapper): update readme file ([#672](https://github.com/regentmarkets/flutter-deriv-packages/issues/672)). ([13e6b3f3](https://github.com/regentmarkets/flutter-deriv-packages/commit/13e6b3f35ba863098fd9785daaa8ccc7cb23b388)) + + +## 2024-07-03 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.7.10`](#deriv_auth---v6710) + +--- + +#### `deriv_auth` - `v6.7.10` + + - **FIX**(deriv_auth): minor change in readme file ([#669](https://github.com/regentmarkets/flutter-deriv-packages/issues/669)). ([b29d222c](https://github.com/regentmarkets/flutter-deriv-packages/commit/b29d222ce219d0664e3cafb6c302cd1041749905)) + + +## 2024-07-02 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.7.9`](#deriv_auth---v679) + +--- + +#### `deriv_auth` - `v6.7.9` + + - **REFACTOR**(deriv_auth): Replace uni_links2 with app_links ([#664](https://github.com/regentmarkets/flutter-deriv-packages/issues/664)). ([f99554bc](https://github.com/regentmarkets/flutter-deriv-packages/commit/f99554bc134b7fe7fe0b3f5bf7555728868176c9)) + + +## 2024-07-02 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.7.8`](#deriv_auth---v678) + +--- + +#### `deriv_auth` - `v6.7.8` + + - **FIX**(deriv_auth): fix_error_when_initializing_mock_auth_service ([#660](https://github.com/regentmarkets/flutter-deriv-packages/issues/660)). ([30aa969f](https://github.com/regentmarkets/flutter-deriv-packages/commit/30aa969f7ed3f083ab1610518eded103d0aa2eb7)) + + +## 2024-07-01 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.7.7`](#deriv_auth---v677) + - [`deriv_passkeys` - `v0.0.2+9`](#deriv_passkeys---v0029) + +--- + +#### `deriv_auth` - `v6.7.7` + + - **REFACTOR**(deriv_passkeys): update deriv api dependency ([#656](https://github.com/regentmarkets/flutter-deriv-packages/issues/656)). ([3425078b](https://github.com/regentmarkets/flutter-deriv-packages/commit/3425078b52baac4f387504c9d41063bda1dba249)) + +#### `deriv_passkeys` - `v0.0.2+9` + + - **REFACTOR**(deriv_passkeys): update deriv api dependency ([#656](https://github.com/regentmarkets/flutter-deriv-packages/issues/656)). ([3425078b](https://github.com/regentmarkets/flutter-deriv-packages/commit/3425078b52baac4f387504c9d41063bda1dba249)) + + +## 2024-06-28 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_mobile_chart_wrapper` - `v0.0.2`](#deriv_mobile_chart_wrapper---v002) + +--- + +#### `deriv_mobile_chart_wrapper` - `v0.0.2` + + - **FEAT**(deriv_mobile_chart_wrapper): [DERG-2498] create deriv_mobile_chart_wrapper package ([#626](https://github.com/regentmarkets/flutter-deriv-packages/issues/626)). ([01aec1ed](https://github.com/regentmarkets/flutter-deriv-packages/commit/01aec1edbd8f02e21951918ec86f00fb28ef2c58)) + + +## 2024-06-28 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`analytics` - `v2.1.0`](#analytics---v210) + - [`deriv_auth` - `v6.7.6`](#deriv_auth---v676) + - [`deriv_passkeys` - `v0.0.2+8`](#deriv_passkeys---v0028) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.7.6` + - `deriv_passkeys` - `v0.0.2+8` + +--- + +#### `analytics` - `v2.1.0` + + - **FEAT**(analytics): ilya/DERG-2409/Add_tracking_to_real_account_sign_up_on_Deriv_Go ([#603](https://github.com/regentmarkets/flutter-deriv-packages/issues/603)). ([9ada725d](https://github.com/regentmarkets/flutter-deriv-packages/commit/9ada725ded271e2b94cffed622eff2e75539cb55)) + + +## 2024-06-27 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_passkeys` - `v0.0.2+7`](#deriv_passkeys---v0027) + - [`deriv_auth` - `v6.7.5`](#deriv_auth---v675) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.7.5` + +--- + +#### `deriv_passkeys` - `v0.0.2+7` + + - **FIX**(deriv_passkeys): Fix_logout_issue_by_fetching_refresh_token ([#630](https://github.com/regentmarkets/flutter-deriv-packages/issues/630)). ([282278ae](https://github.com/regentmarkets/flutter-deriv-packages/commit/282278aeb27256eaa37660f58eff704d27e92c93)) + + +## 2024-06-26 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.5.1`](#deriv_localizations---v151) + - [`deriv_auth` - `v6.7.4`](#deriv_auth---v674) + - [`deriv_passkeys` - `v0.0.2+6`](#deriv_passkeys---v0026) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.7.4` + - `deriv_passkeys` - `v0.0.2+6` + +--- + +#### `deriv_localizations` - `v1.5.1` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#648](https://github.com/regentmarkets/flutter-deriv-packages/issues/648)). ([c925c22a](https://github.com/regentmarkets/flutter-deriv-packages/commit/c925c22a97e6e13568fefa0d86d993985093617c)) + + +## 2024-06-26 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.5.0`](#deriv_localizations---v150) + - [`deriv_auth` - `v6.7.3`](#deriv_auth---v673) + - [`deriv_passkeys` - `v0.0.2+5`](#deriv_passkeys---v0025) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.7.3` + - `deriv_passkeys` - `v0.0.2+5` + +--- + +#### `deriv_localizations` - `v1.5.0` + + - **FEAT**(deriv_localizations): add localization for deriv mobile chart wrapper package ([#627](https://github.com/regentmarkets/flutter-deriv-packages/issues/627)). ([33f5e3a1](https://github.com/regentmarkets/flutter-deriv-packages/commit/33f5e3a1bc0765cb8559b5a39e300b8c088aa705)) + + +## 2024-06-26 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_passkeys` - `v0.0.2+4`](#deriv_passkeys---v0024) + - [`deriv_auth` - `v6.7.2`](#deriv_auth---v672) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.7.2` + +--- + +#### `deriv_passkeys` - `v0.0.2+4` + + - **FIX**(deriv_passkeys): fix passkeys success button styles ([#629](https://github.com/regentmarkets/flutter-deriv-packages/issues/629)). ([d1fb8590](https://github.com/regentmarkets/flutter-deriv-packages/commit/d1fb8590eb5eb55f9bd9db3a255f33cf152f364d)) + + +## 2024-06-25 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.7.1`](#deriv_auth---v671) + - [`deriv_web_view` - `v0.2.2+3`](#deriv_web_view---v0223) + - [`deriv_ui` - `v0.0.7+9`](#deriv_ui---v0079) + - [`deriv_widgetbook` - `v0.0.2+9`](#deriv_widgetbook---v0029) + - [`deriv_passkeys` - `v0.0.2+3`](#deriv_passkeys---v0023) + - [`deriv_language_selector` - `v0.0.2+7`](#deriv_language_selector---v0027) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_ui` - `v0.0.7+9` + - `deriv_widgetbook` - `v0.0.2+9` + - `deriv_passkeys` - `v0.0.2+3` + - `deriv_language_selector` - `v0.0.2+7` + +--- + +#### `deriv_auth` - `v6.7.1` + + - **FIX**(deriv_auth): Fix login provider null issue ([#641](https://github.com/regentmarkets/flutter-deriv-packages/issues/641)). ([e4181541](https://github.com/regentmarkets/flutter-deriv-packages/commit/e41815416eb76b3724481f8a0b980e943311ebeb)) + - **FIX**(deriv_web_view): update deriv ui version in deriv auth ([#639](https://github.com/regentmarkets/flutter-deriv-packages/issues/639)). ([11b46a9f](https://github.com/regentmarkets/flutter-deriv-packages/commit/11b46a9f7bd00482d3cac7820cf4cd5d61da0cad)) + +#### `deriv_web_view` - `v0.2.2+3` + + - **FIX**(deriv_web_view): update deriv web view readme file ([#637](https://github.com/regentmarkets/flutter-deriv-packages/issues/637)). ([b0b66dad](https://github.com/regentmarkets/flutter-deriv-packages/commit/b0b66dadb6cb1b30dc3ad759f2eee031673b6bbf)) + + +## 2024-06-25 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.7.0`](#deriv_auth---v670) + - [`deriv_web_view` - `v0.2.2+2`](#deriv_web_view---v0222) + - [`update_checker` - `v1.4.0`](#update_checker---v140) + - [`deriv_ui` - `v0.0.7+8`](#deriv_ui---v0078) + - [`deriv_widgetbook` - `v0.0.2+8`](#deriv_widgetbook---v0028) + - [`deriv_passkeys` - `v0.0.2+2`](#deriv_passkeys---v0022) + - [`deriv_language_selector` - `v0.0.2+6`](#deriv_language_selector---v0026) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_ui` - `v0.0.7+8` + - `deriv_widgetbook` - `v0.0.2+8` + - `deriv_passkeys` - `v0.0.2+2` + - `deriv_language_selector` - `v0.0.2+6` + +--- + +#### `deriv_auth` - `v6.7.0` + + - **FIX**(deriv_web_view): update deriv ui version in deriv auth ([#639](https://github.com/regentmarkets/flutter-deriv-packages/issues/639)). ([11b46a9f](https://github.com/regentmarkets/flutter-deriv-packages/commit/11b46a9f7bd00482d3cac7820cf4cd5d61da0cad)) + - **FEAT**(deriv_auth): add log in user tracking events. ([#620](https://github.com/regentmarkets/flutter-deriv-packages/issues/620)). ([ae9556cf](https://github.com/regentmarkets/flutter-deriv-packages/commit/ae9556cf3af98196bb22e351d9a8eccbf534889c)) + +#### `deriv_web_view` - `v0.2.2+2` + + - **FIX**(deriv_web_view): update deriv web view readme file ([#637](https://github.com/regentmarkets/flutter-deriv-packages/issues/637)). ([b0b66dad](https://github.com/regentmarkets/flutter-deriv-packages/commit/b0b66dadb6cb1b30dc3ad759f2eee031673b6bbf)) + - **FIX**(deriv_webview): add language parameter to needed classes ([#600](https://github.com/regentmarkets/flutter-deriv-packages/issues/600)). ([9b857999](https://github.com/regentmarkets/flutter-deriv-packages/commit/9b857999f479486830671f8b8e8b7fc26dcd769b)) + +#### `update_checker` - `v1.4.0` + + - **FEAT**(update_checker): add the ability to change the key from the app side ([#628](https://github.com/regentmarkets/flutter-deriv-packages/issues/628)). ([b18609a0](https://github.com/regentmarkets/flutter-deriv-packages/commit/b18609a00533aaab6d6962eb89f323e2f560df8b)) + + +## 2024-06-24 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_ui` - `v0.0.7+7`](#deriv_ui---v0077) + - [`deriv_auth` - `v6.6.9`](#deriv_auth---v669) + - [`deriv_passkeys` - `v0.0.2+1`](#deriv_passkeys---v0021) + - [`deriv_widgetbook` - `v0.0.2+7`](#deriv_widgetbook---v0027) + - [`deriv_language_selector` - `v0.0.2+5`](#deriv_language_selector---v0025) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.6.9` + - `deriv_passkeys` - `v0.0.2+1` + - `deriv_widgetbook` - `v0.0.2+7` + - `deriv_language_selector` - `v0.0.2+5` + +--- + +#### `deriv_ui` - `v0.0.7+7` + + - **REFACTOR**(deriv_ui): [DERG-3500] return inputted amount based on the formatter in numpad ([#624](https://github.com/regentmarkets/flutter-deriv-packages/issues/624)). ([fdf4df19](https://github.com/regentmarkets/flutter-deriv-packages/commit/fdf4df1979ddb8710dba8925ef779cca25d60615)) + + +## 2024-06-21 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_passkeys` - `v0.0.2`](#deriv_passkeys---v002) + - [`deriv_auth` - `v6.6.8`](#deriv_auth---v668) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.6.8` + +--- + +#### `deriv_passkeys` - `v0.0.2` + + - **REFACTOR**(deriv_passkeys): Removed deprecated linter rules ([#625](https://github.com/regentmarkets/flutter-deriv-packages/issues/625)). ([2fdc28f2](https://github.com/regentmarkets/flutter-deriv-packages/commit/2fdc28f20b8efe2ddc3a9a261c40b533307f25e3)) + - **FEAT**(deriv_passkeys): add user tracking events. ([#607](https://github.com/regentmarkets/flutter-deriv-packages/issues/607)). ([d86b51e2](https://github.com/regentmarkets/flutter-deriv-packages/commit/d86b51e2fe4ca4d18768d0ba17567a388a8d360d)) + + +## 2024-06-20 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_logger` - `v0.0.2`](#deriv_logger---v002) + - [`update_checker` - `v1.3.0`](#update_checker---v130) + +--- + +#### `deriv_logger` - `v0.0.2` + + - **FEAT**(deriv_logger): add ability to print prettified logs in console and UI ([#608](https://github.com/regentmarkets/flutter-deriv-packages/issues/608)). ([5a91c24b](https://github.com/regentmarkets/flutter-deriv-packages/commit/5a91c24bde607ff37940edf18f8dfac67d3fc4fa)) + +#### `update_checker` - `v1.3.0` + + - **FEAT**(deriv_logger): add ability to print prettified logs in console and UI ([#608](https://github.com/regentmarkets/flutter-deriv-packages/issues/608)). ([5a91c24b](https://github.com/regentmarkets/flutter-deriv-packages/commit/5a91c24bde607ff37940edf18f8dfac67d3fc4fa)) + + +## 2024-06-14 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.4.4`](#deriv_localizations---v144) + - [`deriv_auth` - `v6.6.7`](#deriv_auth---v667) + - [`deriv_passkeys` - `v0.0.1+8`](#deriv_passkeys---v0018) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.6.7` + - `deriv_passkeys` - `v0.0.1+8` + +--- + +#### `deriv_localizations` - `v1.4.4` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#617](https://github.com/regentmarkets/flutter-deriv-packages/issues/617)). ([1d5e5f14](https://github.com/regentmarkets/flutter-deriv-packages/commit/1d5e5f141640aa00546dcbb31d2db8eb9a994452)) + + +## 2024-06-14 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.6.6`](#deriv_auth---v666) + - [`deriv_localizations` - `v1.4.3`](#deriv_localizations---v143) + - [`deriv_passkeys` - `v0.0.1+7`](#deriv_passkeys---v0017) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_passkeys` - `v0.0.1+7` + +--- + +#### `deriv_auth` - `v6.6.6` + + - **REFACTOR**(deriv_auth): added a flag to allow hiding passkeys button ([#612](https://github.com/regentmarkets/flutter-deriv-packages/issues/612)). ([a4026a9d](https://github.com/regentmarkets/flutter-deriv-packages/commit/a4026a9d8164abc1c66beb327d48610d8ce30dde)) + +#### `deriv_localizations` - `v1.4.3` + + - **REFACTOR**(deriv_localizations): Update p2p strings for passkeys ([#615](https://github.com/regentmarkets/flutter-deriv-packages/issues/615)). ([5fd85af2](https://github.com/regentmarkets/flutter-deriv-packages/commit/5fd85af24394ea68b8b0a7abc854b9c33b791c26)) + + +## 2024-06-13 + +### Changes + +--- + +Packages with breaking changes: + + - [`analytics` - `v2.0.0`](#analytics---v200) + +Packages with other changes: + + - [`deriv_localizations` - `v1.4.2`](#deriv_localizations---v142) + - [`deriv_auth` - `v6.6.5`](#deriv_auth---v665) + - [`deriv_passkeys` - `v0.0.1+6`](#deriv_passkeys---v0016) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.6.5` + - `deriv_passkeys` - `v0.0.1+6` + +--- + +#### `analytics` - `v2.0.0` + + - **BREAKING** **REFACTOR**(analytics): added logAppOpen event ([#610](https://github.com/regentmarkets/flutter-deriv-packages/issues/610)). ([74f8d9c3](https://github.com/regentmarkets/flutter-deriv-packages/commit/74f8d9c3a7311ec7abb1cfe76c3f6f190fbcb81a)) + +#### `deriv_localizations` - `v1.4.2` + + - **FIX**(deriv-localization): fix passkey button strings ([#606](https://github.com/regentmarkets/flutter-deriv-packages/issues/606)). ([ed2a7ea9](https://github.com/regentmarkets/flutter-deriv-packages/commit/ed2a7ea958e34aa027ecb9ef6919f04fd5c7d5f1)) + + +## 2024-06-07 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.6.4`](#deriv_auth---v664) + - [`deriv_date_range_picker` - `v0.0.1+6`](#deriv_date_range_picker---v0016) + - [`deriv_expandable_bottom_sheet` - `v0.0.1+6`](#deriv_expandable_bottom_sheet---v0016) + - [`deriv_numpad` - `v1.1.5`](#deriv_numpad---v115) + - [`deriv_passkeys` - `v0.0.1+5`](#deriv_passkeys---v0015) + - [`deriv_ui` - `v0.0.7+6`](#deriv_ui---v0076) + - [`deriv_widgetbook` - `v0.0.2+6`](#deriv_widgetbook---v0026) + - [`deriv_language_selector` - `v0.0.2+4`](#deriv_language_selector---v0024) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_language_selector` - `v0.0.2+4` + +--- + +#### `deriv_auth` - `v6.6.4` + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + +#### `deriv_date_range_picker` - `v0.0.1+6` + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + +#### `deriv_expandable_bottom_sheet` - `v0.0.1+6` + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + +#### `deriv_numpad` - `v1.1.5` + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + +#### `deriv_passkeys` - `v0.0.1+5` + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + - **FIX**(deriv_passkeys): Increased iOS support to 16 ([#601](https://github.com/regentmarkets/flutter-deriv-packages/issues/601)). ([b136424f](https://github.com/regentmarkets/flutter-deriv-packages/commit/b136424f144454727d670c3076074ed0e7197ae0)) + +#### `deriv_ui` - `v0.0.7+6` + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + +#### `deriv_widgetbook` - `v0.0.2+6` + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + + +## 2024-05-30 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.6.3`](#deriv_auth---v663) + - [`deriv_date_range_picker` - `v0.0.1+5`](#deriv_date_range_picker---v0015) + - [`deriv_expandable_bottom_sheet` - `v0.0.1+5`](#deriv_expandable_bottom_sheet---v0015) + - [`deriv_numpad` - `v1.1.4`](#deriv_numpad---v114) + - [`deriv_passkeys` - `v0.0.1+4`](#deriv_passkeys---v0014) + - [`deriv_ui` - `v0.0.7+5`](#deriv_ui---v0075) + - [`deriv_widgetbook` - `v0.0.2+5`](#deriv_widgetbook---v0025) + - [`deriv_language_selector` - `v0.0.2+3`](#deriv_language_selector---v0023) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_language_selector` - `v0.0.2+3` + +--- + +#### `deriv_auth` - `v6.6.3` + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + +#### `deriv_date_range_picker` - `v0.0.1+5` + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + +#### `deriv_expandable_bottom_sheet` - `v0.0.1+5` + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + +#### `deriv_numpad` - `v1.1.4` + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + +#### `deriv_passkeys` - `v0.0.1+4` + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + +#### `deriv_ui` - `v0.0.7+5` + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + +#### `deriv_widgetbook` - `v0.0.2+5` + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + + +## 2024-05-29 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_feature_flag` - `v0.1.1`](#deriv_feature_flag---v011) + +--- + +#### `deriv_feature_flag` - `v0.1.1` + + - **FEAT**(deriv_feature_flag): Add a methods to retrieve values for feature flag types other than boolean ([#513](https://github.com/regentmarkets/flutter-deriv-packages/issues/513)). ([dd30f341](https://github.com/regentmarkets/flutter-deriv-packages/commit/dd30f3419b8d0ca887b4cfc58280db4bc4738076)) + + +## 2024-05-28 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_passkeys` - `v0.0.1+3`](#deriv_passkeys---v0013) + - [`deriv_auth` - `v6.6.2`](#deriv_auth---v662) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.6.2` + +--- + +#### `deriv_passkeys` - `v0.0.1+3` + + - **REFACTOR**(deriv_passkeys): Update exception handling in DerivPasskeysPlugin. ([5d218724](https://github.com/regentmarkets/flutter-deriv-packages/commit/5d218724ffd2cf71dc58238fda97e8413abfee8a)) + - **REFACTOR**(deriv_passkeys): Remove unused code. ([8a12ae4a](https://github.com/regentmarkets/flutter-deriv-packages/commit/8a12ae4aebffb48fbfe4b205019a83a4a86fde7e)) + - **REFACTOR**(deriv_passkeys): Update exception handling in DerivPasskeysPlugin. ([5f31ece9](https://github.com/regentmarkets/flutter-deriv-packages/commit/5f31ece9eb5331e1e252c9db0c63423dd5b46d8f)) + - **REFACTOR**(deriv_passkeys): Update exception handling in DerivPasskeysPlugin. ([ca366418](https://github.com/regentmarkets/flutter-deriv-packages/commit/ca36641847dc206354ece977e44c8bf4531a3def)) + - **REFACTOR**(deriv_passkeys): Fix exception handling in DerivPasskeysPlugin. ([eafa7cd1](https://github.com/regentmarkets/flutter-deriv-packages/commit/eafa7cd1f5eacc6d7c58f345936292dc87af624e)) + + +## 2024-05-27 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.6.1`](#deriv_auth---v661) + - [`deriv_live_chat` - `v0.0.1+2`](#deriv_live_chat---v0012) + - [`deriv_localizations` - `v1.4.1`](#deriv_localizations---v141) + - [`deriv_passkeys` - `v0.0.1+2`](#deriv_passkeys---v0012) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_passkeys` - `v0.0.1+2` + +--- + +#### `deriv_auth` - `v6.6.1` + + - **REFACTOR**(deriv_auth): update localization ([#594](https://github.com/regentmarkets/flutter-deriv-packages/issues/594)). ([5204c74f](https://github.com/regentmarkets/flutter-deriv-packages/commit/5204c74f609d946ea797e766e6bb652d82f76930)) + +#### `deriv_live_chat` - `v0.0.1+2` + + - **REFACTOR**(deriv_live_chat): Glitch_while_clicking_on_live_chat_icon_IOS ([#585](https://github.com/regentmarkets/flutter-deriv-packages/issues/585)). ([957faa3f](https://github.com/regentmarkets/flutter-deriv-packages/commit/957faa3fb16be0174e5e529cbcc068de31cc7bb3)) + +#### `deriv_localizations` - `v1.4.1` + + - **REFACTOR**(deriv_auth): update localization ([#594](https://github.com/regentmarkets/flutter-deriv-packages/issues/594)). ([5204c74f](https://github.com/regentmarkets/flutter-deriv-packages/commit/5204c74f609d946ea797e766e6bb652d82f76930)) + + +## 2024-05-20 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.6.0`](#deriv_auth---v660) + +--- + +#### `deriv_auth` - `v6.6.0` + + - **REFACTOR**(deriv_auth): replaced deriv_localizations commit hash ref with tag ref. ([572aa5fd](https://github.com/regentmarkets/flutter-deriv-packages/commit/572aa5fd54241d09b9993b102c0eceb117594821)) + - **REFACTOR**(deriv_passkeys): updated deriv_passkeys and deriv_localizations. ([d6eccdca](https://github.com/regentmarkets/flutter-deriv-packages/commit/d6eccdcaf9fa37784ae3f9fb2bd13a98e874aae0)) + - **REFACTOR**: update deriv_passkeys. ([cd545c74](https://github.com/regentmarkets/flutter-deriv-packages/commit/cd545c74f7076010d7153d74c20288b2b8db016b)) + - **REFACTOR**: update deriv_passkeys. ([41fa9ed8](https://github.com/regentmarkets/flutter-deriv-packages/commit/41fa9ed899462b0eeef34fa3971ffe82e937ce91)) + - **REFACTOR**(deriv_auth): Updated deriv_localizations. ([4d43e258](https://github.com/regentmarkets/flutter-deriv-packages/commit/4d43e258d72e71da6c53f1bf2b241f95d4ba4c67)) + - **REFACTOR**(deriv_auth): updated deriv_passkeys and deriv_localizations versions. ([a3acdf1a](https://github.com/regentmarkets/flutter-deriv-packages/commit/a3acdf1ad34bdbba464d866273e165709d908159)) + - **REFACTOR**(deriv_auth): updated deriv_passkeys commit hash. ([ed0f0057](https://github.com/regentmarkets/flutter-deriv-packages/commit/ed0f005785991a0c166edaf4f93e03922826ee91)) + - **REFACTOR**(deriv_auth): updated deriv_passkeys commit hash. ([82de1610](https://github.com/regentmarkets/flutter-deriv-packages/commit/82de161098d7cd698dcf465a4ad4a2cdac1fdd75)) + - **REFACTOR**(deriv_auth): updated deriv_passkeys commit hash. ([dcda848b](https://github.com/regentmarkets/flutter-deriv-packages/commit/dcda848bb014f72e138e907c84c258be1a7c5c63)) + - **REFACTOR**(deriv_auth): updated deriv_passkeys commit hash. ([87ff83bb](https://github.com/regentmarkets/flutter-deriv-packages/commit/87ff83bbc8050c3893186b86003345ca3ed739bd)) + - **REFACTOR**(deriv_auth): updated deriv_passkeys commit hash. ([608dcf4b](https://github.com/regentmarkets/flutter-deriv-packages/commit/608dcf4b812945efa605ae5c553afa06e6a9f8db)) + - **REFACTOR**(deriv_auth): updated deriv_passkeys commit hash. ([6a2708eb](https://github.com/regentmarkets/flutter-deriv-packages/commit/6a2708eb6cb8a3b343fd8688a27ab038fd3c1acb)) + - **REFACTOR**(deriv_auth): updated deriv_ui. ([294ca423](https://github.com/regentmarkets/flutter-deriv-packages/commit/294ca42318568fd876f531856b61d0930efb05b8)) + - **REFACTOR**(deriv_passkeys): removed context parameter from onLoggedIn callback. ([02f390b2](https://github.com/regentmarkets/flutter-deriv-packages/commit/02f390b2cb4736993f876fe60f6513490682837a)) + - **REFACTOR**(deriv_auth): updated deriv_passkeys and deriv_localizations versions. ([14b85c22](https://github.com/regentmarkets/flutter-deriv-packages/commit/14b85c22c7edbd8046eb9f2feef68aa49aff8df2)) + - **REFACTOR**(deriv_auth): new deriv_passkeys commit hash ref. ([7bb5f71b](https://github.com/regentmarkets/flutter-deriv-packages/commit/7bb5f71b4a3c364b3ee4455fe1c97b8ac5e83dea)) + - **REFACTOR**(deriv_auth): updated passkeys dependencies. ([d966d9f2](https://github.com/regentmarkets/flutter-deriv-packages/commit/d966d9f28a743d652dd14174827da5e20a697664)) + - **REFACTOR**(deriv_auth): integrating deriv_passkeys. ([45cbbb6c](https://github.com/regentmarkets/flutter-deriv-packages/commit/45cbbb6c20274e84e4f932057d0816a3b4dc581c)) + - **REFACTOR**(deriv_auth): fixing deriv_passkeys dependencies. ([ac5dd589](https://github.com/regentmarkets/flutter-deriv-packages/commit/ac5dd5895caf6f2f2e60b2906a38f6ac27bfc11a)) + - **REFACTOR**(deriv_auth): linked onLoggedIn function in login_page. ([1e5ba631](https://github.com/regentmarkets/flutter-deriv-packages/commit/1e5ba63165ff214ec44e589daddb9566ec47b94a)) + - **REFACTOR**(deriv_auth): added context to onLoggedIn. ([c93046ab](https://github.com/regentmarkets/flutter-deriv-packages/commit/c93046abadefeb4af75cb0a2996f69a37415b33e)) + - **REFACTOR**(deriv_auth): updated flutter_deriv_api. ([c3187ba8](https://github.com/regentmarkets/flutter-deriv-packages/commit/c3187ba85494198bb3b11e568ad50ff714b69cfa)) + - **REFACTOR**(deriv_auth): updated `deriv_http_client`. ([c12a9ee4](https://github.com/regentmarkets/flutter-deriv-packages/commit/c12a9ee43fcc321a9f2ca404215a4f15069e4a71)) + - **FIX**(deriv_auth): fixed onPressed error. ([a3cce1fb](https://github.com/regentmarkets/flutter-deriv-packages/commit/a3cce1fb5884592d0329e67de4a5a6ca9b55f694)) + - **FIX**(deriv_auth): calling onLoggedIn correctly. ([5c19f3ae](https://github.com/regentmarkets/flutter-deriv-packages/commit/5c19f3aee610bd3ff9e9c25edae4a558ae8a97b8)) + - **FIX**(deriv_auth): fixed deriv_passkeys dependency. ([607eca12](https://github.com/regentmarkets/flutter-deriv-packages/commit/607eca127e5a394dbb58abe82a5d5d4b4f811156)) + - **FEAT**(deriv_auth): single entry ([#420](https://github.com/regentmarkets/flutter-deriv-packages/issues/420)). ([e594c05b](https://github.com/regentmarkets/flutter-deriv-packages/commit/e594c05b87c05d189913a7de8b9e1e4e6d03b76a)) + - **FEAT**(deriv_passkeys): connected passkeys functionality with deriv auth. ([7333af28](https://github.com/regentmarkets/flutter-deriv-packages/commit/7333af283e3e5e00970a2dd49930f243d7f1f558)) + - **FEAT**(deriv_auth): added deriv_passkeys to deriv_auth. ([e96c8127](https://github.com/regentmarkets/flutter-deriv-packages/commit/e96c81278999fa2dfae57e6adf5785bb82a51fe3)) + - **FEAT**(deriv_auth): single entry reset password and merge conflicts. ([f7930d66](https://github.com/regentmarkets/flutter-deriv-packages/commit/f7930d66880d2d091959646dd928e79189ce5704)) + - **FEAT**(deriv_auith): single entry setting page. ([dc29784f](https://github.com/regentmarkets/flutter-deriv-packages/commit/dc29784f2f74c34a7a5bf9910b4041adca591e74)) + - **FEAT**(deriv_auth): single entry signup page. ([f38cc253](https://github.com/regentmarkets/flutter-deriv-packages/commit/f38cc253209afdf3573e3a85fe8ec0cd7082c5d5)) + - **FEAT**(deriv_auth): single entry login and reset pass added. ([573a4a78](https://github.com/regentmarkets/flutter-deriv-packages/commit/573a4a787a6aa518537f2678c19a748cebcf0fb4)) + - **FEAT**(deriv_auth): adding login page to single entry. ([5ce30c3b](https://github.com/regentmarkets/flutter-deriv-packages/commit/5ce30c3b0fbd84ad3a4cc191d0c7949ec9691ef5)) + - **FEAT**(deriv_auth): single entry. ([c1e0067b](https://github.com/regentmarkets/flutter-deriv-packages/commit/c1e0067b7bbaf94cfd13f342cd05aaa8f65ba497)) + + +## 2024-05-20 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.4.0`](#deriv_localizations---v140) + - [`deriv_passkeys` - `v0.0.1+1`](#deriv_passkeys---v0011) + - [`deriv_auth` - `v6.5.2`](#deriv_auth---v652) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.5.2` + +--- + +#### `deriv_localizations` - `v1.4.0` + + - **FEAT**(deriv_passkeys): [UPM-547] Deriv passkeys package ([#425](https://github.com/regentmarkets/flutter-deriv-packages/issues/425)). ([c5509175](https://github.com/regentmarkets/flutter-deriv-packages/commit/c5509175edb6a94122cce6fe6f63a43d44904dc9)) + +#### `deriv_passkeys` - `v0.0.1+1` + + - **REFACTOR**(deriv_passkeys): updated deriv_localizations ([#590](https://github.com/regentmarkets/flutter-deriv-packages/issues/590)). ([3f299fae](https://github.com/regentmarkets/flutter-deriv-packages/commit/3f299faea33731a814d18f18ba99180eb6483ca6)) + + +## 2024-05-17 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.3.2`](#deriv_localizations---v132) + - [`deriv_auth` - `v6.5.1`](#deriv_auth---v651) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.5.1` + +--- + +#### `deriv_localizations` - `v1.3.2` + + - **REFACTOR**(deriv_localizations): Updating deriv localizations for passkeys package ([#581](https://github.com/regentmarkets/flutter-deriv-packages/issues/581)). ([0bf743c3](https://github.com/regentmarkets/flutter-deriv-packages/commit/0bf743c3b7a65f70935b32b68b7062ed07a1ae72)) + + +## 2024-05-15 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.5.0`](#deriv_auth---v650) + +--- + +#### `deriv_auth` - `v6.5.0` + + - **FEAT**(deriv_auth): Add language selector in auth package ([#576](https://github.com/regentmarkets/flutter-deriv-packages/issues/576)). ([cd3768ef](https://github.com/regentmarkets/flutter-deriv-packages/commit/cd3768ef2b6bd7420e7957277461e95e78eee545)) + + +## 2024-05-07 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.3.1`](#deriv_localizations---v131) + - [`deriv_ui` - `v0.0.7+4`](#deriv_ui---v0074) + - [`deriv_auth` - `v6.4.3`](#deriv_auth---v643) + - [`deriv_widgetbook` - `v0.0.2+4`](#deriv_widgetbook---v0024) + - [`deriv_language_selector` - `v0.0.2+2`](#deriv_language_selector---v0022) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.4.3` + - `deriv_widgetbook` - `v0.0.2+4` + - `deriv_language_selector` - `v0.0.2+2` + +--- + +#### `deriv_localizations` - `v1.3.1` + + - **REFACTOR**(deriv_localizations): [UPM-925] Updating passkeys strings in deriv localizations ([#568](https://github.com/regentmarkets/flutter-deriv-packages/issues/568)). ([3c8cabd1](https://github.com/regentmarkets/flutter-deriv-packages/commit/3c8cabd11fdfd302f129ed8a53e73c0c7e3fd7b5)) + +#### `deriv_ui` - `v0.0.7+4` + + - **FIX**(deriv_ui): add specific formatter for crypto and fiat currency ([#569](https://github.com/regentmarkets/flutter-deriv-packages/issues/569)). ([711abee1](https://github.com/regentmarkets/flutter-deriv-packages/commit/711abee199278b7f0fd7463de6f480d561e99d67)) + + +## 2024-04-26 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.3.0`](#deriv_localizations---v130) + - [`deriv_auth` - `v6.4.2`](#deriv_auth---v642) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.4.2` + +--- + +#### `deriv_localizations` - `v1.3.0` + + - **FEAT**(deriv_localization): Update localization string for deriv_auth ([#571](https://github.com/regentmarkets/flutter-deriv-packages/issues/571)). ([d2d76902](https://github.com/regentmarkets/flutter-deriv-packages/commit/d2d769023b651419270842d450a56a4bbd264327)) + + +## 2024-04-24 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.4.1`](#deriv_auth---v641) + +--- + +#### `deriv_auth` - `v6.4.1` + + - **FIX**(deriv_auth): Update settings page navigation in auth flow with callback ([#567](https://github.com/regentmarkets/flutter-deriv-packages/issues/567)). ([53857baf](https://github.com/regentmarkets/flutter-deriv-packages/commit/53857baf66ecc870e8a1452663c564b8ee57837a)) + + +## 2024-04-22 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.4.0`](#deriv_auth---v640) + +--- + +#### `deriv_auth` - `v6.4.0` + + - **FEAT**(deriv_auth): [P2PS-2679] add widget keys to auth components ([#565](https://github.com/regentmarkets/flutter-deriv-packages/issues/565)). ([e7bc54b4](https://github.com/regentmarkets/flutter-deriv-packages/commit/e7bc54b4be3d80236c83f1fecbe6f012f8759690)) + + +## 2024-04-22 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_web_view` - `v0.2.2+1`](#deriv_web_view---v0221) + - [`deriv_auth` - `v6.3.8`](#deriv_auth---v638) + - [`deriv_ui` - `v0.0.7+3`](#deriv_ui---v0073) + - [`deriv_widgetbook` - `v0.0.2+3`](#deriv_widgetbook---v0023) + - [`deriv_language_selector` - `v0.0.2+1`](#deriv_language_selector---v0021) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.3.8` + - `deriv_ui` - `v0.0.7+3` + - `deriv_widgetbook` - `v0.0.2+3` + - `deriv_language_selector` - `v0.0.2+1` + +--- + +#### `deriv_web_view` - `v0.2.2+1` + + - **FIX**(deriv_web_view): update PTA model ([#557](https://github.com/regentmarkets/flutter-deriv-packages/issues/557)). ([49583f82](https://github.com/regentmarkets/flutter-deriv-packages/commit/49583f823fdac9c3ec3a85529fad9ab9f6784c67)) + + +## 2024-04-03 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_language_selector` - `v0.0.2`](#deriv_language_selector---v002) + +--- + +#### `deriv_language_selector` - `v0.0.2` + + - **FEAT**(deriv_language_selector): aliakbar/1586/set_device_lang_as_default_if_app_supports ([#540](https://github.com/regentmarkets/flutter-deriv-packages/issues/540)). ([88ba3104](https://github.com/regentmarkets/flutter-deriv-packages/commit/88ba31049eb3a718db3e1ee5e8e85f56689d94ba)) + + +## 2024-04-02 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.2.0`](#deriv_localizations---v120) + - [`deriv_auth` - `v6.3.7`](#deriv_auth---v637) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.3.7` + +--- + +#### `deriv_localizations` - `v1.2.0` + + - **FEAT**(deriv_localizations): [UPM-856] Adding deriv passkeys localizations. ([#544](https://github.com/regentmarkets/flutter-deriv-packages/issues/544)). ([9a3b42e2](https://github.com/regentmarkets/flutter-deriv-packages/commit/9a3b42e2468c9586eae4eb03ba8a7b2712f44de2)) + + +## 2024-03-29 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.3.6`](#deriv_auth---v636) + +--- + +#### `deriv_auth` - `v6.3.6` + + - **FIX**(auth_single_entry): add auth cubits provider. ([8f71d1d5](https://github.com/regentmarkets/flutter-deriv-packages/commit/8f71d1d570508666843119d7c1317210484fcc20)) + + +## 2024-03-29 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_theme` - `v2.4.0`](#deriv_theme---v240) + - [`deriv_auth` - `v6.3.5`](#deriv_auth---v635) + - [`deriv_ui` - `v0.0.7+2`](#deriv_ui---v0072) + - [`deriv_numpad` - `v1.1.3`](#deriv_numpad---v113) + - [`deriv_widgetbook` - `v0.0.2+2`](#deriv_widgetbook---v0022) + - [`deriv_expandable_bottom_sheet` - `v0.0.1+4`](#deriv_expandable_bottom_sheet---v0014) + - [`deriv_date_range_picker` - `v0.0.1+4`](#deriv_date_range_picker---v0014) + - [`deriv_language_selector` - `v0.0.1+2`](#deriv_language_selector---v0012) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.3.5` + - `deriv_ui` - `v0.0.7+2` + - `deriv_numpad` - `v1.1.3` + - `deriv_widgetbook` - `v0.0.2+2` + - `deriv_expandable_bottom_sheet` - `v0.0.1+4` + - `deriv_date_range_picker` - `v0.0.1+4` + - `deriv_language_selector` - `v0.0.1+2` + +--- + +#### `deriv_theme` - `v2.4.0` + + - **FEAT**(deriv_theme): [UPM-856] Add passkeys colors to deriv theme. ([#545](https://github.com/regentmarkets/flutter-deriv-packages/issues/545)). ([09c54bb6](https://github.com/regentmarkets/flutter-deriv-packages/commit/09c54bb6572d5c8ec0454cf41c8ef282b89f2689)) + + +## 2024-03-25 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.1.3`](#deriv_localizations---v113) + - [`deriv_auth` - `v6.3.4`](#deriv_auth---v634) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.3.4` + +--- + +#### `deriv_localizations` - `v1.1.3` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#541](https://github.com/regentmarkets/flutter-deriv-packages/issues/541)). ([2129cb76](https://github.com/regentmarkets/flutter-deriv-packages/commit/2129cb76fce1ce120af0f1b357f24ff343dbd803)) + + +## 2024-03-14 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.1.2`](#deriv_localizations---v112) + - [`deriv_ui` - `v0.0.7+1`](#deriv_ui---v0071) + - [`deriv_auth` - `v6.3.3`](#deriv_auth---v632) + - [`deriv_widgetbook` - `v0.0.2+1`](#deriv_widgetbook---v0021) + - [`deriv_language_selector` - `v0.0.1+1`](#deriv_language_selector---v0011) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.3.2` + - `deriv_widgetbook` - `v0.0.2+1` + - `deriv_language_selector` - `v0.0.1+1` + +--- + +#### `deriv_localizations` - `v1.1.2` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#534](https://github.com/regentmarkets/flutter-deriv-packages/issues/534)). ([bfe24625](https://github.com/regentmarkets/flutter-deriv-packages/commit/bfe24625b6c9076514f559d403f9ea7d339dc6be)) + +#### `deriv_ui` - `v0.0.7+1` + + - **REFACTOR**(deriv_ui): update theme deps. ([db4c4bc2](https://github.com/regentmarkets/flutter-deriv-packages/commit/db4c4bc2a6d4f042ab624f781ea0dc5e837ed860)) + + +## 2024-03-12 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.3.1`](#deriv_auth---v631) + - [`deriv_theme` - `v2.3.0`](#deriv_theme---v230) + - [`deriv_ui` - `v0.0.7`](#deriv_ui---v007) + - [`deriv_widgetbook` - `v0.0.2`](#deriv_widgetbook---v002) + - [`deriv_numpad` - `v1.1.2`](#deriv_numpad---v112) + - [`deriv_date_range_picker` - `v0.0.1+3`](#deriv_date_range_picker---v0013) + - [`deriv_expandable_bottom_sheet` - `v0.0.1+3`](#deriv_expandable_bottom_sheet---v0013) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_numpad` - `v1.1.2` + - `deriv_date_range_picker` - `v0.0.1+3` + - `deriv_expandable_bottom_sheet` - `v0.0.1+3` + +--- + +#### `deriv_auth` - `v6.3.1` + + - **FIX**: deriv_toke_service_test ([#519](https://github.com/regentmarkets/flutter-deriv-packages/issues/519)). ([25b9dc45](https://github.com/regentmarkets/flutter-deriv-packages/commit/25b9dc454c222bb7299d859a21cb1b3cb874c1bf)) + +#### `deriv_theme` - `v2.3.0` + + - **FEAT**(deriv_ui): add language selector component ([#509](https://github.com/regentmarkets/flutter-deriv-packages/issues/509)). ([bff66151](https://github.com/regentmarkets/flutter-deriv-packages/commit/bff661513f330d014154657fbf170b75512dc180)) + +#### `deriv_ui` - `v0.0.7` + + - **FIX**(deriv_ui): update theme dependency ([#522](https://github.com/regentmarkets/flutter-deriv-packages/issues/522)). ([d00e3edf](https://github.com/regentmarkets/flutter-deriv-packages/commit/d00e3edfe08e861401c501b2ac1944a9d7af11f2)) + - **FEAT**(deriv_ui): add language selector component ([#509](https://github.com/regentmarkets/flutter-deriv-packages/issues/509)). ([bff66151](https://github.com/regentmarkets/flutter-deriv-packages/commit/bff661513f330d014154657fbf170b75512dc180)) + +#### `deriv_widgetbook` - `v0.0.2` + + - **FEAT**(deriv_ui): add language selector component ([#509](https://github.com/regentmarkets/flutter-deriv-packages/issues/509)). ([bff66151](https://github.com/regentmarkets/flutter-deriv-packages/commit/bff661513f330d014154657fbf170b75512dc180)) + + +## 2024-03-08 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.3.0`](#deriv_auth---v630) + - [`deriv_web_view` - `v0.2.2`](#deriv_web_view---v022) + - [`deriv_ui` - `v0.0.6+7`](#deriv_ui---v0067) + - [`deriv_widgetbook` - `v0.0.1+2`](#deriv_widgetbook---v0012) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_ui` - `v0.0.6+7` + - `deriv_widgetbook` - `v0.0.1+2` + +--- + +#### `deriv_auth` - `v6.3.0` + + - **FEAT**(deriv_http_client): update to the latest deriv_http_client ([#501](https://github.com/regentmarkets/flutter-deriv-packages/issues/501)). ([d582995a](https://github.com/regentmarkets/flutter-deriv-packages/commit/d582995a226906c34c69c7716b3e5573c88c0c4e)) + +#### `deriv_web_view` - `v0.2.2` + + - **FEAT**(deriv_http_client): update to the latest deriv_http_client ([#501](https://github.com/regentmarkets/flutter-deriv-packages/issues/501)). ([d582995a](https://github.com/regentmarkets/flutter-deriv-packages/commit/d582995a226906c34c69c7716b3e5573c88c0c4e)) + + +## 2024-03-07 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_ui` - `v0.0.6+6`](#deriv_ui---v0066) + - [`deriv_widgetbook` - `v0.0.1+1`](#deriv_widgetbook---v0011) + - [`deriv_auth` - `v6.2.1`](#deriv_auth---v621) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_widgetbook` - `v0.0.1+1` + - `deriv_auth` - `v6.2.1` + +--- + +#### `deriv_ui` - `v0.0.6+6` + + - **FIX**(deriv_ui): validating secondary currency instead of main currency in numpad. ([37154fb4](https://github.com/regentmarkets/flutter-deriv-packages/commit/37154fb4c7b9171ae6a1caf0e9d7dad58b63f9be)) + + +## 2024-03-01 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.2.0`](#deriv_auth---v620) + - [`deriv_ui` - `v0.0.6+5`](#deriv_ui---v0065) + +--- + +#### `deriv_auth` - `v6.2.0` + + - **REFACTOR**(deriv_ui): remove example and android folder ([#503](https://github.com/regentmarkets/flutter-deriv-packages/issues/503)). ([8c90e199](https://github.com/regentmarkets/flutter-deriv-packages/commit/8c90e1995a3a04c945923cb0f8f0e7480cde03b7)) + - **FIX**(deriv_auth): endpoint change not reflecting in social login ([#498](https://github.com/regentmarkets/flutter-deriv-packages/issues/498)). ([32e33a46](https://github.com/regentmarkets/flutter-deriv-packages/commit/32e33a464092dcd570f5ef8d7524e0dc9b369566)) + - **FIX**(deriv_auth): endpoint change not reflecting in social login ([#498](https://github.com/regentmarkets/flutter-deriv-packages/issues/498)). ([5714a5dd](https://github.com/regentmarkets/flutter-deriv-packages/commit/5714a5dd403c5fd0fc97d3ee634f9c76241b22da)) + - **FEAT**(deriv_auth): single entry ([#420](https://github.com/regentmarkets/flutter-deriv-packages/issues/420)). ([19130046](https://github.com/regentmarkets/flutter-deriv-packages/commit/19130046f21d24a28a5e135914308a411ee762e3)) + - **FEAT**(deriv_auth): single entry ([#420](https://github.com/regentmarkets/flutter-deriv-packages/issues/420)). ([e594c05b](https://github.com/regentmarkets/flutter-deriv-packages/commit/e594c05b87c05d189913a7de8b9e1e4e6d03b76a)) + +#### `deriv_ui` - `v0.0.6+5` + + - **REFACTOR**(deriv_ui): remove example and android folder ([#503](https://github.com/regentmarkets/flutter-deriv-packages/issues/503)). ([8c90e199](https://github.com/regentmarkets/flutter-deriv-packages/commit/8c90e1995a3a04c945923cb0f8f0e7480cde03b7)) + - **REFACTOR**(deriv_ui): update deriv_ui dependencies ([#499](https://github.com/regentmarkets/flutter-deriv-packages/issues/499)). ([51192870](https://github.com/regentmarkets/flutter-deriv-packages/commit/511928702f997536812529e081c81e4ae8f6122b)) + - **REFACTOR**(deriv_ui): update deriv_ui dependencies ([#499](https://github.com/regentmarkets/flutter-deriv-packages/issues/499)). ([2ade47f5](https://github.com/regentmarkets/flutter-deriv-packages/commit/2ade47f5f42a37325e4e4906c8c095fc26d777b6)) + + +## 2024-03-01 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.1.1`](#deriv_auth---v611) + +--- + +#### `deriv_auth` - `v6.1.1` + + - **FIX**(deriv_auth): endpoint change not reflecting in social login ([#498](https://github.com/regentmarkets/flutter-deriv-packages/issues/498)). ([5714a5dd](https://github.com/regentmarkets/flutter-deriv-packages/commit/5714a5dd403c5fd0fc97d3ee634f9c76241b22da)) + + +## 2024-02-29 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.1.0`](#deriv_auth---v610) + +--- + +#### `deriv_auth` - `v6.1.0` + + - **FEAT**(deriv_auth): single entry ([#420](https://github.com/regentmarkets/flutter-deriv-packages/issues/420)). ([e594c05b](https://github.com/regentmarkets/flutter-deriv-packages/commit/e594c05b87c05d189913a7de8b9e1e4e6d03b76a)) + + +## 2024-02-29 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_ui` - `v0.0.6+4`](#deriv_ui---v0064) + - [`deriv_auth` - `v6.0.2`](#deriv_auth---v602) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.0.2` + +--- + +#### `deriv_ui` - `v0.0.6+4` + + - **REFACTOR**(deriv_ui): update deriv_ui dependencies ([#499](https://github.com/regentmarkets/flutter-deriv-packages/issues/499)). ([2ade47f5](https://github.com/regentmarkets/flutter-deriv-packages/commit/2ade47f5f42a37325e4e4906c8c095fc26d777b6)) + + +## 2024-02-29 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v6.1.0`](#deriv_auth---v610) + +--- + +#### `deriv_auth` - `v6.1.0` + + - **FEAT**(deriv_auth): single entry ([#420](https://github.com/regentmarkets/flutter-deriv-packages/issues/420)). ([e594c05b](https://github.com/regentmarkets/flutter-deriv-packages/commit/e594c05b87c05d189913a7de8b9e1e4e6d03b76a)) + + +## 2024-02-29 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_ui` - `v0.0.6+4`](#deriv_ui---v0064) + - [`deriv_auth` - `v6.0.2`](#deriv_auth---v602) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.0.2` + +--- + +#### `deriv_ui` - `v0.0.6+4` + + - **REFACTOR**(deriv_ui): update deriv_ui dependencies ([#499](https://github.com/regentmarkets/flutter-deriv-packages/issues/499)). ([2ade47f5](https://github.com/regentmarkets/flutter-deriv-packages/commit/2ade47f5f42a37325e4e4906c8c095fc26d777b6)) + + +## 2024-02-29 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_ui` - `v0.0.6+4`](#deriv_ui---v0064) + - [`deriv_auth` - `v6.0.2`](#deriv_auth---v602) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v6.0.2` + +--- + +#### `deriv_ui` - `v0.0.6+4` + + - **REFACTOR**(deriv_ui): update deriv_ui dependencies ([#499](https://github.com/regentmarkets/flutter-deriv-packages/issues/499)). ([2ade47f5](https://github.com/regentmarkets/flutter-deriv-packages/commit/2ade47f5f42a37325e4e4906c8c095fc26d777b6)) + + +## 2024-02-29 + +### Changes + +--- + +Packages with breaking changes: + + - [`deriv_auth` - `v6.0.0`](#deriv_auth---v600) + - [`deriv_http_client` - `v2.0.0`](#deriv_http_client---v200) + - [`deriv_web_view` - `v0.2.0`](#deriv_web_view---v020) + +Packages with other changes: + + - [`deriv_ui` - `v0.0.6+3`](#deriv_ui---v0063) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_ui` - `v0.0.6+3` + +--- + +#### `deriv_auth` - `v6.0.0` + + - **BREAKING** **REFACTOR**(deriv_auth): ramin_make_auth_and_web_view_proxy_aware ([#483](https://github.com/regentmarkets/flutter-deriv-packages/issues/483)). ([e99afc92](https://github.com/regentmarkets/flutter-deriv-packages/commit/e99afc926531c0c36c567d0ac8c66e906fa27ea5)) + +#### `deriv_http_client` - `v2.0.0` + + - **BREAKING** **REFACTOR**(deriv_auth): ramin_make_auth_and_web_view_proxy_aware ([#483](https://github.com/regentmarkets/flutter-deriv-packages/issues/483)). ([e99afc92](https://github.com/regentmarkets/flutter-deriv-packages/commit/e99afc926531c0c36c567d0ac8c66e906fa27ea5)) + +#### `deriv_web_view` - `v0.2.0` + + - **BREAKING** **REFACTOR**(deriv_auth): ramin_make_auth_and_web_view_proxy_aware ([#483](https://github.com/regentmarkets/flutter-deriv-packages/issues/483)). ([e99afc92](https://github.com/regentmarkets/flutter-deriv-packages/commit/e99afc926531c0c36c567d0ac8c66e906fa27ea5)) + + +## 2024-02-27 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v5.0.3`](#deriv_auth---v503) + +--- + +#### `deriv_auth` - `v5.0.3` + + - **REFACTOR**(deriv_auth): rename inavalid to invalid in auth state handler ([#490](https://github.com/regentmarkets/flutter-deriv-packages/issues/490)). ([2e9cfa75](https://github.com/regentmarkets/flutter-deriv-packages/commit/2e9cfa75007fce25b90394bd92905e9c3ca876cc)) + + +## 2024-02-27 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_web_view` - `v0.1.0+1`](#deriv_web_view---v0101) + - [`deriv_auth` - `v5.0.2`](#deriv_auth---v502) + - [`deriv_ui` - `v0.0.6+2`](#deriv_ui---v0062) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v5.0.2` + - `deriv_ui` - `v0.0.6+2` + +--- + +#### `deriv_web_view` - `v0.1.0+1` + + - **FIX**(deriv_web_view): fix platform exception not being caught ([#489](https://github.com/regentmarkets/flutter-deriv-packages/issues/489)). ([89f038a0](https://github.com/regentmarkets/flutter-deriv-packages/commit/89f038a0717c1902c6fbc22668705ad2636df9d5)) + + +## 2024-02-26 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_ui` - `v0.0.6+1`](#deriv_ui---v0061) + - [`deriv_auth` - `v5.0.1`](#deriv_auth---v501) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v5.0.1` + +--- + +#### `deriv_ui` - `v0.0.6+1` + + - **REVERT**(deriv_ui): java file change. ([a3e95943](https://github.com/regentmarkets/flutter-deriv-packages/commit/a3e959434a5b2e6c6fa851de431ebce9a55b4c7d)) + - **REFACTOR**(deriv_ui): numpad/ move listener to initState. ([66f2be15](https://github.com/regentmarkets/flutter-deriv-packages/commit/66f2be15f3394645cf347b14bfdd9bee3a9215a7)) + - **REFACTOR**(deriv_ui): add default focus node and auto focus option in text field, custom checkbox theme changed. ([7f3e68c2](https://github.com/regentmarkets/flutter-deriv-packages/commit/7f3e68c2c31859379637eda3d5d1b580eb011019)) + - **REFACTOR**(deriv_ui): date_picker/replace pop with maybePop for widgetbook. ([d4e18f88](https://github.com/regentmarkets/flutter-deriv-packages/commit/d4e18f88198b23b49c2a787bc8142e6271ff9198)) + - **REFACTOR**(deriv_ui): numpad/move access of inherited widget to didChangeDependencies. ([9a0541aa](https://github.com/regentmarkets/flutter-deriv-packages/commit/9a0541aad0588b1c185dc12e67d177cb6da7089b)) + - **FIX**(deriv_ui): date_picker/match background color with design. ([0eb7740e](https://github.com/regentmarkets/flutter-deriv-packages/commit/0eb7740e0997c0286153748229a7bb0d3ab9415e)) + + +## 2024-02-26 + +### Changes + +--- + +Packages with breaking changes: + + - [`deriv_auth` - `v5.0.0`](#deriv_auth---v500) + +Packages with other changes: + + - There are no other changes in this release. + +--- + +#### `deriv_auth` - `v5.0.0` + + - **BREAKING** **REFACTOR**(deriv_auth): [MOBC-802] migrate to deriv_localizations package [#486](https://github.com/regentmarkets/flutter-deriv-packages/issues/486). ([e9297272](https://github.com/regentmarkets/flutter-deriv-packages/commit/e9297272c91235263cf2335b8ba69a3a1d9c1583)) + + +## 2024-02-15 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_feature_flag` - `v0.1.0+1`](#deriv_feature_flag---v0101) + +--- + +#### `deriv_feature_flag` - `v0.1.0+1` + + - **FIX**(deriv_feature_flag): remove env dependancy ([#477](https://github.com/regentmarkets/flutter-deriv-packages/issues/477)). ([c62b20eb](https://github.com/regentmarkets/flutter-deriv-packages/commit/c62b20eb88cf1397ecf4437a7854ff19187d7662)) + + +## 2024-02-15 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`update_checker` - `v1.2.2`](#update_checker---v122) + +--- + +#### `update_checker` - `v1.2.2` + + - **FIX**(update_checker): fix optional update issue in remote config [#475](https://github.com/regentmarkets/flutter-deriv-packages/issues/475). ([ac0dc26b](https://github.com/regentmarkets/flutter-deriv-packages/commit/ac0dc26b46b478248ea81d1dba6b7c6844b88995)) + + +## 2024-02-14 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_ui` - `v0.0.6`](#deriv_ui---v006) + - [`deriv_auth` - `v4.1.1`](#deriv_auth---v411) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v4.1.1` + +--- + +#### `deriv_ui` - `v0.0.6` + + - **FEAT**(deriv_ui): [MOBC-750] add deriv_numpad to deriv_ui ([#434](https://github.com/regentmarkets/flutter-deriv-packages/issues/434)). ([3802e2e6](https://github.com/regentmarkets/flutter-deriv-packages/commit/3802e2e6d874cbd3ed21b3ca0f6983000840938a)) + + +## 2024-02-14 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v4.1.0`](#deriv_auth---v410) + +--- + +#### `deriv_auth` - `v4.1.0` + + - **FIX**(deriv_auth): localCurrency issue. ([efbeb86d](https://github.com/regentmarkets/flutter-deriv-packages/commit/efbeb86d5ed69edf27f31625818092a921119ed6)) + - **FEAT**(deriv-auth): add ctrader to platformEnumMapper. ([38bc6f86](https://github.com/regentmarkets/flutter-deriv-packages/commit/38bc6f861e5c98b38e558aa2f7d54253f0a12807)) + + +## 2024-02-14 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.1.1`](#deriv_localizations---v111) + +--- + +#### `deriv_localizations` - `v1.1.1` + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated [#466](https://github.com/regentmarkets/flutter-deriv-packages/issues/466). ([df2f5cad](https://github.com/regentmarkets/flutter-deriv-packages/commit/df2f5cad5f4f2e32b34258188718e9f1c5406caa)) + + +## 2024-02-13 + +### Changes + +--- + +Packages with breaking changes: + + - [`deriv_auth` - `v4.0.0`](#deriv_auth---v400) + +Packages with other changes: + + - There are no other changes in this release. + +--- + +#### `deriv_auth` - `v4.0.0` + + - **BREAKING** **FEAT**(deriv_auth): handle webview logic for social login ([#446](https://github.com/regentmarkets/flutter-deriv-packages/issues/446)). ([94eeec1e](https://github.com/regentmarkets/flutter-deriv-packages/commit/94eeec1eeda0ffe7809d49e541cb76363a9b8326)) + + +## 2024-02-13 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v3.0.1`](#deriv_auth---v301) + - [`deriv_ui` - `v0.0.5`](#deriv_ui---v005) + +--- + +#### `deriv_auth` - `v3.0.1` + + - **FIX**(deriv_auth): reset password design mismatch ([#456](https://github.com/regentmarkets/flutter-deriv-packages/issues/456)). ([efe30f1a](https://github.com/regentmarkets/flutter-deriv-packages/commit/efe30f1a8884ced0397d546dbe6144cea88d124c)) + +#### `deriv_ui` - `v0.0.5` + + - **FEAT**(deriv_ui): [MOBC-751] add form_builder to deriv_ui ([#443](https://github.com/regentmarkets/flutter-deriv-packages/issues/443)). ([c2be88ef](https://github.com/regentmarkets/flutter-deriv-packages/commit/c2be88ef478b40ff99c053b68cf625d80e4f7fc7)) + + +## 2024-02-13 + +### Changes + +--- + +Packages with breaking changes: + - [`deriv_auth` - `v3.0.0`](#deriv_auth---v300) + +Packages with other changes: + + - [`deriv_ui` - `v0.0.4`](#deriv_ui---v004) + +#### `deriv_auth` - `v3.0.0` + + - **BREAKING** **REFACTOR**(deriv_auth): add `isLinkExpired` boolean to reset password error state ([#428](https://github.com/regentmarkets/flutter-deriv-packages/issues/428)). ([29c93ac6](https://github.com/regentmarkets/flutter-deriv-packages/commit/29c93ac6fbb82ee48aee225050c2f1d3ddd79b39)) + +#### `deriv_ui` - `v0.0.4` + + - **FEAT**(deriv_ui): [MOBC-751] add form_builder to deriv_ui ([#443](https://github.com/regentmarkets/flutter-deriv-packages/issues/443)). ([c2be88ef](https://github.com/regentmarkets/flutter-deriv-packages/commit/c2be88ef478b40ff99c053b68cf625d80e4f7fc7)) + - **FEAT**: add grouped_list_view ([#445](https://github.com/regentmarkets/flutter-deriv-packages/issues/445)). ([b0eaafe6](https://github.com/regentmarkets/flutter-deriv-packages/commit/b0eaafe6294ee2629af12c6234758b92676e4c7c)) + + +## 2024-02-08 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_numpad` - `v1.1.1`](#deriv_numpad---v111) + +--- + +#### `deriv_numpad` - `v1.1.1` + + - **REFACTOR**(deriv_numpad): pass validation result to numpad. ([1e44cd04](https://github.com/regentmarkets/flutter-deriv-packages/commit/1e44cd04437eee9db677ca12b0d3cfbc094ca613)) + - **FIX**(deriv_numpad): fix asset not found issue. ([1af28c77](https://github.com/regentmarkets/flutter-deriv-packages/commit/1af28c775adf1d516161c9f5d80c2958b53a0ef5)) + + +## 2024-02-06 + +### Changes + +--- + +Packages with breaking changes: + + - [`deriv_feature_flag` - `v0.1.0`](#deriv_feature_flag---v010) + +Packages with other changes: + + - There are no other changes in this release. + +--- + +#### `deriv_feature_flag` - `v0.1.0` + + - **DOCS**(deriv_feature_flag): update readme. ([9c86a182](https://github.com/regentmarkets/flutter-deriv-packages/commit/9c86a18271df410161099e0d7ffa3002b17fd3ca)) + - **BREAKING** **REFACTOR**(deriv_feature_flag): remove static from initialize method. ([6c9c50e5](https://github.com/regentmarkets/flutter-deriv-packages/commit/6c9c50e550d1cf32723d1beb8238c74a37444c2d)) + + +## 2024-02-05 + +### Changes + +--- + +Packages with breaking changes: + + - [`deriv_web_view` - `v0.1.0`](#deriv_web_view---v010) + +Packages with other changes: + + - [`deriv_ui` - `v0.0.3+1`](#deriv_ui---v0031) + - [`deriv_auth` - `v2.1.1`](#deriv_auth---v211) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_ui` - `v0.0.3+1` + - `deriv_auth` - `v2.1.1` + +--- + +#### `deriv_web_view` - `v0.1.0` + + - **FEAT**(deriv_web_view): add open in app web view with redirect url handling helper. ([4b910581](https://github.com/regentmarkets/flutter-deriv-packages/commit/4b9105816e489dfc2997e1c406b4b96af8d9e190)) + - **BREAKING** **REFACTOR**(deriv_web_view): remove hardcoded redirect urls. ([68d2ef60](https://github.com/regentmarkets/flutter-deriv-packages/commit/68d2ef600109cb1d55533ede41298d01b18f26c7)) + + +## 2024-01-30 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v2.1.0`](#deriv_auth---v210) + - [`deriv_ui` - `v0.0.3`](#deriv_ui---v003) + +--- + +#### `deriv_auth` - `v2.1.0` + + - **FEAT**(deriv_ui): [MOBC-740] move UI related packages to deriv_ui ([#422](https://github.com/regentmarkets/flutter-deriv-packages/issues/422)). ([a2fd5b97](https://github.com/regentmarkets/flutter-deriv-packages/commit/a2fd5b97f81a0dbbc7a6bf07625027e04dfb9a5d)) + +#### `deriv_ui` - `v0.0.3` + + - **FEAT**(deriv_ui): [MOBC-740] move UI related packages to deriv_ui ([#422](https://github.com/regentmarkets/flutter-deriv-packages/issues/422)). ([a2fd5b97](https://github.com/regentmarkets/flutter-deriv-packages/commit/a2fd5b97f81a0dbbc7a6bf07625027e04dfb9a5d)) + + +## 2024-01-29 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v2.0.8`](#deriv_auth---v208) + +--- + +#### `deriv_auth` - `v2.0.8` + + - **REFACTOR**(deriv_auth): add reset state functionality to DerivResetPassCubit ([#423](https://github.com/regentmarkets/flutter-deriv-packages/issues/423)). ([99e8eb12](https://github.com/regentmarkets/flutter-deriv-packages/commit/99e8eb12211ea65157495d77ad9f9c9630c618e3)) + + +## 2024-01-26 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v2.0.7`](#deriv_auth---v207) + +--- + +#### `deriv_auth` - `v2.0.7` + + - **FIX**(deriv_auth): fix 2fa for social login ([#418](https://github.com/regentmarkets/flutter-deriv-packages/issues/418)). ([5a1001c5](https://github.com/regentmarkets/flutter-deriv-packages/commit/5a1001c5d6411baa9c07407db4794bf594cec9a9)) + + +## 2024-01-26 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v2.0.6`](#deriv_auth---v206) + +--- + +#### `deriv_auth` - `v2.0.6` + + - **REFACTOR**(deriv_auth): improve reset password error handling ([#417](https://github.com/regentmarkets/flutter-deriv-packages/issues/417)). ([cc2d2ebb](https://github.com/regentmarkets/flutter-deriv-packages/commit/cc2d2ebbbecf53de56aa7c067aec4ad505fcc6b5)) + + +## 2024-01-23 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v2.0.4`](#deriv_auth---v204) + +--- + +#### `deriv_auth` - `v2.0.4` + + - **REFACTOR**(deriv_auth): renamed localization getter in ContextExtension ([#414](https://github.com/regentmarkets/flutter-deriv-packages/issues/414)). ([b625fbce](https://github.com/regentmarkets/flutter-deriv-packages/commit/b625fbce37e7acfd9454ba16b908207aee3c9a86)) + - **REFACTOR**(deriv_auth): Update package info plus deriv auth ([#410](https://github.com/regentmarkets/flutter-deriv-packages/issues/410)). ([e2e717f3](https://github.com/regentmarkets/flutter-deriv-packages/commit/e2e717f36e93039cadc706cc29b12b48a7c1b411)) + + +## 2024-01-23 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_ui` - `v0.0.2+2`](#deriv_ui---v0022) + - [`deriv_auth` - `v2.0.3`](#deriv_auth---v203) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth` - `v2.0.3` + +--- + +#### `deriv_ui` - `v0.0.2+2` + + - **REFACTOR**(deriv_ui): updated flutter_svg ([#411](https://github.com/regentmarkets/flutter-deriv-packages/issues/411)). ([2289efcb](https://github.com/regentmarkets/flutter-deriv-packages/commit/2289efcbfe3b7de160e0c4cee58b72a347d78eb9)) + + +## 2024-01-23 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_banner` - `v0.0.1+1`](#deriv_banner---v0011) + - [`deriv_store_launcher` - `v0.0.1+1`](#deriv_store_launcher---v0011) + +--- + +#### `deriv_banner` - `v0.0.1+1` + + - **REFACTOR**(deriv_banner): updated kotlin version ([#399](https://github.com/regentmarkets/flutter-deriv-packages/issues/399)). ([9c19b5b4](https://github.com/regentmarkets/flutter-deriv-packages/commit/9c19b5b45aaa40897a7b9884794ab9cbb29fe4ff)) + +#### `deriv_store_launcher` - `v0.0.1+1` + + - **REFACTOR**(deriv_store_launcher): udpated kotlin and gradle version ([#400](https://github.com/regentmarkets/flutter-deriv-packages/issues/400)). ([ad9d72e1](https://github.com/regentmarkets/flutter-deriv-packages/commit/ad9d72e10186e695a72d022d3b3f6ebdd5120666)) + + +## 2024-01-23 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`update_checker` - `v1.2.1`](#update_checker---v121) + +--- + +#### `update_checker` - `v1.2.1` + + - **REFACTOR**(update_checker): increase package info plus version ([#407](https://github.com/regentmarkets/flutter-deriv-packages/issues/407)). ([5147c12b](https://github.com/regentmarkets/flutter-deriv-packages/commit/5147c12bd6b49aed82dd4e3036f63de79816edba)) + + +## 2024-01-23 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_web_socket_client` - `v1.0.1`](#deriv_web_socket_client---v101) + +--- + +#### `deriv_web_socket_client` - `v1.0.1` + + - **REFACTOR**: rename ConnectionState to DerivConnectionState ([#384](https://github.com/regentmarkets/flutter-deriv-packages/issues/384)). ([0252a312](https://github.com/regentmarkets/flutter-deriv-packages/commit/0252a3120fd5f78b7d4a1aa61c0f0ca3f41de40a)) + + +## 2024-01-23 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v2.0.2`](#deriv_auth---v202) + +--- + +#### `deriv_auth` - `v2.0.2` + + - **FIX**(deriv_auth): fix overflow issue in change password layout ([#404](https://github.com/regentmarkets/flutter-deriv-packages/issues/404)). ([abd05684](https://github.com/regentmarkets/flutter-deriv-packages/commit/abd056841f774ffb806e76569b60701d2fa74808)) + + +## 2024-01-23 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v2.0.1`](#deriv_auth---v201) + +--- + +#### `deriv_auth` - `v2.0.1` + + - **REFACTOR**(deriv_auth): updated package_info_plus ([#401](https://github.com/regentmarkets/flutter-deriv-packages/issues/401)). ([16d402a4](https://github.com/regentmarkets/flutter-deriv-packages/commit/16d402a45e680df0734f04fb5dee7f4eb1067119)) + + +## 2024-01-15 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_numpad` - `v1.1.0`](#deriv_numpad---v110) + +--- + +#### `deriv_numpad` - `v1.1.0` + + - **REFACTOR**(deriv_numpad): UI polishing. ([f78c4668](https://github.com/regentmarkets/flutter-deriv-packages/commit/f78c46684d060d304913c945157d1847ec511c98)) + - **REFACTOR**(deriv_numpad): update spacing in textField UI. ([caa25dce](https://github.com/regentmarkets/flutter-deriv-packages/commit/caa25dce509356cf709393282253ed8b68b82ff3)) + - **REFACTOR**(deriv_numpad): add mock stream value in `example` app for exchange rate. ([0876e700](https://github.com/regentmarkets/flutter-deriv-packages/commit/0876e700d6abf672932b5a008234aa2227aef690)) + - **REFACTOR**(deriv_numpad): add `onClose` callback to exchanger. ([14ff5224](https://github.com/regentmarkets/flutter-deriv-packages/commit/14ff5224dfdd7b111b6fba0b2638cc3eb9c9aa60)) + - **REFACTOR**(deriv_numpad): update ui spacing. ([0890e6b8](https://github.com/regentmarkets/flutter-deriv-packages/commit/0890e6b819d74df89211135d2329511cd80b16db)) + - **REFACTOR**(deriv_numpad): add return type for currency exchange. ([0e24f5f7](https://github.com/regentmarkets/flutter-deriv-packages/commit/0e24f5f7ffdb7f26f1237d0e279a72a8eb242dea)) + - **REFACTOR**(deriv_numpad): Minor UI adjustments. ([3830bb3b](https://github.com/regentmarkets/flutter-deriv-packages/commit/3830bb3be7f9e49b268ab5322a0eb677dd42f5b8)) + - **REFACTOR**(deriv_numpad): update `example` app. ([7e6a70c8](https://github.com/regentmarkets/flutter-deriv-packages/commit/7e6a70c83632cab98f7a820120560ca2544fc9c0)) + - **REFACTOR**(deriv_numpad): update notifier instance fetching logic. ([036f7c65](https://github.com/regentmarkets/flutter-deriv-packages/commit/036f7c65e090ed27a7d8ba36e54eab837431129b)) + - **FIX**(deriv_numpad): handle exchange rate change when field is empty. ([9b0ff0b0](https://github.com/regentmarkets/flutter-deriv-packages/commit/9b0ff0b0079c4c90683bf4d868eed96287ed6fd1)) + - **FIX**(deriv_numpad): fix secondary currency type issue. ([6821bd2b](https://github.com/regentmarkets/flutter-deriv-packages/commit/6821bd2b9a0bd8a81bc1c16bc2a4e8fc30674842)) + - **FIX**(deriv_numpad): fix range validation for default case. ([4b403dff](https://github.com/regentmarkets/flutter-deriv-packages/commit/4b403dff5ffd92b28b8e69cf77e24a598ea0a6e5)) + - **FIX**(deriv_numpad): add title for exchanger. ([4347d4fb](https://github.com/regentmarkets/flutter-deriv-packages/commit/4347d4fb9be768b459873c1fa8be3b5e053f332b)) + - **FIX**(deriv_numpad): add dynamic validation from client code. ([f07b5d8a](https://github.com/regentmarkets/flutter-deriv-packages/commit/f07b5d8a54bb3e3996cbe9b272ed4a1d778aaf9f)) + - **FIX**(deriv_numpad): fix empty textfield while swapping. ([3fa9786c](https://github.com/regentmarkets/flutter-deriv-packages/commit/3fa9786c0df1b00bd68188d208e506d92b2606fb)) + - **FIX**(deriv_numpad): fallback sdk version to minimum `2.16.2`. ([e2162e16](https://github.com/regentmarkets/flutter-deriv-packages/commit/e2162e1658280003379e58e6b7edf31bd1d7619e)) + - **FEAT**(deriv_numpad): add factory constructor for currency exchange. ([5744974b](https://github.com/regentmarkets/flutter-deriv-packages/commit/5744974b35acf52ab99c078c45c1cbb69061b44d)) + - **FEAT**(deriv_numpad): add rate changing stream for currency exchange. ([ae9ddbea](https://github.com/regentmarkets/flutter-deriv-packages/commit/ae9ddbea0a1d74c8d31fe92ee5db8e2b3b499a47)) + - **FEAT**(deriv_numpad): update exchange logic for crypto exchange. ([ac808e69](https://github.com/regentmarkets/flutter-deriv-packages/commit/ac808e69ba4e109d21094a2a00493f531c7c28df)) + - **FEAT**(deriv_numpad): polish exchange logic when swapping currency. ([12bbb017](https://github.com/regentmarkets/flutter-deriv-packages/commit/12bbb017b68adf6eaec634e7ecf7f3d6e84299d1)) + - **FEAT**(deriv_numpad): add ability to switch between currency. ([894449f1](https://github.com/regentmarkets/flutter-deriv-packages/commit/894449f1996f455803bc020301df6eafea1d7ba1)) + - **FEAT**(deriv_numpad): add currency exchange UI. ([c47c2a85](https://github.com/regentmarkets/flutter-deriv-packages/commit/c47c2a85e5566cc2b867b847c2136189e5c4d856)) + - **FEAT**(deriv_numpad): add currency switcher ui. ([5ffd83c7](https://github.com/regentmarkets/flutter-deriv-packages/commit/5ffd83c721f19373eeb428bcf36597d575620442)) + - **DOCS**: minor adjustments in documentation. ([7ac660f6](https://github.com/regentmarkets/flutter-deriv-packages/commit/7ac660f6134b9eb89bdf0b5efb40a3dce58ae2ae)) + - **DOCS**(deriv_numpad): add documentation for public member. ([28266e8e](https://github.com/regentmarkets/flutter-deriv-packages/commit/28266e8edf65dfe34d4d9f3470508ab83d8855a0)) + - **DOCS**(deriv_numpad): add documentation for public variables. ([8cbb263e](https://github.com/regentmarkets/flutter-deriv-packages/commit/8cbb263eff0c39b1ba2ab7bad645546954a41146)) + + +## 2024-01-09 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_localizations` - `v1.1.0`](#deriv_localizations---v110) + +--- + +#### `deriv_localizations` - `v1.1.0` + + - **FEAT**(deriv_localizations): [MOBC-669] Added the localizations package. ([#370](https://github.com/regentmarkets/flutter-deriv-packages/issues/370)). ([278d3386](https://github.com/regentmarkets/flutter-deriv-packages/commit/278d33862c798a0c03742feeedb31ff6b6c1c2ff)) + + +## 2024-01-08 + +### Changes + +--- + +Packages with breaking changes: + + - [`deriv_auth` - `v2.0.0`](#deriv_auth---v200) + +Packages with other changes: + + - There are no other changes in this release. + +--- + +#### `deriv_auth` - `v2.0.0` + + - **BREAKING** **REFACTOR**: [MOBC-701] [MOBC-702] combine deriv_auth_ui with deriv_auth and remove deriv_auth_ui ([#388](https://github.com/regentmarkets/flutter-deriv-packages/issues/388)). ([853bcbfa](https://github.com/regentmarkets/flutter-deriv-packages/commit/853bcbfaa63f86194fae3d262fdac736c9a15c48)) + + +## 2024-01-05 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_live_chat` - `v0.0.1+1`](#deriv_live_chat---v0011) + +--- + +#### `deriv_live_chat` - `v0.0.1+1` + + - **FIX**(deriv_live_chat): [DERG-750] Glitch on clicking LiveChat icon ([#307](https://github.com/regentmarkets/flutter-deriv-packages/issues/307)). ([658716d0](https://github.com/regentmarkets/flutter-deriv-packages/commit/658716d09caa60dbc02fdedeb7c7b452cbdc06c6)) + + +## 2024-01-05 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_web_view` - `v0.0.1+1`](#deriv_web_view---v0011) + - [`deriv_ui` - `v0.0.2+1`](#deriv_ui---v0021) + - [`deriv_auth_ui` - `v0.0.3+1`](#deriv_auth_ui---v0031) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_ui` - `v0.0.2+1` + - `deriv_auth_ui` - `v0.0.3+1` + +--- + +#### `deriv_web_view` - `v0.0.1+1` + + - **REFACTOR**(deriv_web_view): updated `flutter_inappwebview` ([#390](https://github.com/regentmarkets/flutter-deriv-packages/issues/390)). ([48f2be45](https://github.com/regentmarkets/flutter-deriv-packages/commit/48f2be452fe72a7885df1af8f0a6eb23cbe1c18d)) + + +## 2023-12-22 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth_ui` - `v0.0.3`](#deriv_auth_ui---v003) + +--- + +#### `deriv_auth_ui` - `v0.0.3` + + - **FEAT**(deriv_auth_ui): add option to enable/disable forgot password & create account ([#352](https://github.com/regentmarkets/flutter-deriv-packages/issues/352)). ([1db8715b](https://github.com/regentmarkets/flutter-deriv-packages/commit/1db8715bb48474f89a464f8f777c927ec1fa313a)) + + +## 2023-12-21 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth_ui` - `v0.0.2+2`](#deriv_auth_ui---v0022) + +--- + +#### `deriv_auth_ui` - `v0.0.2+2` + + - **FIX**(deriv_auth_ui): login button enabled on invalid email and password ([#366](https://github.com/regentmarkets/flutter-deriv-packages/issues/366)). ([5109a0c0](https://github.com/regentmarkets/flutter-deriv-packages/commit/5109a0c0bdc2d5adcfe699db2a8abfcd499126d4)) + + +## 2023-12-18 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v1.2.1`](#deriv_auth---v121) + - [`deriv_auth_ui` - `v0.0.2+1`](#deriv_auth_ui---v0021) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth_ui` - `v0.0.2+1` + +--- + +#### `deriv_auth` - `v1.2.1` + + - **FIX**(deriv_auth): local currencies fromJson authorize_model.dart ([#367](https://github.com/regentmarkets/flutter-deriv-packages/issues/367)). ([4d39a44a](https://github.com/regentmarkets/flutter-deriv-packages/commit/4d39a44a2de970219b241db43bb06b2022f04a3f)) + + +## 2023-12-13 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`analytics` - `v1.0.2`](#analytics---v102) + - [`deriv_auth` - `v1.2.0`](#deriv_auth---v120) + - [`deriv_auth_ui` - `v0.0.2`](#deriv_auth_ui---v002) + - [`deriv_date_range_picker` - `v0.0.1+2`](#deriv_date_range_picker---v0012) + - [`deriv_dependency_injector` - `v1.0.2`](#deriv_dependency_injector---v102) + - [`deriv_env` - `v0.0.1+2`](#deriv_env---v0012) + - [`deriv_numpad` - `v1.0.2`](#deriv_numpad---v102) + - [`deriv_theme` - `v2.2.0`](#deriv_theme---v220) + - [`deriv_ui` - `v0.0.2`](#deriv_ui---v002) + - [`deriv_expandable_bottom_sheet` - `v0.0.1+2`](#deriv_expandable_bottom_sheet---v0012) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_expandable_bottom_sheet` - `v0.0.1+2` + +--- + +#### `analytics` - `v1.0.2` + + - **REVERT**(analytics): versioning and CHANGELOG. ([81a3a0df](https://github.com/regentmarkets/flutter-deriv-packages/commit/81a3a0df27208bd200009415855c6cb944d016e3)) + - **REFACTOR**(analytics): [MOBC-546] Creating unified analytics package. ([#315](https://github.com/regentmarkets/flutter-deriv-packages/issues/315)). ([fd1d8ed3](https://github.com/regentmarkets/flutter-deriv-packages/commit/fd1d8ed345d4ecf91c5f6c1463c5196b40abcbf6)) + +#### `deriv_auth` - `v1.2.0` + + - **FEAT**(deriv_auth): [DERG-1299] add user agent to login ([#341](https://github.com/regentmarkets/flutter-deriv-packages/issues/341)). ([37f5b763](https://github.com/regentmarkets/flutter-deriv-packages/commit/37f5b763d8a679ff1d458fc4e9b82578f4eecf83)) + +#### `deriv_auth_ui` - `v0.0.2` + + - **REFACTOR**(deriv_auth_ui): [MOBC-629] Adding semantics to UI components ([#321](https://github.com/regentmarkets/flutter-deriv-packages/issues/321)). ([36a0c1fa](https://github.com/regentmarkets/flutter-deriv-packages/commit/36a0c1faa0b47d4f735a79daf67c9e2c0089938e)) + - **REFACTOR**: deriv country selection cubit. ([5b0d5f0c](https://github.com/regentmarkets/flutter-deriv-packages/commit/5b0d5f0c37ecd732739204d2a41c334779803945)) + - **REFACTOR**: deriv country selection cubit. ([e88f6332](https://github.com/regentmarkets/flutter-deriv-packages/commit/e88f6332844433953a63ff5933612ea2e115b045)) + - **REFACTOR**: make countries non-nullable. ([e65d7587](https://github.com/regentmarkets/flutter-deriv-packages/commit/e65d7587e334e82486f6510a50015784adf6fddd)) + - **REFACTOR**: add eof and remove deprecated lint. ([c2c7a80a](https://github.com/regentmarkets/flutter-deriv-packages/commit/c2c7a80a5fe39f747897a8a6be09701d125716e9)) + - **REFACTOR**: auth state handler. ([84e2099d](https://github.com/regentmarkets/flutter-deriv-packages/commit/84e2099d65abe8585c64773254fca99059b3b4cb)) + - **REFACTOR**: auth state handling in signup and set password page. ([05c12d53](https://github.com/regentmarkets/flutter-deriv-packages/commit/05c12d53906de229d316fdbcf18396e4b34aed1f)) + - **FIX**: spacing after social flag. ([d782c86a](https://github.com/regentmarkets/flutter-deriv-packages/commit/d782c86a69eae3489e261060c7eacf63eb2da9d0)) + - **FIX**: add missing ok label in referal dialog. ([086fc636](https://github.com/regentmarkets/flutter-deriv-packages/commit/086fc636bdae24da873a9931e7b053b50e6b1a73)) + - **FIX**: reset password button alignment. ([411b2115](https://github.com/regentmarkets/flutter-deriv-packages/commit/411b2115d0f891b61036e601c5f26045e3a861f3)) + - **FIX**: change color to general. ([cac78e49](https://github.com/regentmarkets/flutter-deriv-packages/commit/cac78e49f1650fe1ba5f7698b97ce7a5adaa1308)) + - **FEAT**: [MOBC-608] auth ui setting page ([#320](https://github.com/regentmarkets/flutter-deriv-packages/issues/320)). ([ce8d202b](https://github.com/regentmarkets/flutter-deriv-packages/commit/ce8d202bc1636f29ca99475d645d99aead663e57)) + - **FEAT**: add flag for social login. ([016f3902](https://github.com/regentmarkets/flutter-deriv-packages/commit/016f3902d6d110f76446a0cc4ce0edfa2f35dd8a)) + - **FEAT**: reset pass success page. ([afb19891](https://github.com/regentmarkets/flutter-deriv-packages/commit/afb1989104a02fc3e8c03a55e1b35216500be422)) + - **FEAT**: add gesture detector in get started layout. ([185928c6](https://github.com/regentmarkets/flutter-deriv-packages/commit/185928c6d8489d8edf3732a58cf05d91b0cac71b)) + - **FEAT**: country consent added in country_selection_layout. ([7cc23945](https://github.com/regentmarkets/flutter-deriv-packages/commit/7cc239455f1169777f8c11d78487f1e39e35e76b)) + - **FEAT**: add social button flag. ([f8f95171](https://github.com/regentmarkets/flutter-deriv-packages/commit/f8f9517127a5cda26eed12c290f702c8623826e9)) + - **FEAT**: add auth error state handler in set password page. ([737ed84a](https://github.com/regentmarkets/flutter-deriv-packages/commit/737ed84a282869bce1bbde00794309e9e9fc2b34)) + - **FEAT**: add auth error state handler in sign up page. ([b7910157](https://github.com/regentmarkets/flutter-deriv-packages/commit/b79101574ac82790c8acb3fb5cda93dfb6274500)) + - **FEAT**: add deriv auth error state handler. ([6818ff32](https://github.com/regentmarkets/flutter-deriv-packages/commit/6818ff321b0875a039013e339e55cabba683a4fb)) + - **DOCS**: update documentation based on latest changes. ([4ba23ea9](https://github.com/regentmarkets/flutter-deriv-packages/commit/4ba23ea9b791d5d1d6aaf54fd4562ba1232e4259)) + +#### `deriv_date_range_picker` - `v0.0.1+2` + + - **FIX**: change color to general. ([cac78e49](https://github.com/regentmarkets/flutter-deriv-packages/commit/cac78e49f1650fe1ba5f7698b97ce7a5adaa1308)) + +#### `deriv_dependency_injector` - `v1.0.2` + + - **REFACTOR**(deriv_dependency_injector): [MOBC-534] deprecate deriv dependency injector ([#347](https://github.com/regentmarkets/flutter-deriv-packages/issues/347)). ([92426f15](https://github.com/regentmarkets/flutter-deriv-packages/commit/92426f15eaa5caa529724590e006fd0f65d6800e)) + +#### `deriv_env` - `v0.0.1+2` + + - **REFACTOR**(deriv_env): make package independent of env file ([#318](https://github.com/regentmarkets/flutter-deriv-packages/issues/318)). ([a7242c81](https://github.com/regentmarkets/flutter-deriv-packages/commit/a7242c81b97fda70a622d7bbbb97fe997067117a)) + +#### `deriv_numpad` - `v1.0.2` + + - **FIX**: change color to general. ([cac78e49](https://github.com/regentmarkets/flutter-deriv-packages/commit/cac78e49f1650fe1ba5f7698b97ce7a5adaa1308)) + +#### `deriv_theme` - `v2.2.0` + + - **FEAT**: Ayaan/Added margins 112 and 164 ([#319](https://github.com/regentmarkets/flutter-deriv-packages/issues/319)). ([bf625820](https://github.com/regentmarkets/flutter-deriv-packages/commit/bf6258206b4bb4cbdd1ef6744e07e2adb8d0d5ee)) + +#### `deriv_ui` - `v0.0.2` + + - **REFACTOR**(deriv_auth_ui): [MOBC-629] Adding semantics to UI components ([#321](https://github.com/regentmarkets/flutter-deriv-packages/issues/321)). ([36a0c1fa](https://github.com/regentmarkets/flutter-deriv-packages/commit/36a0c1faa0b47d4f735a79daf67c9e2c0089938e)) + - **FIX**: change color to general. ([cac78e49](https://github.com/regentmarkets/flutter-deriv-packages/commit/cac78e49f1650fe1ba5f7698b97ce7a5adaa1308)) + - **FEAT**: country consent added in country_selection_layout. ([7cc23945](https://github.com/regentmarkets/flutter-deriv-packages/commit/7cc239455f1169777f8c11d78487f1e39e35e76b)) + + +## 2023-12-12 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_dependency_injector` - `v1.0.1`](#deriv_dependency_injector---v101) + +--- + +#### `deriv_dependency_injector` - `v1.0.1` + + - **REFACTOR**(deriv_dependency_injector): [MOBC-534] deprecate deriv dependency injector ([#347](https://github.com/regentmarkets/flutter-deriv-packages/issues/347)). ([92426f15](https://github.com/regentmarkets/flutter-deriv-packages/commit/92426f15eaa5caa529724590e006fd0f65d6800e)) + + +## 2023-12-08 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`analytics` - `v1.0.1`](#analytics---v101) + +--- + +#### `analytics` - `v1.0.1` + + - **REVERT**(analytics): versioning and CHANGELOG. ([81a3a0df](https://github.com/regentmarkets/flutter-deriv-packages/commit/81a3a0df27208bd200009415855c6cb944d016e3)) + - **REFACTOR**(analytics): [MOBC-546] Creating unified analytics package. ([#315](https://github.com/regentmarkets/flutter-deriv-packages/issues/315)). ([fd1d8ed3](https://github.com/regentmarkets/flutter-deriv-packages/commit/fd1d8ed345d4ecf91c5f6c1463c5196b40abcbf6)) + + +## 2023-12-04 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth` - `v1.1.0`](#deriv_auth---v110) + - [`deriv_auth_ui` - `v0.0.1+3`](#deriv_auth_ui---v0013) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_auth_ui` - `v0.0.1+3` + +--- + +#### `deriv_auth` - `v1.1.0` + + - **FEAT**(deriv_auth): [DERG-1299] add user agent to login ([#341](https://github.com/regentmarkets/flutter-deriv-packages/issues/341)). ([37f5b763](https://github.com/regentmarkets/flutter-deriv-packages/commit/37f5b763d8a679ff1d458fc4e9b82578f4eecf83)) + + +## 2023-11-29 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_auth_ui` - `v0.0.1+2`](#deriv_auth_ui---v0012) + - [`deriv_ui` - `v0.0.1+2`](#deriv_ui---v0012) + +--- + +#### `deriv_auth_ui` - `v0.0.1+2` + + - **REFACTOR**(deriv_auth_ui): [MOBC-629] Adding semantics to UI components ([#321](https://github.com/regentmarkets/flutter-deriv-packages/issues/321)). ([36a0c1fa](https://github.com/regentmarkets/flutter-deriv-packages/commit/36a0c1faa0b47d4f735a79daf67c9e2c0089938e)) + +#### `deriv_ui` - `v0.0.1+2` + + - **REFACTOR**(deriv_auth_ui): [MOBC-629] Adding semantics to UI components ([#321](https://github.com/regentmarkets/flutter-deriv-packages/issues/321)). ([36a0c1fa](https://github.com/regentmarkets/flutter-deriv-packages/commit/36a0c1faa0b47d4f735a79daf67c9e2c0089938e)) + + +## 2023-11-28 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_theme` - `v2.1.0`](#deriv_theme---v210) + - [`deriv_date_range_picker` - `v0.0.1+1`](#deriv_date_range_picker---v0011) + - [`deriv_ui` - `v0.0.1+1`](#deriv_ui---v0011) + - [`deriv_auth_ui` - `v0.0.1+1`](#deriv_auth_ui---v0011) + - [`deriv_expandable_bottom_sheet` - `v0.0.1+1`](#deriv_expandable_bottom_sheet---v0011) + - [`deriv_numpad` - `v1.0.1`](#deriv_numpad---v101) + +Packages with dependency updates only: + +> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project. + + - `deriv_date_range_picker` - `v0.0.1+1` + - `deriv_ui` - `v0.0.1+1` + - `deriv_auth_ui` - `v0.0.1+1` + - `deriv_expandable_bottom_sheet` - `v0.0.1+1` + - `deriv_numpad` - `v1.0.1` + +--- + +#### `deriv_theme` - `v2.1.0` + + - **FEAT**: Ayaan/Added margins 112 and 164 ([#319](https://github.com/regentmarkets/flutter-deriv-packages/issues/319)). ([bf625820](https://github.com/regentmarkets/flutter-deriv-packages/commit/bf6258206b4bb4cbdd1ef6744e07e2adb8d0d5ee)) + + +## 2023-11-27 + +### Changes + +--- + +Packages with breaking changes: + + - There are no breaking changes in this release. + +Packages with other changes: + + - [`deriv_env` - `v0.0.1+1`](#deriv_env---v0011) + +--- + +#### `deriv_env` - `v0.0.1+1` + + - **REFACTOR**(deriv_env): make package independent of env file ([#318](https://github.com/regentmarkets/flutter-deriv-packages/issues/318)). ([a7242c81](https://github.com/regentmarkets/flutter-deriv-packages/commit/a7242c81b97fda70a622d7bbbb97fe997067117a)) + diff --git a/README.md b/README.md index 5b70334a5..f3918501c 100644 --- a/README.md +++ b/README.md @@ -2,33 +2,95 @@ This repository contains private packages & plugins that are used by the company's products built in Flutter. +### GIT HOOK + +This Hook will check for Semantic versioning commit convention
+1- Please download commit-msg file from githooks/commit-msg.
+2- Run the command below.
+ +```BASH +cp $HOME/Downloads/commit-msg $HOME/.git/hooks/commit-msg \ + && chmod +x $HOME/.git/hooks/commit-msg +``` + +## Using the packages + +Each package has been released as git tag with convention as **packageName-vVersionNumber**`(Example: deriv_auth-v7.0.7)`. To use the package, add the following to your pubspec.yaml file: + +```yaml +deriv_ui: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_ui + ref: deriv_ui-v0.1.1 #your prefered version +``` + ## Packages -| Name | Description | -| ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | -| [analytics](./packages/analytics) | Used to collect and send analytical information to 'Firebase' and 'Segment'. | -| [deriv_auth](./packages/deriv_auth) | A Dart package that provides Authentication logic for Deriv applications. | -| [deriv_auth_ui](./packages/deriv_auth_ui) | A Dart package that provides Authentication UI for Deriv applications. | -| [deriv_banner](./packages/deriv_banner) | A widget to show banner in apps. | -| [deriv_bloc_manager](./packages/deriv_bloc_manager) | Provides some tools to manage blocs. | -| [deriv_datadog](./packages/deriv_datadog) | A package that helps you monitor the performance and user interactions of your Flutter app by sending data to Datadog. | -| [deriv_date_range_picker](./packages/deriv_date_range_picker) | Provides a widget that allows users to select a date range either by calendar mode or input mode. | -| [deriv_dependency_injector](./packages/deriv_dependency_injector) | A package for handling dependency injection in Dart. | -| [deriv_env](./packages/deriv_env) | A package to load and store environment variables. | -| [deriv_expandable_bottom_sheet](./packages/deriv_expandable_bottom_sheet) | A widget that helps to display an expandable bottom sheet. | -| [deriv_http_client](./packages/deriv_http_client) | A package that provides a wrapper for http package. | -| [deriv_lint](./packages/deriv_lint) | A Dart package that provides lint rules for Dart and Flutter. | -| [deriv_live_chat](./packages/deriv_live_chat) | A plugin for live chat SDK support to dart. | -| [deriv_numpad](./packages/deriv_numpad) | Number Pad Widget for number input. | -| [deriv_rudderstack](./packages/deriv_rudderstack) | A plugin that add RudderStack SDK support to Flutter. | -| [deriv_signup_login](./packages/deriv_signup_login) | A package for handling in app signup and login. | -| [deriv_store_launcher](./packages/deriv_store_launcher) | A plugin to lunch app stores base on platform and manufacturer. | -| [deriv_technical_analysis](./packages/deriv_technical_analysis) | A Dart package for Technical Analysis. | -| [deriv_theme](./packages/deriv_theme) | A package that contains the theme used by Deriv products. | -| [deriv_ui](./packages/deriv_ui) | A package that contains the UI components used by Deriv products. | -| [deriv_utilities](./packages/deriv_utilities) | A package that contains the utilities including helper functions, mixins, and extensions. | -| [deriv_websocket](./packages/deriv_web_socket_client) | A package that provides a easy to use websocket client. | -| [deriv_web_view](./packages/deriv_web_view) | Deriv web view package. | -| [form_builder](./packages/form_builder) | A simpler and cleaner way to create, validate and submit forms. | -| [update_checker](./packages/update_checker) | Check and retrieve update information from the server for the given package. | +| Name | Description | Version | +| ------------------------------------------------------------------------- |-------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------| +| [analytics](./packages/analytics) | Used to collect and send analytical information to 'Firebase', 'Rudderstack' and 'Datadog'. | [v2.2.2](./packages/analytics/CHANGELOG.md) | | [v4.1.0](./packages/analytics/CHANGELOG.md) | +| [deriv_auth](./packages/deriv_auth) | A Dart package that provides Authentication logic for Deriv applications. | [v7.0.7 ](./packages/deriv_auth/CHANGELOG.md) | +| [deriv_banner](./packages/deriv_banner) | A widget to show banner in apps. | [v0.0.1+1](./packages/deriv_banner/CHANGELOG.md) | +| [deriv_bloc_manager](./packages/deriv_bloc_manager) | Provides some tools to manage blocs. | [v0.0.1](./packages/deriv_bloc_manager/CHANGELOG.md) | +| [deriv_datadog](./packages/deriv_datadog) | A package that helps you monitor the performance and user interactions of your Flutter app by sending data to Datadog. | [v0.0.1](./packages/deriv_datadog/CHANGELOG.md) | +| [deriv_date_range_picker](./packages/deriv_date_range_picker) | Provides a widget that allows users to select a date range either by calendar mode or input mode. | [v0.0.1+12](./packages/deriv_date_range_picker/CHANGELOG.md) | +| [deriv_dependency_injector](./packages/deriv_dependency_injector) | A package for handling dependency injection in Dart. | [v1.0.2](./packages/deriv_dependency_injector/CHANGELOG.md) | +| [deriv_env](./packages/deriv_env) | A package to load and store environment variables. | [v0.0.1+2](./packages/deriv_env/CHANGELOG.md) | +| [deriv_expandable_bottom_sheet](./packages/deriv_expandable_bottom_sheet) | A widget that helps to display an expandable bottom sheet. | [v0.0.2](./packages/deriv_expandable_bottom_sheet/CHANGELOG.md) | +| [deriv_http_client](./packages/deriv_http_client) | A package that provides a wrapper for http package. | [v2.0.2](./packages/deriv_http_client/CHANGELOG.md) | +| [deriv_lint](./packages/deriv_lint) | A Dart package that provides lint rules for Dart and Flutter. | [v1.0.0](./packages/deriv_lint/CHANGELOG.md) | +| [deriv_live_chat](./packages/deriv_live_chat) | A plugin for live chat SDK support to dart. | [v0.0.2](./packages/deriv_live_chat/CHANGELOG.md) | +| [deriv_language_selector](./packages/deriv_language_selector) | A package to handle language change of the app. | [v0.0.3+14](./packages/deriv_language_selector/CHANGELOG.md) | +| [deriv_localizations](./packages/deriv_localizations) | A Package that contains the localization arb(coming from Crowdin) and dart generated files for flutter_deriv_packages. | [v1.7.2](./packages/deriv_localizations/CHANGELOG.md) | +| [deriv_numpad](./packages/deriv_numpad) | Number Pad Widget for number input. | [v1.1.11](./packages/deriv_numpad/CHANGELOG.md) | +| [deriv_rudderstack](./packages/deriv_rudderstack) | A plugin that add RudderStack SDK support to Flutter. | [v1.2.0](./packages/deriv_rudderstack/CHANGELOG.md) | +| [deriv_store_launcher](./packages/deriv_store_launcher) | A plugin to launch app stores base on platform and manufacturer. | [v0.0.2](./packages/deriv_store_launcher/CHANGELOG.md) | +| [deriv_technical_analysis](./packages/deriv_technical_analysis) | A Dart package for Technical Analysis. | [v0.0.1](./packages/deriv_technical_analysis/CHANGELOG.md) | +| [deriv_theme](./packages/deriv_theme) | A package that contains the theme used by Deriv products. | [v2.8.0](./packages/deriv_theme/CHANGELOG.md) | +| [deriv_ui](./packages/deriv_ui) | A package that contains the UI components used by Deriv products. | [v0.1.1](./packages/deriv_ui/CHANGELOG.md) | +| [deriv_utilities](./packages/deriv_utilities) | A package that contains the utilities including helper functions, mixins, and extensions. | [v1.0.0](./packages/deriv_utilities/CHANGELOG.md) | +| [deriv_websocket](./packages/deriv_web_socket_client) | A package that provides a easy to use websocket client. | [v1.0.1](./packages/deriv_web_socket_client/CHANGELOG.md) | +| [deriv_web_view](./packages/deriv_web_view) | Deriv web view package. | [v0.2.2+5](./packages/deriv_web_view/CHANGELOG.md) | +| [deriv_widgetbook](./packages/deriv_widgetbook) | Storybook for Deriv UI Widgets and Components | [v0.0.2+35](./packages/deriv_widgetbook/CHANGELOG.md) | +| [form_builder](./packages/form_builder) | A simpler and cleaner way to create, validate and submit forms. | [v1.0.0+1](./packages/form_builder/CHANGELOG.md) | +| [update_checker](./packages/update_checker) | Check and retrieve update information from the server for the given package. | [v3.1.1](./packages/update_checker/CHANGELOG.md) | +| [deriv_feature_flag](./packages/deriv_feature_flag) | A package to provide feature flag functionality for apps. | [v0.1.2](./packages/deriv_feature_flag/CHANGELOG.md) | +| [deriv_mobile_chart_wrapper](./packages/deriv_mobile_chart_wrapper) | A wrapper package around package _**deriv_chart**_ to implement any functionality specific to mobile and can be wrapped around the main chart package. | [v0.1.8+1](./packages/deriv_mobile_chart_wrapper/CHANGELOG.md) | +| [deriv_cipher](./packages/deriv_cipher) | A package to encrypt and decrypt data using AES encryption.| [v0.0.2](./packages/deriv_cipher/CHANGELOG.md) | + + +## Environment Setup + +We use [Melos](https://pub.dev/packages/melos) to manage the multiple packages in this repository. To get started, install Melos globally: + +```bash +dart pub global activate melos +``` + +Running `pub get` on all packages, run: + +```bash +melos bootstrap +``` + +`Analyze` and `Test` has already been configured in `melos.yaml` so you can do the following: + +Running `flutter analyze` on all packages: + +```bash +melos run analyze +``` + +Running `flutter test` on all packages: + +```bash +melos run test --no-select +``` + +If you'd like to run any other command on all packages, you can configure it in `melos.yaml` or run it directly with melos: +```bash +$ melos exec --\ + your command here +``` diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 000000000..b60c54779 --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,4 @@ +pull_request_title: "chore(deriv_localizations): Crowdin Localization Updated" +files: + - source: /packages/deriv_localizations/lib/l10n/**/app_en.arb + translation: /packages/deriv_localizations/lib/l10n/**/app_%two_letters_code%.arb diff --git a/docs/versioning_workflow.md b/docs/versioning_workflow.md new file mode 100644 index 000000000..05995b8b2 --- /dev/null +++ b/docs/versioning_workflow.md @@ -0,0 +1,48 @@ +## Versioning workflow + +We have setup an automated workflow for semantic versioning of the package after any change on the package. + +### Pre-requisites for versioning + +- The PR being merged is following conventional message pattern. Example: +``` +feat(package_name): add new feature +``` +- If we are ```squash and merging``` or ```creating merge commit```, the commit message should be same as PR title which has followed the convention. + +- If we are ```rebase and merging```, the commit messages on the PR should follow the convention. + +**Note**: ```Squash and merging``` PRs is preferred because of having clean history in master. Learn more [here](https://wikijs.deriv.cloud/en/Mobile/Github-Contributions#squashing-and-merging). + +For commit message rules please refer [GIT_RULES.md](../.github/GIT_RULES.md). + +### Message pattern for versioning + +- feat!(package_name): breaking change updates the major version. +- feat(package_name): new feature updates the minor version. +- fix(package_name): bug fix updates the patch version. +- refactor(package_name): code changes that doesn't fix or add anything updates the patch version. +- chore, ci: no change in version. + +### How versioning works + +- When a PR is merged, the [version.yaml](.github/workflows/version.yaml) will run. +- [Melos](https://melos.invertase.dev/) will do the magic of bumping the version of the package and the package that is dependent on the package with new update. +- Git tags will be created for the new version. +- Slack notification will be sent to the #announce_flutter_packages_update channel with the new tag versions. +- Pull request will be created to update the packages version in ```pubspec.yaml``` and ```Readme``` and also to update the changelogs. + +Note: Until the PR to update the package version is merged, the latest package version will not be reflected on the master branch so for latest update you can check slack channel. + +### FAQs + +1. What if I have made changes in two packages, which package name should I use in PR title? +- If you have made changes in two packages you can use the package name of the parent package. Example: ```deriv_auth``` is dependent on ```deriv_ui```, so if you made changes in both packages you can use ```deriv_ui``` as package name in the pr. +If two packages you changed are not related its recommended to create two separate PRs. + +2. Where can I see the latest version of the package? +- Main README.md of this repository will have the latest version of the package. + +3. What if I make a change like doc update that dont need versioning? +- To avoid versioning for such changes you can use the message type as ```chore```. Example: ```chore(deriv_auth): update readme```. + diff --git a/flutter_architecture_poc/example/pubspec.lock b/flutter_architecture_poc/example/pubspec.lock new file mode 100644 index 000000000..c6c04945f --- /dev/null +++ b/flutter_architecture_poc/example/pubspec.lock @@ -0,0 +1,443 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + bloc: + dependency: transitive + description: + name: bloc + sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" + url: "https://pub.dev" + source: hosted + version: "8.1.4" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 + url: "https://pub.dev" + source: hosted + version: "3.0.5" + dart_web_socket_handler: + dependency: "direct main" + description: + path: "." + ref: main + resolved-ref: "660fad8b27c73426ce6054ec971abeebadf4c77b" + url: "https://github.com/hamed-deriv/dart_web_socket_handler.git" + source: git + version: "1.0.0" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a + url: "https://pub.dev" + source: hosted + version: "8.1.6" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: e1a30a66d734f9e498b1b6522d6a75ded28242bad2359a9158df38a1c30bcf1f + url: "https://pub.dev" + source: hosted + version: "10.2.0" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.dev" + source: hosted + version: "0.18.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + url: "https://pub.dev" + source: hosted + version: "1.15.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + provider: + dependency: transitive + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" + rxdart: + dependency: "direct main" + description: + name: rxdart + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" + source: hosted + version: "0.27.7" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + url: "https://pub.dev" + source: hosted + version: "14.2.5" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" + url: "https://pub.dev" + source: hosted + version: "2.4.5" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" +sdks: + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/flutter_architecture_poc/pubspec.lock b/flutter_architecture_poc/pubspec.lock new file mode 100644 index 000000000..1ff280296 --- /dev/null +++ b/flutter_architecture_poc/pubspec.lock @@ -0,0 +1,402 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77" + url: "https://pub.dev" + source: hosted + version: "73.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.2" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a" + url: "https://pub.dev" + source: hosted + version: "6.8.0" + args: + dependency: transitive + description: + name: args + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + url: "https://pub.dev" + source: hosted + version: "2.6.0" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + url: "https://pub.dev" + source: hosted + version: "1.19.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + coverage: + dependency: transitive + description: + name: coverage + sha256: c1fb2dce3c0085f39dc72668e85f8e0210ec7de05345821ff58530567df345a5 + url: "https://pub.dev" + source: hosted + version: "1.9.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 + url: "https://pub.dev" + source: hosted + version: "3.0.5" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "40f592dd352890c3b60fec1b68e786cefb9603e05ff303dbc4dda49b304ecdf4" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + js: + dependency: transitive + description: + name: js + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + url: "https://pub.dev" + source: hosted + version: "0.7.1" + lints: + dependency: "direct dev" + description: + name: lints + sha256: a2c3d198cb5ea2e179926622d433331d8b58374ab8f29cdda6e863bd62fd369c + url: "https://pub.dev" + source: hosted + version: "1.0.1" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + macros: + dependency: transitive + description: + name: macros + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + url: "https://pub.dev" + source: hosted + version: "0.1.2-main.4" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" + source: hosted + version: "0.10.12" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + url: "https://pub.dev" + source: hosted + version: "1.12.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test: + dependency: "direct dev" + description: + name: test + sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" + url: "https://pub.dev" + source: hosted + version: "1.25.8" + test_api: + dependency: transitive + description: + name: test_api + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + url: "https://pub.dev" + source: hosted + version: "0.7.3" + test_core: + dependency: transitive + description: + name: test_core + sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" + url: "https://pub.dev" + source: hosted + version: "0.6.5" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + url: "https://pub.dev" + source: hosted + version: "14.3.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.4.0 <4.0.0" diff --git a/githooks/commit-msg b/githooks/commit-msg new file mode 100755 index 000000000..096e4d223 --- /dev/null +++ b/githooks/commit-msg @@ -0,0 +1,28 @@ +#!/bin/sh + +if [ -z "$1" ]; then + echo "Missing argument (commit message). Did you try to run this manually?" + exit 1 +fi + +commitTitle="$(cat $1 | head -n1)" + +# ignore merge requests +if echo "$commitTitle" | grep -qE "Merge branch"; then + echo "Commit hook: ignoring branch merge" + exit 0 +fi +# check semantic versioning scheme +if ! echo "$commitTitle" | grep -qE '^(feat|fix|docs|style|refactor|perf|test|chore|build|ci|revert)(\([a-z0-9\s\-\_\,]+\))?!?:\s\w'; then + echo "Your commit message did not follow semantic versioning: $commitTitle" + echo "" + echo "Format: (): " + echo "Example: feat(deriv-auth): single entry" + echo "" + echo "Valid types: build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test" + echo "" + echo "Please see" + echo "- https://github.com/regentmarkets/flutter-deriv-packages/blob/master/.github/GIT_RULES.md" + echo "- https://www.conventionalcommits.org/en/v1.0.0/#summary" + exit 1 +fi diff --git a/melos.yaml b/melos.yaml new file mode 100644 index 000000000..c2d73fcdb --- /dev/null +++ b/melos.yaml @@ -0,0 +1,47 @@ +name: flutter_deriv_packages +repository: https://github.com/regentmarkets/flutter-deriv-packages + +packages: + - packages/* + +command: + version: + # Generate commit links in package changelogs. + linkToCommits: true + workspaceChangelog: true + updateGitTagRefs: true + + bootstrap: + # It seems so that running "pub get" in parallel has some issues (like + # https://github.com/dart-lang/pub/issues/3404). Disabling this feature + # makes the CI much more stable. + runPubGetInParallel: false + +scripts: + analyze: + name: Flutter Analyze + description: Run flutter analyze for all packages. + ## Only fail if there are errors or warnings and not on infos. + run: | + if flutter analyze 2>&1 | grep -q -E 'error:|warning:'; + then exit 1 + else exit 0 + fi + exec: + concurrency: 1 + failFast: true + + test: + name: Flutter Test + description: Run flutter test for all packages. + run: flutter test --coverage + exec: + concurrency: 6 + failFast: true + packageFilters: + dirExists: + - test + ignore: + # Ignore until we have a proper test. + - "deriv_feature_flag" + - "deriv_auth" diff --git a/packages/analytics/.gitignore b/packages/analytics/.gitignore index 4655c8a00..0927debe3 100644 --- a/packages/analytics/.gitignore +++ b/packages/analytics/.gitignore @@ -73,3 +73,7 @@ pubspec.lock !**/ios/**/default.mode2v3 !**/ios/**/default.pbxuser !**/ios/**/default.perspectivev3 + +# FVM Version Cache +.fvm/ +.fvmrc diff --git a/packages/analytics/CHANGELOG.md b/packages/analytics/CHANGELOG.md index 0a228ceff..33d4b2a9e 100644 --- a/packages/analytics/CHANGELOG.md +++ b/packages/analytics/CHANGELOG.md @@ -1,3 +1,55 @@ +## 4.1.0 + + - **FEAT**(analytics): Add indicator events ([#846](https://github.com/regentmarkets/flutter-deriv-packages/issues/846)). ([1d30ddc9](https://github.com/regentmarkets/flutter-deriv-packages/commit/1d30ddc9c7510e280cc90fc7e1308b945d5758ed)) + +## 4.0.0 + +> Note: This release has breaking changes. + + - **BREAKING** **FEAT**(analytics): add auto trade rudderstack events ([#845](https://github.com/regentmarkets/flutter-deriv-packages/issues/845)). ([8c15aafd](https://github.com/regentmarkets/flutter-deriv-packages/commit/8c15aafda41428c9d19272117b0a5a49b16b4154)) + +## 3.0.0 + +> Note: This release has breaking changes. + + - **BREAKING** **FEAT**(analytics): [DRGO-1247] Ramin/update for flutter 3.24 ([#838](https://github.com/regentmarkets/flutter-deriv-packages/issues/838)). ([cfdf2d81](https://github.com/regentmarkets/flutter-deriv-packages/commit/cfdf2d812b4d4227f72b434f7db9ea182081ae6b)) + +## 2.3.0 + + - **FEAT**(analytics): Amend tracking events Rudderstack ([#821](https://github.com/regentmarkets/flutter-deriv-packages/issues/821)). ([bd197376](https://github.com/regentmarkets/flutter-deriv-packages/commit/bd197376cf450ec375a9759c9511563a253a0c64)) + +## 2.2.2 + + - **REFACTOR**(analytics): Amend virtual n real tracking events ([#771](https://github.com/regentmarkets/flutter-deriv-packages/issues/771)). ([fc8850f0](https://github.com/regentmarkets/flutter-deriv-packages/commit/fc8850f01c1bb0a6805b41b3ff10e2a6e33b6e0f)) + +## 2.2.1 + + - **FIX**(analytics): ilya/TRHM-945/Amend_Virtual_n_Real_Tracking_Events ([#762](https://github.com/regentmarkets/flutter-deriv-packages/issues/762)). ([cbba6688](https://github.com/regentmarkets/flutter-deriv-packages/commit/cbba668827d72c971d1100ecafee5719d4617639)) + +## 2.2.0 + + - **FEAT**(analytics): add trade page events to rudderstack events ([#691](https://github.com/regentmarkets/flutter-deriv-packages/issues/691)). ([b9e2b098](https://github.com/regentmarkets/flutter-deriv-packages/commit/b9e2b098fb76ea4f8f5f633c062a6cd20f4db6f0)) + +## 2.1.0 + + - **FEAT**(analytics): ilya/DERG-2409/Add_tracking_to_real_account_sign_up_on_Deriv_Go ([#603](https://github.com/regentmarkets/flutter-deriv-packages/issues/603)). ([9ada725d](https://github.com/regentmarkets/flutter-deriv-packages/commit/9ada725ded271e2b94cffed622eff2e75539cb55)) + +## 2.0.0 + +> Note: This release has breaking changes. + + - **BREAKING** **REFACTOR**(analytics): added logAppOpen event ([#610](https://github.com/regentmarkets/flutter-deriv-packages/issues/610)). ([74f8d9c3](https://github.com/regentmarkets/flutter-deriv-packages/commit/74f8d9c3a7311ec7abb1cfe76c3f6f190fbcb81a)) + +## 1.0.2 + + - **REVERT**(analytics): versioning and CHANGELOG. ([81a3a0df](https://github.com/regentmarkets/flutter-deriv-packages/commit/81a3a0df27208bd200009415855c6cb944d016e3)) + - **REFACTOR**(analytics): [MOBC-546] Creating unified analytics package. ([#315](https://github.com/regentmarkets/flutter-deriv-packages/issues/315)). ([fd1d8ed3](https://github.com/regentmarkets/flutter-deriv-packages/commit/fd1d8ed345d4ecf91c5f6c1463c5196b40abcbf6)) + +## 1.0.1 + + - **REVERT**(analytics): versioning and CHANGELOG. ([81a3a0df](https://github.com/regentmarkets/flutter-deriv-packages/commit/81a3a0df27208bd200009415855c6cb944d016e3)) + - **REFACTOR**(analytics): [MOBC-546] Creating unified analytics package. ([#315](https://github.com/regentmarkets/flutter-deriv-packages/issues/315)). ([fd1d8ed3](https://github.com/regentmarkets/flutter-deriv-packages/commit/fd1d8ed345d4ecf91c5f6c1463c5196b40abcbf6)) + ## [1.0.0] - Migrated the package to null safety. diff --git a/packages/analytics/README.md b/packages/analytics/README.md index 1b38b96a5..a2d25a263 100644 --- a/packages/analytics/README.md +++ b/packages/analytics/README.md @@ -1,121 +1,127 @@ -# analytics -*** -This package is used for collecting and sending analytical information from the app to "Firebase" and "RudderStack". +# Deriv Analytics Library + +A Flutter analytics library that integrates multiple analytics services including Datadog, Firebase, and RudderStack. + +## Table of Contents + +1. [Installation](#installation) +2. [Quick Start](#quick-start) +3. [Configuration](#configuration) + - [Datadog](#datadog) + - [Firebase](#firebase) + - [RudderStack](#rudderstack) +4. [Logging Events](#logging-events) +5. [User Identification](#user-identification) +6. [Tracking Screens](#tracking-screens) + ## Installation -##### 1. Add to pubspec.yaml + +To install the package, add the following to your `pubspec.yaml`: + ```yaml -analytics: +dependencies: + analytics: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/analytics ref: ``` -Setup the Android and iOS sources as described at rudderstack.com and generate the write keys for Android and iOS sources. - -Set the WRITE_KEY in addition to enabling the plugin to track app lifecycle, screens, and whether to enable debug mode or not in Android and iOS as follows: - -### Android -```xml - - - - [...] - - - - - - - - - -``` -### iOS -```xml - - - - - [...] - com.deriv.rudderstack.WRITE_KEY - ADD-YOUR-KEY - com.deriv.rudderstack.TRACK_APPLICATION_LIFECYCLE_EVENTS - - com.deriv.rudderstack.RECORD_SCREEN_VIEWS - - com.deriv.rudderstack.DEBUG - - [...] - - -``` +Run `flutter packages get` to fetch the package. + +## Quick Start + +Initialize the analytics services you want to use in your `main.dart`: -## How to use -*** -##### 1. Enabling analytics. -```dart -Analytics().init( - isEnabled: true, // set value to false for disable 'Analytics' - ); -``` -##### 2. To track PageRoute transitions. -```dart -MaterialApp( - navigatorObservers: Analytics().observer == null - ? [] - : [Analytics().observer], - ); -``` -##### 3. Logging to 'RudderStack' in different scenarios. -###### when app is opened. -```dart -Analytics().logAppOpened(); -``` -###### when app is in background. ```dart -Analytics().logAppBackgrounded(); +final datadog = DerivDatadog(); +final firebase = DerivFirebaseAnalytics(); +final rudderstack = DerivRudderstack(); + +void main() { + + WidgetsFlutterBinding.ensureInitialized(); + + DerivDatadogConfiguration configuration = DerivDatadogConfiguration( + applicationId: 'DATADOG_APPLICATION_ID', + clientToken: 'DATADOG_CLIENT_TOKEN', + env: 'DATADOG_ENVIRONMENT', + serviceName: 'DATADOG_SERVICE_NAME', + trackingConsent: TrackingConsent.granted, + ); + + DerivDatadog().setup(configuration); + + DerivRudderstack().setup(RudderstackConfiguration( + dataPlaneUrl: 'RUDDERSTACK_DATA_PLANE_URL', + writeKey: 'RUDDERSTACK_WRITE_KEY', + debugEnabled: true, + )); + + DerivFirebaseAnalytics(FirebaseAnalytics.instanceFor(app: await Firebase.initializeApp())).setup( + FirebaseConfiguration( + isAnalyticsCollectionEnabled: true, + ), + ); + + runApp(MyApp()); +} ``` -###### when app is crashed. + +## Configuration + +### Datadog + ```dart -Analytics().logAppCrashed(); +await datadog.setup(DerivDatadogConfiguration( + clientToken: 'your_client_token', + applicationId: 'your_application_id', + env: 'production', + trackingConsent: TrackingConsent.granted, +)); ``` -##### 4. Sending information about current screen. +### Firebase + ```dart -Analytics().setCurrentScreen(screenName: ""); +await firebase.setup(FirebaseConfiguration( + isAnalyticsCollectionEnabled: true, +)); ``` -##### 4. Setting routes/screens which need to be ignored for analytics. + +### RudderStack + ```dart -Analytics().setIgnoredRoutes([ - 'IGNORED_SCREEN_NAME_1', - 'IGNORED_SCREEN_NAME_2', - '.....................', - 'IGNORED_SCREEN_NAME_N' - ]); +await rudderstack.setup(RudderstackConfiguration( + dataPlaneUrl: 'your_data_plane_url', + writeKey: 'your_write_key', +)); ``` -##### 4. Sending information during user login. + +## Logging Events + +You can log events as follows: + ```dart -Analytics().logLoginEvent(userId: "",); +datadog.logEvent('button_click', {'label': 'cta_button'}); +firebase.logEvent('button_click', {'label': 'cta_button'}); +rudderstack.track('button_click'); ``` -##### 7. Sending information during user logout. + +## User Identification + +To identify a user: + ```dart -Analytics().logLogoutEvent(); +datadog.setUserInfo(id: '123', email: 'email@example.com'); +firebase.setUserId(id: '123'); +rudderstack.identify(userId: '123'); ``` -##### 8. Sending information about important events to "Firebase". + +## Tracking Screens + ```dart -Analytics().logToFirebase( - name: "", - params: {'PARAM_1': 'VALUE_1', - 'PARAM_1': 'VALUE_1', - '.......': '.......', - 'PARAM_N': 'VALUE_N'}, - ); +datadog.screen(screenName: 'Home'); +firebase.setCurrentScreen(screenName: 'Home'); +rudderstack.screen(screenName: 'Home'); ``` diff --git a/packages/analytics/example/.env.example b/packages/analytics/example/.env.example new file mode 100644 index 000000000..ccbb3537c --- /dev/null +++ b/packages/analytics/example/.env.example @@ -0,0 +1,9 @@ +DATADOG_APPLICATION_ID = +DATADOG_CLIENT_TOKEN = +DATADOG_ENVIRONMENT = +DATADOG_SERVICE_NAME = + + + +RUDDERSTACK_DATA_PLANE_URL = +RUDDERSTACK_WRITE_KEY = diff --git a/packages/analytics/example/.gitignore b/packages/analytics/example/.gitignore new file mode 100644 index 000000000..a62ba7b84 --- /dev/null +++ b/packages/analytics/example/.gitignore @@ -0,0 +1,48 @@ +# 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 + +.env \ No newline at end of file diff --git a/packages/analytics/example/.metadata b/packages/analytics/example/.metadata new file mode 100644 index 000000000..3c3e4b52f --- /dev/null +++ b/packages/analytics/example/.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: 5464c5bac742001448fe4fc0597be939379f88ea + channel: stable + +project_type: app diff --git a/packages/analytics/example/README.md b/packages/analytics/example/README.md new file mode 100644 index 000000000..6b3bf8eb5 --- /dev/null +++ b/packages/analytics/example/README.md @@ -0,0 +1,9 @@ +# Analytics Example App + +## How to run + +1. Clone this repository +2. Run `cp .env.example .env` +3. Add your RudderStack and Datadog configurations to `.env` +4. Run `flutter pub get` +5. Run `flutter run` \ No newline at end of file diff --git a/packages/deriv_auth_ui/example/analysis_options.yaml b/packages/analytics/example/analysis_options.yaml similarity index 100% rename from packages/deriv_auth_ui/example/analysis_options.yaml rename to packages/analytics/example/analysis_options.yaml diff --git a/packages/deriv_auth_ui/example/android/.gitignore b/packages/analytics/example/android/.gitignore similarity index 100% rename from packages/deriv_auth_ui/example/android/.gitignore rename to packages/analytics/example/android/.gitignore diff --git a/packages/analytics/example/android/app/build.gradle b/packages/analytics/example/android/app/build.gradle new file mode 100644 index 000000000..806b0464e --- /dev/null +++ b/packages/analytics/example/android/app/build.gradle @@ -0,0 +1,68 @@ +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 21 + + 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.example" + 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-jdk8:1.4.32:$kotlin_version" +} diff --git a/packages/analytics/example/android/app/src/debug/AndroidManifest.xml b/packages/analytics/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..c208884f3 --- /dev/null +++ b/packages/analytics/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/analytics/example/android/app/src/main/AndroidManifest.xml b/packages/analytics/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..3f41384db --- /dev/null +++ b/packages/analytics/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/packages/deriv_auth_ui/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/analytics/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt similarity index 100% rename from packages/deriv_auth_ui/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt rename to packages/analytics/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt diff --git a/packages/deriv_auth_ui/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/analytics/example/android/app/src/main/res/drawable-v21/launch_background.xml similarity index 100% rename from packages/deriv_auth_ui/example/android/app/src/main/res/drawable-v21/launch_background.xml rename to packages/analytics/example/android/app/src/main/res/drawable-v21/launch_background.xml diff --git a/packages/deriv_auth_ui/example/android/app/src/main/res/drawable/launch_background.xml b/packages/analytics/example/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from packages/deriv_auth_ui/example/android/app/src/main/res/drawable/launch_background.xml rename to packages/analytics/example/android/app/src/main/res/drawable/launch_background.xml diff --git a/packages/deriv_auth_ui/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/analytics/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from packages/deriv_auth_ui/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to packages/analytics/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/packages/deriv_auth_ui/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/analytics/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from packages/deriv_auth_ui/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to packages/analytics/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/packages/deriv_auth_ui/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/analytics/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from packages/deriv_auth_ui/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to packages/analytics/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/packages/deriv_auth_ui/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/analytics/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from packages/deriv_auth_ui/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to packages/analytics/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/packages/deriv_auth_ui/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/analytics/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from packages/deriv_auth_ui/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to packages/analytics/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/packages/analytics/example/android/app/src/main/res/values-night/styles.xml b/packages/analytics/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..3db14bb53 --- /dev/null +++ b/packages/analytics/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/analytics/example/android/app/src/main/res/values/styles.xml b/packages/analytics/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..d460d1e92 --- /dev/null +++ b/packages/analytics/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/analytics/example/android/app/src/profile/AndroidManifest.xml b/packages/analytics/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..c208884f3 --- /dev/null +++ b/packages/analytics/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/analytics/example/android/build.gradle b/packages/analytics/example/android/build.gradle new file mode 100644 index 000000000..aa282b69a --- /dev/null +++ b/packages/analytics/example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.7.0' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.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/packages/deriv_auth_ui/example/android/gradle.properties b/packages/analytics/example/android/gradle.properties similarity index 100% rename from packages/deriv_auth_ui/example/android/gradle.properties rename to packages/analytics/example/android/gradle.properties diff --git a/packages/deriv_auth_ui/example/android/settings.gradle b/packages/analytics/example/android/settings.gradle similarity index 100% rename from packages/deriv_auth_ui/example/android/settings.gradle rename to packages/analytics/example/android/settings.gradle diff --git a/packages/deriv_auth_ui/example/ios/.gitignore b/packages/analytics/example/ios/.gitignore similarity index 100% rename from packages/deriv_auth_ui/example/ios/.gitignore rename to packages/analytics/example/ios/.gitignore diff --git a/packages/deriv_auth_ui/example/ios/Flutter/AppFrameworkInfo.plist b/packages/analytics/example/ios/Flutter/AppFrameworkInfo.plist similarity index 100% rename from packages/deriv_auth_ui/example/ios/Flutter/AppFrameworkInfo.plist rename to packages/analytics/example/ios/Flutter/AppFrameworkInfo.plist diff --git a/packages/deriv_auth_ui/example/ios/Flutter/Debug.xcconfig b/packages/analytics/example/ios/Flutter/Debug.xcconfig similarity index 100% rename from packages/deriv_auth_ui/example/ios/Flutter/Debug.xcconfig rename to packages/analytics/example/ios/Flutter/Debug.xcconfig diff --git a/packages/deriv_auth_ui/example/ios/Flutter/Release.xcconfig b/packages/analytics/example/ios/Flutter/Release.xcconfig similarity index 100% rename from packages/deriv_auth_ui/example/ios/Flutter/Release.xcconfig rename to packages/analytics/example/ios/Flutter/Release.xcconfig diff --git a/packages/analytics/example/ios/Podfile b/packages/analytics/example/ios/Podfile new file mode 100644 index 000000000..68df00b22 --- /dev/null +++ b/packages/analytics/example/ios/Podfile @@ -0,0 +1,50 @@ +# 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__)) +end + +# post_install do |installer| +# installer.pods_project.targets.each do |target| +# flutter_additional_ios_build_settings(target) +# end +# end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0' + end + end + end diff --git a/packages/analytics/example/ios/Podfile.lock b/packages/analytics/example/ios/Podfile.lock new file mode 100644 index 000000000..4be589ed5 --- /dev/null +++ b/packages/analytics/example/ios/Podfile.lock @@ -0,0 +1,178 @@ +PODS: + - datadog_flutter_plugin (0.0.1): + - DatadogSDK (= 1.23.0) + - DatadogSDKCrashReporting (= 1.23.0) + - DictionaryCoder (= 1.0.8) + - Flutter + - DatadogSDK (1.23.0) + - DatadogSDKCrashReporting (1.23.0): + - DatadogSDK (= 1.23.0) + - PLCrashReporter (~> 1.11.0) + - DictionaryCoder (1.0.8) + - Firebase/Analytics (10.15.0): + - Firebase/Core + - Firebase/Core (10.15.0): + - Firebase/CoreOnly + - FirebaseAnalytics (~> 10.15.0) + - Firebase/CoreOnly (10.15.0): + - FirebaseCore (= 10.15.0) + - firebase_analytics (10.6.1): + - Firebase/Analytics (= 10.15.0) + - firebase_core + - Flutter + - firebase_core (2.19.0): + - Firebase/CoreOnly (= 10.15.0) + - Flutter + - FirebaseAnalytics (10.15.0): + - FirebaseAnalytics/AdIdSupport (= 10.15.0) + - FirebaseCore (~> 10.0) + - FirebaseInstallations (~> 10.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" + - nanopb (< 2.30910.0, >= 2.30908.0) + - FirebaseAnalytics/AdIdSupport (10.15.0): + - FirebaseCore (~> 10.0) + - FirebaseInstallations (~> 10.0) + - GoogleAppMeasurement (= 10.15.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" + - nanopb (< 2.30910.0, >= 2.30908.0) + - FirebaseCore (10.15.0): + - FirebaseCoreInternal (~> 10.0) + - GoogleUtilities/Environment (~> 7.8) + - GoogleUtilities/Logger (~> 7.8) + - FirebaseCoreInternal (10.16.0): + - "GoogleUtilities/NSData+zlib (~> 7.8)" + - FirebaseInstallations (10.16.0): + - FirebaseCore (~> 10.0) + - GoogleUtilities/Environment (~> 7.8) + - GoogleUtilities/UserDefaults (~> 7.8) + - PromisesObjC (~> 2.1) + - Flutter (1.0.0) + - GoogleAppMeasurement (10.15.0): + - GoogleAppMeasurement/AdIdSupport (= 10.15.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" + - nanopb (< 2.30910.0, >= 2.30908.0) + - GoogleAppMeasurement/AdIdSupport (10.15.0): + - GoogleAppMeasurement/WithoutAdIdSupport (= 10.15.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" + - nanopb (< 2.30910.0, >= 2.30908.0) + - GoogleAppMeasurement/WithoutAdIdSupport (10.15.0): + - GoogleUtilities/AppDelegateSwizzler (~> 7.11) + - GoogleUtilities/MethodSwizzler (~> 7.11) + - GoogleUtilities/Network (~> 7.11) + - "GoogleUtilities/NSData+zlib (~> 7.11)" + - nanopb (< 2.30910.0, >= 2.30908.0) + - GoogleUtilities/AppDelegateSwizzler (7.11.5): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Environment (7.11.5): + - PromisesObjC (< 3.0, >= 1.2) + - GoogleUtilities/Logger (7.11.5): + - GoogleUtilities/Environment + - GoogleUtilities/MethodSwizzler (7.11.5): + - GoogleUtilities/Logger + - GoogleUtilities/Network (7.11.5): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (7.11.5)" + - GoogleUtilities/Reachability (7.11.5): + - GoogleUtilities/Logger + - GoogleUtilities/UserDefaults (7.11.5): + - GoogleUtilities/Logger + - MetricsReporter (1.1.1): + - RSCrashReporter (= 1.0.0) + - RudderKit (= 1.4.0) + - nanopb (2.30909.0): + - nanopb/decode (= 2.30909.0) + - nanopb/encode (= 2.30909.0) + - nanopb/decode (2.30909.0) + - nanopb/encode (2.30909.0) + - PLCrashReporter (1.11.1) + - PromisesObjC (2.3.1) + - RSCrashReporter (1.0.0) + - Rudder (1.23.0): + - MetricsReporter (= 1.1.1) + - rudder_plugin_ios (0.0.1): + - Flutter + - Rudder (< 2.0.0, >= 1.23.0) + - RudderKit (1.4.0) + +DEPENDENCIES: + - datadog_flutter_plugin (from `.symlinks/plugins/datadog_flutter_plugin/ios`) + - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) + - firebase_core (from `.symlinks/plugins/firebase_core/ios`) + - Flutter (from `Flutter`) + - rudder_plugin_ios (from `.symlinks/plugins/rudder_plugin_ios/ios`) + +SPEC REPOS: + trunk: + - DatadogSDK + - DatadogSDKCrashReporting + - DictionaryCoder + - Firebase + - FirebaseAnalytics + - FirebaseCore + - FirebaseCoreInternal + - FirebaseInstallations + - GoogleAppMeasurement + - GoogleUtilities + - MetricsReporter + - nanopb + - PLCrashReporter + - PromisesObjC + - RSCrashReporter + - Rudder + - RudderKit + +EXTERNAL SOURCES: + datadog_flutter_plugin: + :path: ".symlinks/plugins/datadog_flutter_plugin/ios" + firebase_analytics: + :path: ".symlinks/plugins/firebase_analytics/ios" + firebase_core: + :path: ".symlinks/plugins/firebase_core/ios" + Flutter: + :path: Flutter + rudder_plugin_ios: + :path: ".symlinks/plugins/rudder_plugin_ios/ios" + +SPEC CHECKSUMS: + datadog_flutter_plugin: 8f47e24d1953bd48658d44bb557e173534f26ac2 + DatadogSDK: c2519eea53cc46c17d28a90b9784c3b265afdb52 + DatadogSDKCrashReporting: c3d443bad91788a9c4f781e079fa0a43564bedd7 + DictionaryCoder: 5f84fff69f54cb806071538430bdafe04a89d658 + Firebase: 66043bd4579e5b73811f96829c694c7af8d67435 + firebase_analytics: 8b63b894ac3be5ced6a9a4614174754b03377c2f + firebase_core: fd674fcc642742ef7289acea60bd21a1a021bd98 + FirebaseAnalytics: 47cef43728f81a839cf1306576bdd77ffa2eac7e + FirebaseCore: 2cec518b43635f96afe7ac3a9c513e47558abd2e + FirebaseCoreInternal: 26233f705cc4531236818a07ac84d20c333e505a + FirebaseInstallations: b822f91a61f7d1ba763e5ccc9d4f2e6f2ed3b3ee + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + GoogleAppMeasurement: 722db6550d1e6d552b08398b69a975ac61039338 + GoogleUtilities: 13e2c67ede716b8741c7989e26893d151b2b2084 + MetricsReporter: 759631361ffd2b8f0d375b1225c8a631311f6da2 + nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 + PLCrashReporter: 5d2d3967afe0efad61b3048d617e2199a5d1b787 + PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 + RSCrashReporter: e9ccaebd996263f8325a6cbef44ff74aa5f58e30 + Rudder: 125d9dc03e178b35b0f74487aa694afe1a8e6f4f + rudder_plugin_ios: 940ac533f6b3882d7d4389d084aeb2a844c8f4c4 + RudderKit: f272f9872183946452ac94cd7bb2244a71e6ca8f + +PODFILE CHECKSUM: 9eb4a36c9b6b15b2354428bb0f3d2873ffa3b2e6 + +COCOAPODS: 1.13.0 diff --git a/packages/analytics/example/ios/Runner.xcodeproj/project.pbxproj b/packages/analytics/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..86dcea23b --- /dev/null +++ b/packages/analytics/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,552 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 083EBA9E56DE58145841DF61 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52E312214333A7F75C8F00F6 /* Pods_Runner.framework */; }; + 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 */; }; +/* 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 */ + 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 = ""; }; + 482409292D5D6DE86C778A03 /* 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 = ""; }; + 52E312214333A7F75C8F00F6 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.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 = ""; }; + 83DB042B106A298E00E72DAC /* 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; 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 = ""; }; + 9995D6A89D80469EC59877B9 /* 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 */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 083EBA9E56DE58145841DF61 /* Pods_Runner.framework in Frameworks */, + ); + 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 = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + C6A8115C1B6376D40F79AA1D /* Pods */, + ADB2A804837615BFCC32E1C6 /* 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 = ""; + }; + ADB2A804837615BFCC32E1C6 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 52E312214333A7F75C8F00F6 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + C6A8115C1B6376D40F79AA1D /* Pods */ = { + isa = PBXGroup; + children = ( + 83DB042B106A298E00E72DAC /* Pods-Runner.debug.xcconfig */, + 482409292D5D6DE86C778A03 /* Pods-Runner.release.xcconfig */, + 9995D6A89D80469EC59877B9 /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + F034C2F5ACDA6DC94A5B86B5 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 5538329A9D37E08C0AD65335 /* [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 = 1430; + 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 = ( + "${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"; + }; + 5538329A9D37E08C0AD65335 /* [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; + }; + 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"; + }; + F034C2F5ACDA6DC94A5B86B5 /* [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_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 = com.example.example; + 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 = 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 = com.example.example; + 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.example; + 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/packages/deriv_auth_ui/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/analytics/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to packages/analytics/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/packages/deriv_auth_ui/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/analytics/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/analytics/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/deriv_auth_ui/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/analytics/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to packages/analytics/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/packages/analytics/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/analytics/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..a6b826db2 --- /dev/null +++ b/packages/analytics/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/deriv_auth_ui/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/analytics/example/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner.xcworkspace/contents.xcworkspacedata rename to packages/analytics/example/ios/Runner.xcworkspace/contents.xcworkspacedata diff --git a/packages/deriv_auth_ui/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/analytics/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/analytics/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/deriv_auth_ui/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/analytics/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to packages/analytics/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/packages/deriv_auth_ui/example/ios/Runner/AppDelegate.swift b/packages/analytics/example/ios/Runner/AppDelegate.swift similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/AppDelegate.swift rename to packages/analytics/example/ios/Runner/AppDelegate.swift diff --git a/packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png rename to packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000..28c6bf030 Binary files /dev/null and b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000..f091b6b0b Binary files /dev/null and b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000..4cde12118 Binary files /dev/null and b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000..d0ef06e7e Binary files /dev/null and b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000..dcdc2306c Binary files /dev/null and b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000..c8f9ed8f5 Binary files /dev/null and b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000..75b2d164a Binary files /dev/null and b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000..c4df70d39 Binary files /dev/null and b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000..6a84f41e1 Binary files /dev/null and b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000..d0e1f5853 Binary files /dev/null and b/packages/analytics/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to packages/analytics/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/packages/deriv_auth_ui/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/analytics/example/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to packages/analytics/example/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/packages/deriv_auth_ui/example/ios/Runner/Base.lproj/Main.storyboard b/packages/analytics/example/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Base.lproj/Main.storyboard rename to packages/analytics/example/ios/Runner/Base.lproj/Main.storyboard diff --git a/packages/deriv_auth_ui/example/ios/Runner/Info.plist b/packages/analytics/example/ios/Runner/Info.plist similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Info.plist rename to packages/analytics/example/ios/Runner/Info.plist diff --git a/packages/deriv_auth_ui/example/ios/Runner/Runner-Bridging-Header.h b/packages/analytics/example/ios/Runner/Runner-Bridging-Header.h similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Runner-Bridging-Header.h rename to packages/analytics/example/ios/Runner/Runner-Bridging-Header.h diff --git a/packages/analytics/example/lib/main.dart b/packages/analytics/example/lib/main.dart new file mode 100644 index 000000000..7dab5093e --- /dev/null +++ b/packages/analytics/example/lib/main.dart @@ -0,0 +1,79 @@ +import 'package:analytics/sdk/datadog/core/datadog_configuration.dart'; +import 'package:analytics/sdk/datadog/core/enums.dart'; +import 'package:analytics/sdk/datadog/sdk/deriv_datadog.dart'; +import 'package:analytics/sdk/rudderstack/core/rudderstack_configuration.dart'; +import 'package:analytics/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart'; +import 'package:deriv_env/env.dart'; +import 'package:example/pages/rudderstack.dart'; +import 'package:flutter/material.dart'; + +import 'package:example/pages/first_page.dart'; +import 'package:example/pages/second_page.dart'; +import 'package:example/pages/splash_screen.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + await Env().load(); + + DerivDatadogConfiguration configuration = DerivDatadogConfiguration( + applicationId: Env().get( + 'DATADOG_APPLICATION_ID', + ), + clientToken: Env().get( + 'DATADOG_CLIENT_TOKEN', + ), + env: Env().get( + 'DATADOG_ENVIRONMENT', + ), + serviceName: Env().get( + 'DATADOG_SERVICE_NAME', + ), + trackingConsent: TrackingConsent.granted, + ); + + DerivDatadog().setup(configuration); + + DerivRudderstack().setup(RudderstackConfiguration( + dataPlaneUrl: Env().get('RUDDERSTACK_DATA_PLANE_URL'), + writeKey: Env().get( + 'RUDDERSTACK_WRITE_KEY', + ), + )); + + runApp(const App()); +} + +class App extends StatefulWidget { + const App({Key? key}) : super(key: key); + + @override + State createState() => _AppState(); +} + +class _AppState extends State { + @override + void initState() { + super.initState(); + + DerivDatadog().setUserInfo( + id: "0", + name: "Example App User", + email: "example_user@deriv.com", + extraInfo: {}, + ); + } + + // This widget is the root of your application. + @override + Widget build(BuildContext context) => MaterialApp( + navigatorObservers: [DerivDatadog().navigatorObserver], + initialRoute: '/splash_screen', + routes: { + '/splash_screen': (context) => const SplashScreen(), + '/': (context) => const FirstPage(), + '/second': (context) => const SecondPage(), + '/rudderstack': (context) => const RudderStack(), + }, + ); +} diff --git a/packages/analytics/example/lib/pages/first_page.dart b/packages/analytics/example/lib/pages/first_page.dart new file mode 100644 index 000000000..9cd47ea61 --- /dev/null +++ b/packages/analytics/example/lib/pages/first_page.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +class FirstPage extends StatefulWidget { + const FirstPage({Key? key}) : super(key: key); + + @override + State createState() => _FirstPageState(); +} + +class _FirstPageState extends State { + @override + Widget build(BuildContext context) => Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text("DataDog Test App 1"), + TextButton( + onPressed: () { + Navigator.pushNamed(context, "/second"); + }, + child: const Text("Test Button 1"), + ), + ], + ), + ), + ); +} diff --git a/packages/analytics/example/lib/pages/rudderstack.dart b/packages/analytics/example/lib/pages/rudderstack.dart new file mode 100644 index 000000000..26749ebe7 --- /dev/null +++ b/packages/analytics/example/lib/pages/rudderstack.dart @@ -0,0 +1,171 @@ +import 'package:analytics/sdk/rudderstack/models/user_info.dart'; +import 'package:analytics/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart'; +import 'package:flutter/material.dart'; + +class RudderStack extends StatefulWidget { + // ignore: public_member_api_docs + const RudderStack({super.key}); + + @override + _RudderStackState createState() => _RudderStackState(); +} + +class _RudderStackState extends State { + bool _enabled = false; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) => MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Rudderstack example app'), + //back button + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => Navigator.pop(context, false), + ), + ), + body: SingleChildScrollView( + child: Builder( + builder: (BuildContext context) => Column( + children: [ + _enableController(context), + const Divider(), + GridView.count( + shrinkWrap: true, + crossAxisCount: 2, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + padding: const EdgeInsets.all(16), + children: _eventsList(context), + ), + ], + ), + ), + ), + ), + ); + + List _eventsList(BuildContext context) => [ + InkWell( + child: Container( + padding: const EdgeInsets.all(8), + color: Colors.teal[100], + child: const Center( + child: Text( + 'Identify', + style: TextStyle(fontSize: 18), + )), + ), + onTap: () async { + await DerivRudderstack() + .setContext(token: 'xxx-xxxx-xxxx-xxxxx-xxxx-test'); + final bool result = await DerivRudderstack() + .identify(userInfo: UserInfo(userId: 988)); + _showSnackBar(context, result); + }, + ), + InkWell( + child: Container( + padding: const EdgeInsets.all(8), + color: Colors.blue[100], + child: const Center( + child: Text( + 'Track', + style: TextStyle(fontSize: 18), + )), + ), + onTap: () async { + final bool result = await DerivRudderstack() + .track(eventName: 'Application Opened'); + + _showSnackBar(context, result); + }), + InkWell( + child: Container( + padding: const EdgeInsets.all(8), + color: Colors.orange[100], + child: const Center( + child: Text( + 'Screen', + style: TextStyle(fontSize: 18), + )), + ), + onTap: () async { + final bool result = + await DerivRudderstack().screen(screenName: 'main'); + + _showSnackBar(context, result); + }), + InkWell( + child: Container( + padding: const EdgeInsets.all(8), + color: Colors.indigo[100], + child: const Center( + child: Text( + 'group', + style: TextStyle(fontSize: 18), + )), + ), + onTap: () async { + final bool result = + await DerivRudderstack().group(groupId: 'Group-id-test'); + _showSnackBar(context, result); + }), + InkWell( + child: Container( + padding: const EdgeInsets.all(8), + color: Colors.purple[100], + child: const Center( + child: Text( + 'alias', + style: TextStyle(fontSize: 18), + )), + ), + onTap: () async { + final bool result = + await DerivRudderstack().alias(alias: 'Alias-test'); + _showSnackBar(context, result); + }), + InkWell( + child: Container( + padding: const EdgeInsets.all(8), + color: Colors.deepOrange[100], + child: const Center( + child: Text( + 'reset', + style: TextStyle(fontSize: 18), + )), + ), + onTap: () async { + final bool result = await DerivRudderstack().reset(); + _showSnackBar(context, result); + }), + ]; + + Widget _enableController(BuildContext context) => SwitchListTile( + title: const Text('Enable RudderStack'), + value: _enabled, + onChanged: (bool newValue) async { + setState(() => _enabled = newValue); + + bool result; + if (_enabled) { + result = await DerivRudderstack().enable(); + } else { + result = await DerivRudderstack().disable(); + } + + _showSnackBar(context, result); + }, + ); + + void _showSnackBar(BuildContext context, bool success) => + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(success ? 'Success' : 'Failure')), + ); +} diff --git a/packages/analytics/example/lib/pages/second_page.dart b/packages/analytics/example/lib/pages/second_page.dart new file mode 100644 index 000000000..572587e89 --- /dev/null +++ b/packages/analytics/example/lib/pages/second_page.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; + +class SecondPage extends StatefulWidget { + const SecondPage({Key? key}) : super(key: key); + + @override + State createState() => _SecondPageState(); +} + +class _SecondPageState extends State { + int count = 0; + + @override + Widget build(BuildContext context) => WillPopScope( + onWillPop: () async { + setState(() => count = 0); + + return true; + }, + child: Scaffold( + appBar: AppBar( + elevation: 0, + backgroundColor: Colors.transparent, + iconTheme: const IconThemeData(color: Colors.black), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text("DataDog Test App 2"), + if (count > 0 && count < 5) + const Text("This button doesn't do anything."), + if (count >= 5 && count < 10) + const Text( + "STOP CLICKING THE BUTTON IT DOESN'T DO ANYTHING!"), + if (count >= 10) + const Text( + "There is an error! Happy now?!", + style: TextStyle(color: Colors.red), + ), + TextButton( + onPressed: () { + setState(() => count++); + + Navigator.pushNamed(context, "/rudderstack"); + }, + child: const Text("RudderStack"), + ), + ], + ), + ), + ), + ); +} diff --git a/packages/analytics/example/lib/pages/splash_screen.dart b/packages/analytics/example/lib/pages/splash_screen.dart new file mode 100644 index 000000000..9417ad2e5 --- /dev/null +++ b/packages/analytics/example/lib/pages/splash_screen.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; + +class SplashScreen extends StatefulWidget { + const SplashScreen({Key? key}) : super(key: key); + + @override + State createState() => _SplashScreenState(); +} + +class _SplashScreenState extends State { + @override + void initState() { + super.initState(); + + Future.delayed( + const Duration(seconds: 2), + () => Navigator.pushNamed(context, "/"), + ); + } + + @override + Widget build(BuildContext context) => const Scaffold( + body: SizedBox( + height: double.infinity, + width: double.infinity, + child: Center( + child: Hero( + tag: "datadog_logo", + child: Text( + "Analytics Test App", + ), + ), + ), + ), + ); +} diff --git a/packages/analytics/example/pubspec.yaml b/packages/analytics/example/pubspec.yaml new file mode 100644 index 000000000..2a23fade7 --- /dev/null +++ b/packages/analytics/example/pubspec.yaml @@ -0,0 +1,35 @@ +name: example +description: A new Flutter project. + +publish_to: "none" + +version: 1.0.0+1 + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: "3.10.2" + +dependencies: + flutter: + sdk: flutter + deriv_env: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_env + ref: dev + + analytics: + path: ../ + + cupertino_icons: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + + flutter_lints: ^1.0.0 + +flutter: + uses-material-design: true + assets: + - .env diff --git a/packages/analytics/lib/analytics.dart b/packages/analytics/lib/analytics.dart deleted file mode 100644 index 1aade9933..000000000 --- a/packages/analytics/lib/analytics.dart +++ /dev/null @@ -1,142 +0,0 @@ -import 'dart:io'; - -import 'package:firebase_analytics/firebase_analytics.dart'; -import 'package:firebase_core/firebase_core.dart'; -import 'package:flutter/material.dart'; - -import 'package:deriv_rudderstack/deriv_rudderstack.dart'; - -import 'analytics_route_observer.dart'; - -/// Class that collects and send analytical information to `Firebase` and -/// `RudderStack`. -class Analytics { - /// Initialises - factory Analytics() => _instance; - - Analytics._internal(); - - /// A public instance of the class [Analytics]. - static final Analytics _instance = Analytics._internal(); - - /// Contains ignored routes/screen names. - List ignoredRoutes = []; - - late FirebaseAnalytics _firebaseAnalytics; - - /// An instance of custom route observer created for analytics. - late AnalyticsRouteObserver observer; - - /// Initialises the `Analytics`. - /// Sets the device-token to `RudderStack`. - /// bool [isEnabled] enables or disables "Analytics". - Future init({ - required bool isEnabled, - required FirebaseApp firebaseApp, - }) async { - _firebaseAnalytics = FirebaseAnalytics.instanceFor(app: firebaseApp); - observer = AnalyticsRouteObserver(onNewRoute: _newRouteHandler); - - // Enable or disable the analytics on this device. - await _firebaseAnalytics.setAnalyticsCollectionEnabled(isEnabled); - - // For ios we have to manually setup the rudderStack as it's not get initialized with register method. - if (Platform.isIOS) { - await setupRudderStackForIos(); - } - - isEnabled - ? await DerivRudderstack().enable() - : await DerivRudderstack().disable(); - } - - /// Captures `screen_view` event on route changes. - void _newRouteHandler(PageRoute route) { - setCurrentScreen( - screenName: route.settings.name ?? '', - // ignore: avoid_as - properties: route.settings.arguments as Map? ?? - {}, - ); - } - - /// Captures `app_open` event when the app is opened. - void logAppOpened() { - _firebaseAnalytics.logAppOpen(); - - DerivRudderstack().track(eventName: 'Application Opened'); - } - - /// Captures `Application Backgrounded` event when the app goes to background. - void logAppBackgrounded() { - DerivRudderstack().track(eventName: 'Application Backgrounded'); - } - - /// Captures `Application Crashed` event when the app is crashed. - void logAppCrashed() { - DerivRudderstack().track(eventName: 'Application Crashed'); - } - - /// Captures information about current screen in use. - void setCurrentScreen({ - required String screenName, - Map properties = const {}, - }) { - if (ignoredRoutes.contains(screenName)) { - return; - } - _firebaseAnalytics.setCurrentScreen(screenName: screenName); - - DerivRudderstack().screen( - screenName: screenName, - properties: properties, - ); - } - - /// Captures `login` event upon a successful user log in. - Future logLoginEvent( - {required String deviceToken, required int userId}) async { - await _setFirebaseUserId(userId.toString()); - await _firebaseAnalytics.logLogin(); - - await _setRudderStackDeviceToken(deviceToken); - - await DerivRudderstack().identify(userId: userId.toString()); - } - - /// Captures `logout` event when the user logs out. - void logLogoutEvent() { - _firebaseAnalytics.logEvent(name: 'logout'); - } - - /// Sets the device-token to `RudderStack`. - Future _setRudderStackDeviceToken(String deviceToken) => - DerivRudderstack().setContext(token: deviceToken); - - /// Sets the user id for `Firebase`. - Future _setFirebaseUserId(String userId) => - _firebaseAnalytics.setUserId(id: userId); - - /// Logs push token. - Future logPushToken(String deviceToken) async { - await _setRudderStackDeviceToken(deviceToken); - } - - /// This method initialize the rudderStack client for ios. - Future setupRudderStackForIos() async { - await DerivRudderstack().setup(); - } - - /// Should be called at logout to clear up current `RudderStack` data. - Future reset() async => DerivRudderstack().reset(); - - /// Logs custom events to `Firebase`. - Future logToFirebase({ - required String name, - Map? params, - }) => - _firebaseAnalytics.logEvent( - name: name, - parameters: params, - ); -} diff --git a/packages/analytics/lib/core/logger.dart b/packages/analytics/lib/core/logger.dart new file mode 100644 index 000000000..20e366259 --- /dev/null +++ b/packages/analytics/lib/core/logger.dart @@ -0,0 +1,15 @@ +import 'dart:developer' as logger_dev; + +/// Logger interface for logging errors or messages +abstract class Logger { + /// Logs a [message]. + void log(String message); +} + +/// Concrete implementation of [Logger]. +class ConsoleLogger implements Logger { + @override + void log(String message) { + logger_dev.log(message); + } +} diff --git a/packages/analytics/lib/sdk/base_analytics.dart b/packages/analytics/lib/sdk/base_analytics.dart new file mode 100644 index 000000000..18041e9ca --- /dev/null +++ b/packages/analytics/lib/sdk/base_analytics.dart @@ -0,0 +1,11 @@ +import 'package:analytics/sdk/base_analytics_configuration.dart'; +import 'package:flutter/widgets.dart'; + +/// Define the analytics interface +abstract class BaseAnalytics { + /// The [NavigatorObserver] instances used for tracking navigation events. + NavigatorObserver get navigatorObserver; + + /// Sets up the analytics client. + Future setup(T configuration); +} diff --git a/packages/analytics/lib/sdk/base_analytics_configuration.dart b/packages/analytics/lib/sdk/base_analytics_configuration.dart new file mode 100644 index 000000000..79438847f --- /dev/null +++ b/packages/analytics/lib/sdk/base_analytics_configuration.dart @@ -0,0 +1,2 @@ +/// Abstract base class that defines the structure for all analytics configurations. +abstract class BaseAnalyticsConfiguration {} diff --git a/packages/analytics/lib/sdk/datadog/core/datadog_configuration.dart b/packages/analytics/lib/sdk/datadog/core/datadog_configuration.dart new file mode 100644 index 000000000..66f5a8d20 --- /dev/null +++ b/packages/analytics/lib/sdk/datadog/core/datadog_configuration.dart @@ -0,0 +1,45 @@ +import 'package:analytics/sdk/base_analytics_configuration.dart'; +import 'package:analytics/sdk/datadog/core/enums.dart'; + +/// A class to define the configuration options for the [DerivDatadog]. +class DerivDatadogConfiguration implements BaseAnalyticsConfiguration { + /// Creates a new [DerivDatadogConfiguration] instance with the given options. + const DerivDatadogConfiguration({ + required this.applicationId, + required this.clientToken, + required this.env, + required this.trackingConsent, + this.site = DatadogSite.us1, + this.nativeCrashReportEnabled = true, + this.sessionSamplingRate = 100, + this.tracingSamplingRate = 100, + this.serviceName, + }); + + /// The application id used to identify the app in the `Datadog` dashboard. + final String applicationId; + + /// The client token used to authenticate with the `Datadog API`. + final String clientToken; + + /// The environment in which the SDK is running. + final String env; + + /// The `Datadog` site to use. + final DatadogSite? site; + + /// The user's tracking consent status. + final TrackingConsent trackingConsent; + + /// Whether native crash reporting is enabled. + final bool? nativeCrashReportEnabled; + + /// The sampling rate for sessions. + final double? sessionSamplingRate; + + /// The sampling rate for resource traces. + final double? tracingSamplingRate; + + /// The service name for this application + final String? serviceName; +} diff --git a/packages/analytics/lib/sdk/datadog/core/enums.dart b/packages/analytics/lib/sdk/datadog/core/enums.dart new file mode 100644 index 000000000..20f4d4e46 --- /dev/null +++ b/packages/analytics/lib/sdk/datadog/core/enums.dart @@ -0,0 +1,108 @@ +import 'package:datadog_flutter_plugin/datadog_flutter_plugin.dart' as datadog; + +/// An enum to represent the user's tracking consent status, used in [DatadogSdkConfig]. +enum TrackingConsent { + /// user has granted tracking consent + granted, + + /// user has not granted tracking consent + notGranted, + + /// user's tracking consent is pending + pending, +} + +/// A Extension on [TrackingConsent]. +extension TrackingConsentExtension on TrackingConsent { + /// The consent getter method is an extension to the [datadog.TrackingConsent] + /// enum that returns the corresponding [datadog.TrackingConsent] value for + /// the given [TrackingConsent] value. + datadog.TrackingConsent get consent => trackingConsentMapper[this]!; +} + +/// A mapper class to map [TrackingConsent] to [datadog.TrackingConsent]. +Map trackingConsentMapper = + { + TrackingConsent.granted: datadog.TrackingConsent.granted, + TrackingConsent.notGranted: datadog.TrackingConsent.notGranted, + TrackingConsent.pending: datadog.TrackingConsent.pending, +}; + +/// Determines the server for uploading RUM events. +enum DatadogSite { + /// US based servers. Sends RUM events to + /// [app.datadoghq.com](https://app.datadoghq.com/). + us1, + + /// US based servers. Sends RUM events to + /// [us3.datadoghq.com](https://us3.datadoghq.com/). + us3, + + /// US based servers. Sends RUM events to + /// [us5.datadoghq.com](https://us5.datadoghq.com/). + us5, + + /// Europe based servers. Sends RUM events to + /// [app.datadoghq.eu](https://app.datadoghq.eu/). + eu1, + + /// US based servers, FedRAMP compatible. Sends RUM events to + /// [app.ddog-gov.com](https://app.ddog-gov.com/). + us1Fed, + + /// Asia baesd servers. Sends data to + /// [ap1.datadoghq.com](https://ap1.datadoghq.com). + ap1, +} + +/// A Extension on [DatadogSite]. +extension DatadogSiteExtension on DatadogSite { + /// The consent getter method is an extension to the [datadog.DatadogSite] + /// enum that returns the corresponding [datadog.DatadogSite] value for + /// the given [DatadogSite] value. + datadog.DatadogSite get site => siteMapper[this] ?? datadog.DatadogSite.us1; +} + +/// A mapper class to map [DatadogSite] to [datadog.DatadogSite]. +Map siteMapper = + { + DatadogSite.us1: datadog.DatadogSite.us1, + DatadogSite.us3: datadog.DatadogSite.us3, + DatadogSite.us5: datadog.DatadogSite.us5, + DatadogSite.eu1: datadog.DatadogSite.eu1, + DatadogSite.us1Fed: datadog.DatadogSite.us1Fed, + DatadogSite.ap1: datadog.DatadogSite.ap1, +}; + +/// An enum to represent the user's tracking consent status, used in [DatadogSdkConfig]. +enum RumUserActionType { + /// tap action + tap, + + /// scroll action + scroll, + + /// swipe action + swipe, + + /// custom action + custom +} + +/// A mapper class to map [RumUserActionType] to [datadog.RumUserActionType]. +Map rumUserActionTypeMapper = + { + RumUserActionType.tap: datadog.RumUserActionType.tap, + RumUserActionType.scroll: datadog.RumUserActionType.scroll, + RumUserActionType.swipe: datadog.RumUserActionType.swipe, + RumUserActionType.custom: datadog.RumUserActionType.custom, +}; + +/// Extension on [RumUserActionType]. +extension RumUserActionTypeExtension on RumUserActionType { + /// The consent getter method is an extension to the [datadog.RumUserActionType] + /// enum that returns the corresponding [datadog.RumUserActionType] value for + /// the given [RumUserActionType] value. + datadog.RumUserActionType get rumUserActionType => + rumUserActionTypeMapper[this]!; +} diff --git a/packages/analytics/lib/sdk/datadog/sdk/deriv_datadog.dart b/packages/analytics/lib/sdk/datadog/sdk/deriv_datadog.dart new file mode 100644 index 000000000..e3d91efff --- /dev/null +++ b/packages/analytics/lib/sdk/datadog/sdk/deriv_datadog.dart @@ -0,0 +1,109 @@ +import 'dart:ui'; + +import 'package:analytics/sdk/base_analytics.dart'; +import 'package:analytics/sdk/datadog/core/datadog_configuration.dart'; +import 'package:analytics/sdk/datadog/core/enums.dart'; +import 'package:datadog_flutter_plugin/datadog_flutter_plugin.dart' as datadog; +import 'package:datadog_flutter_plugin/datadog_internal.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; + +/// Implement Datadog +class DerivDatadog implements BaseAnalytics { + /// Returns the singleton instance of the [DerivDatadog]. + factory DerivDatadog() => _instance ??= DerivDatadog._(); + + DerivDatadog._(); + + static DerivDatadog? _instance; + + datadog.DatadogSdk _datadogSDK = datadog.DatadogSdk.instance; + + /// Returns navigation observer for the [Datadog]. + datadog.DatadogNavigationObserver get _navigationObserver => + datadog.DatadogNavigationObserver(datadogSdk: _datadogSDK); + + @override + NavigatorObserver get navigatorObserver => _navigationObserver; + + /// Sets the [rudderClient] instance in the case of testing. + @visibleForTesting + // ignore: use_setters_to_change_properties + void setDatadogSdk(datadog.DatadogSdk rudderClient) { + _datadogSDK = rudderClient; + } + + @override + Future setup(DerivDatadogConfiguration configuration) async { + try { + final datadog.RumConfiguration rumConfiguration = + datadog.RumConfiguration( + applicationId: configuration.applicationId, + sessionSamplingRate: configuration.sessionSamplingRate ?? 100, + tracingSamplingRate: configuration.tracingSamplingRate ?? 100, + ); + + final datadog.DdSdkConfiguration datadogConfiguration = + datadog.DdSdkConfiguration( + clientToken: configuration.clientToken, + env: configuration.env, + serviceName: configuration.serviceName, + site: configuration.site?.site ?? DatadogSite.us1.site, + trackingConsent: configuration.trackingConsent.consent, + nativeCrashReportEnabled: + configuration.nativeCrashReportEnabled ?? true, + loggingConfiguration: datadog.LoggingConfiguration(), + rumConfiguration: rumConfiguration, + ); + + final FlutterExceptionHandler? originalOnError = FlutterError.onError; + FlutterError.onError = (FlutterErrorDetails details) { + _datadogSDK.rum?.handleFlutterError(details); + originalOnError?.call(details); + }; + final ErrorCallback? platformOriginalOnError = + PlatformDispatcher.instance.onError; + PlatformDispatcher.instance.onError = (Object e, StackTrace st) { + _datadogSDK.rum?.addErrorInfo( + e.toString(), + datadog.RumErrorSource.source, + stackTrace: st, + ); + return platformOriginalOnError?.call(e, st) ?? false; + }; + + await _datadogSDK.initialize(datadogConfiguration); + _datadogSDK.updateConfigurationInfo( + LateConfigurationProperty.trackErrors, true); + return true; + } on Exception { + return false; + } + } + + /// Sets the user information for the current session. + void setUserInfo({ + String? id, + String? name, + String? email, + Map extraInfo = const {}, + }) => + _datadogSDK.setUserInfo( + id: id, + name: name, + email: email, + extraInfo: extraInfo, + ); + + /// Logs Custom Event + void logEvent({ + required RumUserActionType type, + required String name, + Map attributes = const {}, + }) => + _datadogSDK.rum?.addUserAction( + type.rumUserActionType, + name, + attributes, + ); +} diff --git a/packages/analytics/lib/sdk/firebase/core/firebase_configuration.dart b/packages/analytics/lib/sdk/firebase/core/firebase_configuration.dart new file mode 100644 index 000000000..7862d7642 --- /dev/null +++ b/packages/analytics/lib/sdk/firebase/core/firebase_configuration.dart @@ -0,0 +1,12 @@ +import 'package:analytics/sdk/base_analytics_configuration.dart'; + +/// A class to define the configuration options for [FirebaseConfiguration]. +class FirebaseConfiguration implements BaseAnalyticsConfiguration { + /// Creates a new [FirebaseConfiguration] instance with the given options. + const FirebaseConfiguration({required this.isAnalyticsCollectionEnabled}); + + /// Sets whether analytics collection is enabled for this app on this device. + /// + /// This setting is persisted across app sessions. + final bool isAnalyticsCollectionEnabled; +} diff --git a/packages/analytics/lib/sdk/firebase/sdk/deriv_firebase_analytics.dart b/packages/analytics/lib/sdk/firebase/sdk/deriv_firebase_analytics.dart new file mode 100644 index 000000000..784d9b04f --- /dev/null +++ b/packages/analytics/lib/sdk/firebase/sdk/deriv_firebase_analytics.dart @@ -0,0 +1,107 @@ +import 'dart:async'; + +import 'package:analytics/analytics_route_observer.dart'; +import 'package:analytics/core/logger.dart'; +import 'package:analytics/sdk/base_analytics.dart'; +import 'package:analytics/sdk/firebase/core/firebase_configuration.dart'; +import 'package:firebase_analytics/firebase_analytics.dart'; +import 'package:flutter/widgets.dart'; + +/// A wrapper around Firebase Flutter SDK. +class DerivFirebaseAnalytics implements BaseAnalytics { + /// Creates a new [DerivFirebaseAnalytics] instance. + factory DerivFirebaseAnalytics(FirebaseAnalytics firebaseAnalytics) { + _instance._firebaseAnalytics = firebaseAnalytics; + return _instance; + } + + DerivFirebaseAnalytics._internal(); + + static final DerivFirebaseAnalytics _instance = + DerivFirebaseAnalytics._internal(); + + @override + NavigatorObserver get navigatorObserver => + AnalyticsRouteObserver(onNewRoute: (Route route) { + if (route.settings.name != null) { + setCurrentScreen( + screenName: route.settings.name!, + ); + } + }); + + late FirebaseAnalytics _firebaseAnalytics; + + final Logger _logger = ConsoleLogger(); + + /// Sets up the Firebase client. + /// + /// This method must be called before any other method. + @override + Future setup(FirebaseConfiguration configuration) => _execute(() async { + await _firebaseAnalytics.setAnalyticsCollectionEnabled( + configuration.isAnalyticsCollectionEnabled); + }); + + /// Logs the passed [screenName] to firebase analytics. + /// [screenName] is required and the method returns true if logging completed + /// Successfully, otherwise, a false is returned. + Future setCurrentScreen({required String screenName}) async => + _execute(() async { + await _firebaseAnalytics.logScreenView(screenName: screenName); + }); + + /// Logs the standard app open event. + Future logAppOpen() async => _execute(() async { + await _firebaseAnalytics.logAppOpen(); + }); + + /// Logs the standard login event. + /// + /// Apps with a login feature can report this event to signify that a user has logged in. + Future logLogin({ + String? loginMethod, + }) async => + _execute(() async { + await _firebaseAnalytics.logLogin(loginMethod: loginMethod); + }); + + /// Logs a custom Flutter Analytics event with the given [name] and event [parameters]. + Future logEvent({ + required String name, + Map? parameters, + }) async => + _execute(() async { + await _firebaseAnalytics.logEvent(name: name, parameters: parameters); + }); + + /// Sets the user ID property. + /// + /// Setting a null [id] removes the user id. + Future setUserId({ + required String id, + }) async => + _execute(() async { + await _firebaseAnalytics.setUserId(id: id); + }); + + /// Sets a user property to a given value. + Future setUserProperty({ + required String name, + required String value, + }) async => + _execute(() async { + await _firebaseAnalytics.setUserProperty(name: name, value: value); + }); + + /// Executes [action] and logs errors, if any. + Future _execute(Function action) async { + try { + action(); + return true; + } on Exception catch (e) { + _logger.log('DerivFirebase: $e'); + return false; + } + } +} diff --git a/packages/analytics/lib/sdk/rudderstack/core/rudderstack_configuration.dart b/packages/analytics/lib/sdk/rudderstack/core/rudderstack_configuration.dart new file mode 100644 index 000000000..4465aef12 --- /dev/null +++ b/packages/analytics/lib/sdk/rudderstack/core/rudderstack_configuration.dart @@ -0,0 +1,20 @@ +import 'package:analytics/sdk/base_analytics_configuration.dart'; + +/// A class to define the configuration options for [RudderstackConfiguration]. +class RudderstackConfiguration implements BaseAnalyticsConfiguration { + /// Creates a new [RudderstackConfiguration] instance with the given options. + const RudderstackConfiguration({ + required this.dataPlaneUrl, + required this.writeKey, + this.debugEnabled = false, + }); + + /// The data plane url used to identify the app in the `Rudderstack` dashboard. + final String dataPlaneUrl; + + /// The write key used to authenticate with the `Rudderstack API`. + final String writeKey; + + /// Whether to enable debug mode or not. + final bool debugEnabled; +} diff --git a/packages/analytics/lib/sdk/rudderstack/events/rudderstack_events.dart b/packages/analytics/lib/sdk/rudderstack/events/rudderstack_events.dart new file mode 100644 index 000000000..c1d477940 --- /dev/null +++ b/packages/analytics/lib/sdk/rudderstack/events/rudderstack_events.dart @@ -0,0 +1,640 @@ +import 'package:analytics/sdk/rudderstack/core/rudderstack_configuration.dart'; +import 'package:analytics/sdk/rudderstack/models/user_info.dart'; +import 'package:analytics/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart'; + +/// Class which hold events which should be monitored. +class DerivRudderstackEvents { + /// Creates a new [DerivRudderstackEvents] instance. + factory DerivRudderstackEvents() => _instance; + + DerivRudderstackEvents._internal(); + + static final DerivRudderstackEvents _instance = + DerivRudderstackEvents._internal(); + + ///Set ups Rudderstack connection. + void setup({required String dataPlaneUrl, required String writeKey}) { + DerivRudderstack().setup( + RudderstackConfiguration(dataPlaneUrl: dataPlaneUrl, writeKey: writeKey), + ); + } + + // --------- common_events --------------- + /// Tracks system error has happened, + /// like no connection to the server and etc. + void logError(String error) { + DerivRudderstack().track( + eventName: 'error', + properties: { + 'action': 'other_error', + 'error_message': error, + 'form_source': 'mobile_derivgo', + 'form_name': 'common_events_derivgo' + }, + ); + } + + /// Tracks userId. + void logIdentifyUser({required UserInfo userInfo}) { + DerivRudderstack().identify(userInfo: userInfo); + } + + /// Tracks system error has happened, + /// like no connection to the server and etc. + void logAccountInfo({ + String? accountType, + String? countryResidence, + String? language, + }) { + DerivRudderstack().track( + eventName: 'account_info', + properties: { + 'action': 'account_info', + 'account_type': '$accountType', + 'country_residence': '$countryResidence', + 'language': '$language', + 'form_source': 'mobile_derivgo', + 'form_name': 'common_events_derivgo' + }, + ); + } + + // --------- ce_virtual_signup_form --------------- + /// Captures app_open event when the app is opened. + void logSignupOpened() { + DerivRudderstack().track( + eventName: 'ce_virtual_signup_form', + properties: { + 'action': 'open', + 'form_source': 'mobile_derivgo', + 'form_name': 'virtual_signup_derivgo' + }, + ); + } + + /// Captures user tap on Log in button on sign up screen. + void logUserTappedLoginButton() { + DerivRudderstack().track( + eventName: 'ce_virtual_signup_form', + properties: { + 'action': 'go_to_login', + 'form_source': 'mobile_derivgo', + 'form_name': 'virtual_signup_derivgo' + }, + ); + } + + /// Captures user tap on Get free account button on sign up screen. + void logAppGetFreeAccount(String slideName) { + DerivRudderstack().track( + eventName: 'ce_virtual_signup_form', + properties: { + 'action': 'get_free_account', + 'form_source': 'mobile_derivgo', + 'getstarted_slide_name': + '${slideName.substring(slideName.indexOf('.') + 1, slideName.length)}', + 'form_name': 'virtual_signup_derivgo' + }, + ); + } + + /// Tracks when user turns on or off Refferal toggle switcher. + void logReferralToggleSwitched() { + DerivRudderstack().track( + eventName: 'ce_virtual_signup_form', + properties: { + 'action': 'tap_referral_toggle', + 'form_source': 'mobile_derivgo', + 'form_name': 'virtual_signup_derivgo' + }, + ); + } + + /// Tracks when user gets Invalid referral code pop up with Try again button. + void logTryAgainReferralCode() { + DerivRudderstack().track( + eventName: 'ce_virtual_signup_form', + properties: { + 'action': 'try_again_referral_code', + 'form_source': 'mobile_derivgo', + 'form_name': 'virtual_signup_derivgo' + }, + ); + } + + /// Tracks when Email confirmation is sent to a user. + void logEmailConfirmationSent() { + DerivRudderstack().track( + eventName: 'ce_virtual_signup_form', + properties: { + 'action': 'email_confirmation_sent', + 'signup_provider': 'email', + 'form_source': 'mobile_derivgo', + 'form_name': 'virtual_signup_derivgo' + }, + ); + } + + /// Tracks when user land on Successfull email verification screen. + void logEmailConfirmed() { + DerivRudderstack().track( + eventName: 'ce_virtual_signup_form', + properties: { + 'action': 'email_confirmed', + 'signup_provider': 'email', + 'form_source': 'mobile_derivgo', + 'form_name': 'virtual_signup_derivgo' + }, + ); + } + + /// Tracks when user taps Continue button on Successfull email verification screen. + void logSignupContinued() { + DerivRudderstack().track( + eventName: 'ce_virtual_signup_form', + properties: { + 'action': 'signup_continued', + 'signup_provider': 'email', + 'form_source': 'mobile_derivgo', + 'form_name': 'virtual_signup_derivgo' + }, + ); + } + + /// Tracks when user lands on Country selection screen. + void logCountrySelectionPageOpened() { + DerivRudderstack().track( + eventName: 'ce_virtual_signup_form', + properties: { + 'action': 'country_selection_screen_opened', + 'signup_provider': 'email', + 'form_source': 'mobile_derivgo', + 'form_name': 'virtual_signup_derivgo' + }, + ); + } + + /// Tracks when user lands on Create password page while creating demo account. + void logSetPasswordPageOpened() { + DerivRudderstack().track( + eventName: 'ce_virtual_signup_form', + properties: { + 'action': 'password_screen_opened', + 'signup_provider': 'email', + 'form_source': 'mobile_derivgo', + 'form_name': 'virtual_signup_derivgo' + }, + ); + } + + /// Tracks when user's sign up is finished. + void logSignUpDone(String signupProvider, [int? userId]) { + DerivRudderstack().identify(userInfo: UserInfo(userId: userId ?? 0)); + DerivRudderstack().track( + eventName: 'ce_virtual_signup_form', + properties: { + 'action': 'signup_done', + 'signup_provider': '$signupProvider', + 'form_source': 'mobile_derivgo', + 'form_name': 'virtual_signup_derivgo' + }, + ); + } + + /// Tracks when user taps 'Create free demo account' or social log in button. + void logSignUpPageAction( + String signupProvider, [ + bool? isToggleOn, + String? referralCode, + ]) { + DerivRudderstack().track( + eventName: 'ce_virtual_signup_form', + properties: { + 'action': 'started', + 'signup_provider': '$signupProvider', + 'referral_toggle_mode': '${isToggleOn ?? false} ', + 'referral_code': '$referralCode', + 'form_source': 'mobile_derivgo', + 'form_name': 'virtual_signup_derivgo' + }, + ); + } + + /// Tracks any error is happening and shown to the user (validation, API communication, social providers errors). + void logSignUpFlowError(String? errorText, [String? signupProvider]) { + DerivRudderstack().track( + eventName: 'ce_virtual_signup_form', + properties: { + 'action': 'signup_flow_error', + 'signup_provider': '$signupProvider', + 'error_message': '$errorText', + 'form_source': 'mobile_derivgo', + 'form_name': 'virtual_signup_derivgo' + }, + ); + } + + // --------- ce_real_account_signup_form --------------- + /// Tracks when the real signup form opened. + void logOpenRealSignUp() { + DerivRudderstack().track( + eventName: 'ce_real_account_signup_form', + properties: { + 'action': 'open', + 'form_source': 'mobile_derivgo', + 'form_name': 'real_account_signup_derivgo' + }, + ); + } + + /// Tracks when the user presses the next button + /// and the step is successfully passed. + void logStepPassedRealSignUp( + [String? stepNum, + String? stepCodename, + Map? userChoice]) { + DerivRudderstack().track( + eventName: 'ce_real_account_signup_form', + properties: { + 'action': 'step_passed', + 'step_codename': stepCodename, + 'step_num': stepNum, + 'user_choice': userChoice, + 'form_source': 'mobile_derivgo', + 'form_name': 'real_account_signup_derivgo' + }, + ); + } + + /// Tracks when user tap the 'previous' button and go to previous screen. + void logStepBackRealSignUp(String stepCodeName) { + DerivRudderstack().track( + eventName: 'ce_real_account_signup_form', + properties: { + 'action': 'step_back', + 'form_source': 'mobile_derivgo', + 'form_name': 'real_account_signup_derivgo' + }, + ); + } + + /// Tracks if the user presses the close button on the signup form. + void logCloseRealSignUp() { + DerivRudderstack().track( + eventName: 'ce_real_account_signup_form', + properties: { + 'action': 'close', + 'form_source': 'mobile_derivgo', + 'form_name': 'real_account_signup_derivgo' + }, + ); + } + + /// Tracks if any logical error has happened on the form, + /// validation error for instance. + void logValidationErrorDuringRealSignUp() { + DerivRudderstack().track( + eventName: 'ce_real_account_signup_form', + properties: { + 'action': 'real_signup_error', + 'form_source': 'mobile_derivgo', + 'form_name': 'real_account_signup_derivgo' + }, + ); + } + + /// Tracks when the signup flow is finished. + void logRealSignUpFinished() { + DerivRudderstack().track( + eventName: 'ce_real_account_signup_form', + properties: { + 'action': 'real_signup_finished', + 'form_source': 'mobile_derivgo', + 'form_name': 'real_account_signup_derivgo' + }, + ); + } + + // --------- ce_deriv_go_trade_form --------------- + /// Tracks when user opens trade page + /// + /// tradeType: Trade type name (e.g. 'Multipliers', 'Accumulators') + /// asset: Asset name (e.g. 'EURUSD', 'BTCUSD') + /// chartType: Chart type name (e.g. 'area', 'candle', 'worm') + /// actionTriggerType: Action trigger type (e.g. trade_page_cta, back_to_trade_page, assets_choice, trade_type_choice) + void logTradePageOpened({ + required String tradeType, + required String market, + required String chartType, + required String actionTriggerType, + }) { + DerivRudderstack().track( + eventName: 'ce_deriv_go_trade_form', + properties: { + 'action': 'open_trade_page', + 'form_name': 'ce_deriv_go_trade_form', + 'market_name': market, + 'trade_type_name': tradeType, + 'chart_type_name': chartType, + 'action_trigger_type': actionTriggerType, + }); + } + + /// Tracks when user opens big chart page + /// tradeType: Trade type name (e.g. 'Multipliers', 'Accumulators') + /// asset: Asset name (e.g. 'EURUSD', 'BTCUSD') + /// chartType: Chart type name (e.g. 'area', 'candle', 'worm') + /// actionTriggerType: Action trigger type (e.g. chart_double_click, bigchart_switcher_cta, chart_types_settings_cta) + void logBigChartPageOpened({ + required String tradeType, + required String market, + required String chartType, + required String actionTriggerType, + }) { + DerivRudderstack().track( + eventName: 'ce_deriv_go_trade_form', + properties: { + 'action': 'open_big_chart', + 'form_name': 'ce_deriv_go_trade_form', + 'market_name': market, + 'trade_type_name': tradeType, + 'chart_type_name': chartType, + 'action_trigger_type': actionTriggerType, + }); + } + + /// Tracks when user buys a contract + /// tradeType: Trade type name (e.g. 'Multipliers', 'Accumulators') + /// asset: Asset name (e.g. 'EURUSD', 'BTCUSD') + /// chartType: Chart type name (e.g. 'area', 'candle', 'worm') + /// currentPage: Current page name (e.g. 'trade_page', 'big_chart_page') + void logContractBought({ + required String market, + required String chartType, + required String tradeType, + required String currentPage, + required String ctaName, + int numberOfTrades = 1, + }) { + DerivRudderstack().track( + eventName: 'ce_deriv_go_trade_form', + properties: { + 'action': 'run_contract', + 'form_name': 'ce_deriv_go_trade_form', + 'market_name': market, + 'chart_type_name': chartType, + 'trade_type_name': tradeType, + 'subform_name': currentPage, + 'contract_cta_name': ctaName, + 'number_of_trades ': numberOfTrades, + }); + } + + /// Tracks when user close a contract. + void logContractClosed({ + required String market, + required String chartType, + required String tradeType, + required String currentPage, + required String ctaName, + int numberOfTrades = 1, + }) { + DerivRudderstack().track( + eventName: 'ce_deriv_go_trade_form', + properties: { + 'action': 'close_contract', + 'form_name': 'ce_deriv_go_trade_form', + 'market_name': market, + 'chart_type_name': chartType, + 'trade_type_name': tradeType, + 'subform_name': currentPage, + 'contract_cta_name': ctaName, + 'number_of_trades ': numberOfTrades, + }); + } + + // ------------ ce_indicators_types_form --------------- + + /// Track when user opens indicators bottom sheet. + void logOpenIndicatorTypesBottomSheet() { + DerivRudderstack().track( + eventName: 'ce_indicators_types_form', + properties: { + 'action': 'open', + 'form_name': 'indicators_types_form_derivgo', + 'form_source': 'mobile_derivgo', + }, + ); + } + + /// Track when user closes indicators bottom sheet. + void logCloseIndicatorTypesBottomSheet() { + DerivRudderstack().track( + eventName: 'ce_indicators_types_form', + properties: { + 'action': 'close', + 'form_name': 'indicators_types_form_derivgo', + 'form_source': 'mobile_derivgo', + }, + ); + } + + /// Track when user clicks on indicator type to add the indicator. + /// + ///* indicatorTypeName: Indicator type name (e.g. 'MA', 'RSI' , 'MACD' , 'BB') + ///* categoryName: Indicator category name (e.g. 'Momentum', 'volatility' , 'moving averages') + void logAddIndicatorByClickIndicatorType( + String indicatorTypeName, + String categoryName, + ) { + DerivRudderstack().track( + eventName: 'ce_indicators_types_form', + properties: { + 'action': 'add_active', + 'indicator_type_name': indicatorTypeName, + 'indicators_category_name': categoryName, + 'subform_name': 'indicators_type', + 'form_name': 'indicators_types_form_derivgo', + 'form_source': 'mobile_derivgo', + }, + ); + } + + /// Track when user clicks on indicator info `Add` button to add the indicator. + /// + ///* indicatorTypeName: Indicator type name (e.g. 'MA', 'RSI' , 'MACD' , 'BB') + ///* categoryName: Indicator category name (e.g. 'Momentum', 'volatility' , 'moving averages') + void logAddIndicatorByClickAddOnIndicatorInfo( + String indicatorTypeName, + String categoryName, + ) { + DerivRudderstack().track( + eventName: 'ce_indicators_types_form', + properties: { + 'action': 'add_active', + 'indicator_type_name': indicatorTypeName, + 'indicators_category_name': categoryName, + 'subform_name': 'indicators_info', + 'form_name': 'indicators_types_form_derivgo', + 'form_source': 'mobile_derivgo', + }, + ); + } + + /// Track when user clicks on clean all active indicator. + void logCleanAllActiveIndicator() { + DerivRudderstack().track( + eventName: 'ce_indicators_types_form', + properties: { + 'action': 'clean_all_active', + 'form_name': 'indicators_types_form_derivgo', + 'form_source': 'mobile_derivgo', + }, + ); + } + + /// Track when user deletes active indicator from active indicators list. + /// + ///* indicatorTypeName: Indicator type name (e.g. 'MA', 'RSI' , 'MACD' , 'BB') + ///* categoryName: Indicator category name (e.g. 'Momentum', 'volatility' , 'moving averages') + void logDeleteActiveIndicator( + String indicatorTypeName, + String categoryName, + ) { + DerivRudderstack().track( + eventName: 'ce_indicators_types_form', + properties: { + 'action': 'delete_active', + 'indicator_type_name': indicatorTypeName, + 'indicators_category_name': categoryName, + 'subform_name': 'indicators_type', + 'form_name': 'indicators_types_form_derivgo', + 'form_source': 'mobile_derivgo', + }, + ); + } + + /// Track when user deletes active indicator from indicator's settings. + /// + ///* indicatorTypeName: Indicator type name (e.g. 'MA', 'RSI' , 'MACD' , 'BB') + ///* categoryName: Indicator category name (e.g. 'Momentum', 'volatility' , 'moving averages') + void logDeleteActiveIndicatorFromSettings( + String indicatorTypeName, + String categoryName, + ) { + DerivRudderstack().track( + eventName: 'ce_indicators_types_form', + properties: { + 'action': 'delete_active', + 'indicator_type_name': indicatorTypeName, + 'indicators_category_name': categoryName, + 'subform_name': 'indicators_settings', + 'form_name': 'indicators_types_form_derivgo', + 'form_source': 'mobile_derivgo', + }, + ); + } + + /// Track when user edits indicator's settings. + /// + ///* indicatorTypeName: Indicator type name (e.g. 'MA', 'RSI' , 'MACD' , 'BB') + ///* categoryName: Indicator category name (e.g. 'Momentum', 'volatility' , 'moving averages') + void logEditIndicatorSettings( + String indicatorTypeName, + String categoryName, + ) { + DerivRudderstack().track( + eventName: 'ce_indicators_types_form', + properties: { + 'action': 'edit_active', + 'indicator_type_name': indicatorTypeName, + 'indicators_category_name': categoryName, + 'form_name': 'indicators_types_form_derivgo', + 'form_source': 'mobile_derivgo', + }, + ); + } + + /// Track when user resets indicator's settings. + /// + ///* indicatorTypeName: Indicator type name (e.g. 'MA', 'RSI' , 'MACD' , 'BB') + ///* categoryName: Indicator category name (e.g. 'Momentum', 'volatility' , 'moving averages') + void logResetIndicatorSettings( + String indicatorTypeName, + String categoryName, + ) { + DerivRudderstack().track( + eventName: 'ce_indicators_types_form', + properties: { + 'action': 'reset', + 'subform_name': 'indicators_settings', + 'indicator_type_name': indicatorTypeName, + 'indicators_category_name': categoryName, + 'form_name': 'indicators_types_form_derivgo', + 'form_source': 'mobile_derivgo', + }, + ); + } + + /// Track when user opens indicator info from indicators list. + /// + ///* indicatorTypeName: Indicator type name (e.g. 'MA', 'RSI' , 'MACD' , 'BB') + ///* categoryName: Indicator category name (e.g. 'Momentum', 'volatility' , 'moving averages') + void logOpenIndicatorInfoFromIndicatorsList( + String indicatorTypeName, + String categoryName, + ) { + DerivRudderstack().track( + eventName: 'ce_indicators_types_form', + properties: { + 'action': 'info_open', + 'indicator_type_name': indicatorTypeName, + 'indicators_category_name': categoryName, + 'subform_name': 'indicators_type', + 'form_name': 'indicators_types_form_derivgo', + 'form_source': 'mobile_derivgo', + }, + ); + } + + /// Track when user opens indicator info from indicator's settings. + /// + ///* indicatorTypeName: Indicator type name (e.g. 'MA', 'RSI' , 'MACD' , 'BB') + ///* categoryName: Indicator category name (e.g. 'Momentum', 'volatility' , 'moving averages') + void logOpenIndicatorInfoFromIndicatorSettings( + String indicatorTypeName, + String categoryName, + ) { + DerivRudderstack().track( + eventName: 'ce_indicators_types_form', + properties: { + 'action': 'info_open', + 'indicator_type_name': indicatorTypeName, + 'indicators_category_name': categoryName, + 'subform_name': 'indicators_settings', + 'form_name': 'indicators_types_form_derivgo', + 'form_source': 'mobile_derivgo', + }, + ); + } + + /// Track when user closes indicator info. + /// + ///* indicatorTypeName: Indicator type name (e.g. 'MA', 'RSI' , 'MACD' , 'BB') + ///* categoryName: Indicator category name (e.g. 'Momentum', 'volatility' , 'moving averages') + void logCloseIndicatorInfo( + String indicatorTypeName, + String categoryName, + ) { + DerivRudderstack().track( + eventName: 'ce_indicators_types_form', + properties: { + 'action': 'info_close', + 'indicator_type_name': indicatorTypeName, + 'indicators_category_name': categoryName, + 'form_name': 'indicators_types_form_derivgo', + 'form_source': 'mobile_derivgo', + }, + ); + } +} diff --git a/packages/analytics/lib/sdk/rudderstack/models/user_info.dart b/packages/analytics/lib/sdk/rudderstack/models/user_info.dart new file mode 100644 index 000000000..3538304d3 --- /dev/null +++ b/packages/analytics/lib/sdk/rudderstack/models/user_info.dart @@ -0,0 +1,27 @@ +import 'package:equatable/equatable.dart'; + +/// User Info model. +class UserInfo extends Equatable { + /// Constructor for User Info model. + const UserInfo({ + required this.userId, + this.countryResidence, + this.language, + this.accountType, + }); + + /// User id. + final int userId; + + /// Country of residence. + final String? countryResidence; + + /// User language. + final String? language; + + /// Account type. + final String? accountType; + + @override + List get props => [userId, countryResidence, language, accountType]; +} diff --git a/packages/analytics/lib/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart b/packages/analytics/lib/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart new file mode 100644 index 000000000..402ef45d3 --- /dev/null +++ b/packages/analytics/lib/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart @@ -0,0 +1,127 @@ +import 'dart:async'; + +import 'package:analytics/analytics_route_observer.dart'; +import 'package:analytics/core/logger.dart'; +import 'package:analytics/sdk/base_analytics.dart'; +import 'package:analytics/sdk/rudderstack/core/rudderstack_configuration.dart'; +import 'package:analytics/sdk/rudderstack/models/user_info.dart'; +import 'package:flutter/widgets.dart'; +import 'package:rudder_sdk_flutter_platform_interface/platform.dart'; +import 'package:rudder_sdk_flutter/RudderController.dart'; + +/// A wrapper around RudderStack Flutter SDK. +class DerivRudderstack implements BaseAnalytics { + /// Creates a new [DerivRudderstack] instance. + factory DerivRudderstack() => _instance; + + DerivRudderstack._internal(); + + static final DerivRudderstack _instance = DerivRudderstack._internal(); + + @override + NavigatorObserver get navigatorObserver => + AnalyticsRouteObserver(onNewRoute: (Route route) { + if (route.settings.name != null) { + screen( + screenName: route.settings.name!, + ); + } + }); + + RudderController _rudderClient = RudderController.instance; + Logger _logger = ConsoleLogger(); + + /// The [RudderController] instance used for tracking events. + RudderController get rudderClient => _rudderClient; + + /// The [Logger] instance used for logging messages and errors. + Logger get logger => _logger; + + /// Sets the [rudderClient] instance in the case of testing. + @visibleForTesting + set rudderClient(RudderController rudderClient) { + _rudderClient = rudderClient; + } + + /// Sets the [logger] instance in the case of testing. + @visibleForTesting + set logger(Logger logger) { + _logger = logger; + } + + /// Sets the user id for this instance. + Future identify({ + required UserInfo userInfo, + }) async { + final RudderTraits traits = RudderTraits() + ..put('residence_country', userInfo.countryResidence) + ..put('account_type', userInfo.accountType) + ..put('user_language', userInfo.language); + return _execute(() => rudderClient.identify( + userInfo.userId.toString(), + traits: traits, + )); + } + + /// Tracks an event with the given [eventName] and [properties]. + Future track({ + required String eventName, + Map? properties, + }) async { + final RudderProperty? rudderProperty = + properties == null ? null : RudderProperty.fromMap(properties); + return _execute( + () => rudderClient.track(eventName, properties: rudderProperty)); + } + + /// Logs a screen view with the given [screenName]. + Future screen({required String screenName}) async => + _execute(() => rudderClient.screen(screenName)); + + /// Adds a user to a group with the given [groupId]. + Future group({required String groupId}) async => + _execute(() => rudderClient.group(groupId)); + + /// Aliases a user with the given alias. + Future alias({required String alias}) async => + _execute(() => rudderClient.alias(alias)); + + /// Sets up the RudderStack client. + /// + /// Takes [dataPlaneUrl] and [writeKey] as parameters. + @override + Future setup(RudderstackConfiguration configuration) async => + _execute(() { + final RudderConfigBuilder builder = RudderConfigBuilder() + ..withDataPlaneUrl(configuration.dataPlaneUrl) + ..withDebug(configuration.debugEnabled); + rudderClient.initialize(configuration.writeKey, + config: builder.build()); + }); + + /// Resets the RudderStack client state. + Future reset() async => _execute(() => rudderClient.reset()); + + /// Disables the RudderStack client. + Future disable() async => _execute(() => rudderClient.optOut(true)); + + /// Enables the RudderStack client. + Future enable() async => _execute(() => rudderClient.optOut(false)); + + /// Sets the context for the RudderStack client. + /// + /// Takes a [token] as a parameter. + Future setContext({required String token}) async => + _execute(() => rudderClient.putDeviceToken(token)); + + /// Executes [action] and logs errors, if any. + Future _execute(Function action) async { + try { + action(); + return true; + } on Exception catch (e) { + logger.log('DerivRudderstack: $e'); + return false; + } + } +} diff --git a/packages/analytics/pubspec.yaml b/packages/analytics/pubspec.yaml index 1f61a025b..1fb6ab9d6 100644 --- a/packages/analytics/pubspec.yaml +++ b/packages/analytics/pubspec.yaml @@ -1,6 +1,6 @@ name: analytics description: A new Flutter package for collecting and sending analytical information from the app. -version: 1.0.0 +version: 4.1.0 homepage: https://deriv.com/ publish_to: "none" @@ -10,14 +10,11 @@ environment: flutter: ">=3.10.0" dependencies: - deriv_rudderstack: - git: - url: git@github.com:regentmarkets/flutter-deriv-packages.git - path: packages/deriv_rudderstack - ref: dev - - firebase_analytics: ^10.4.0 - firebase_core: ^2.12.0 + firebase_core: ^3.5.0 + firebase_analytics: ^11.3.2 + rudder_sdk_flutter: ^2.6.0 + datadog_flutter_plugin: ^1.6.2 + equatable: ^2.0.5 flutter: sdk: flutter @@ -25,5 +22,4 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - -flutter: + mocktail: ^1.0.1 diff --git a/packages/analytics/test/datadog/core/deriv_datadog_configuration_test.dart b/packages/analytics/test/datadog/core/deriv_datadog_configuration_test.dart new file mode 100644 index 000000000..eee33ff41 --- /dev/null +++ b/packages/analytics/test/datadog/core/deriv_datadog_configuration_test.dart @@ -0,0 +1,31 @@ +import 'package:analytics/sdk/datadog/core/datadog_configuration.dart'; +import 'package:analytics/sdk/datadog/core/enums.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('DerivDatadogConfiguration', () { + test('DerivDatadogConfiguration', () { + const DerivDatadogConfiguration config = DerivDatadogConfiguration( + applicationId: 'myAppId', + clientToken: 'myClientToken', + env: 'development', + trackingConsent: TrackingConsent.granted, + site: DatadogSite.ap1, + nativeCrashReportEnabled: false, + sessionSamplingRate: 10, + tracingSamplingRate: 20, + serviceName: 'myServiceName', + ); + + expect(config.applicationId, 'myAppId'); + expect(config.clientToken, 'myClientToken'); + expect(config.env, 'development'); + expect(config.trackingConsent, TrackingConsent.granted); + expect(config.site, DatadogSite.ap1); + expect(config.nativeCrashReportEnabled, false); + expect(config.sessionSamplingRate, 10); + expect(config.tracingSamplingRate, 20); + expect(config.serviceName, 'myServiceName'); + }); + }); +} diff --git a/packages/analytics/test/datadog/core/enums_test.dart b/packages/analytics/test/datadog/core/enums_test.dart new file mode 100644 index 000000000..66a44497a --- /dev/null +++ b/packages/analytics/test/datadog/core/enums_test.dart @@ -0,0 +1,27 @@ +import 'package:analytics/sdk/datadog/core/enums.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:datadog_flutter_plugin/datadog_flutter_plugin.dart' as datadog; + +void main() { + group('Enums', () { + test( + 'TrackingConsentExtension: consent should return the corresponding datadog.TrackingConsent value', + () { + expect(TrackingConsent.granted.consent, datadog.TrackingConsent.granted); + expect(TrackingConsent.notGranted.consent, + datadog.TrackingConsent.notGranted); + expect(TrackingConsent.pending.consent, datadog.TrackingConsent.pending); + }); + + test( + 'DatadogSiteExtension: site should return the corresponding datadog.DatadogSite value', + () { + expect(DatadogSite.us1.site, datadog.DatadogSite.us1); + expect(DatadogSite.us3.site, datadog.DatadogSite.us3); + expect(DatadogSite.us5.site, datadog.DatadogSite.us5); + expect(DatadogSite.eu1.site, datadog.DatadogSite.eu1); + expect(DatadogSite.us1Fed.site, datadog.DatadogSite.us1Fed); + expect(DatadogSite.ap1.site, datadog.DatadogSite.ap1); + }); + }); +} diff --git a/packages/analytics/test/datadog/sdk/deriv_datadog_test.dart b/packages/analytics/test/datadog/sdk/deriv_datadog_test.dart new file mode 100644 index 000000000..afd8b05f1 --- /dev/null +++ b/packages/analytics/test/datadog/sdk/deriv_datadog_test.dart @@ -0,0 +1,47 @@ +import 'package:analytics/sdk/base_analytics.dart'; +import 'package:analytics/sdk/datadog/core/datadog_configuration.dart'; +import 'package:analytics/sdk/datadog/sdk/deriv_datadog.dart'; +import 'package:datadog_flutter_plugin/datadog_flutter_plugin.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:datadog_flutter_plugin/datadog_flutter_plugin.dart' as datadog; +import 'package:analytics/sdk/datadog/core/enums.dart' as deriv_datadog_enums; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('DerivDatadog', () { + late DerivDatadog derivDatadog; + late DatadogSdk datadogSdk; + late DerivDatadogConfiguration derivDatadogConfiguration; + + setUpAll(() { + DatadogSdk.initializeForTesting(); + datadogSdk = DatadogSdk.instance; + derivDatadog = DerivDatadog()..setDatadogSdk(datadogSdk); + derivDatadogConfiguration = const DerivDatadogConfiguration( + applicationId: 'applicationId', + clientToken: 'clientToken', + env: 'env', + trackingConsent: deriv_datadog_enums.TrackingConsent.granted, + ); + }); + + test('should return correct instance of DerivDatadog', () { + expect(derivDatadog, isA()); + expect(derivDatadog, isA>()); + }); + + test('should return correct NavigatorObserver', () { + final NavigatorObserver navigationObserver = + derivDatadog.navigatorObserver; + expect(navigationObserver, isNotNull); + expect(navigationObserver, isA()); + }); + + test('should call setup with correct parameters', () async { + final bool result = await derivDatadog.setup(derivDatadogConfiguration); + expect(result, true); + }); + }); +} diff --git a/packages/analytics/test/firebase/core/deriv_firebase_analytics_configuration_test.dart b/packages/analytics/test/firebase/core/deriv_firebase_analytics_configuration_test.dart new file mode 100644 index 000000000..1bd5a0097 --- /dev/null +++ b/packages/analytics/test/firebase/core/deriv_firebase_analytics_configuration_test.dart @@ -0,0 +1,20 @@ +import 'package:analytics/sdk/firebase/core/firebase_configuration.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('FirebaseConfiguration', () { + test('FirebaseConfiguration with isAnalyticsCollectionEnabled true', () { + const FirebaseConfiguration config = + FirebaseConfiguration(isAnalyticsCollectionEnabled: true); + + expect(config.isAnalyticsCollectionEnabled, isTrue); + }); + + test('FirebaseConfiguration with isAnalyticsCollectionEnabled false', () { + const FirebaseConfiguration config = + FirebaseConfiguration(isAnalyticsCollectionEnabled: false); + + expect(config.isAnalyticsCollectionEnabled, isFalse); + }); + }); +} diff --git a/packages/analytics/test/firebase/sdk/deriv_firebase_sdk_test.dart b/packages/analytics/test/firebase/sdk/deriv_firebase_sdk_test.dart new file mode 100644 index 000000000..82f01e30e --- /dev/null +++ b/packages/analytics/test/firebase/sdk/deriv_firebase_sdk_test.dart @@ -0,0 +1,115 @@ +import 'package:analytics/sdk/firebase/core/firebase_configuration.dart'; +import 'package:analytics/sdk/firebase/sdk/deriv_firebase_analytics.dart'; +import 'package:firebase_analytics/firebase_analytics.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockFirebaseAnalytics extends Mock implements FirebaseAnalytics {} + +void main() { + late DerivFirebaseAnalytics derivFirebaseAnalytics; + late MockFirebaseAnalytics mockFirebaseAnalytics; + + setUp(() { + mockFirebaseAnalytics = MockFirebaseAnalytics(); + derivFirebaseAnalytics = DerivFirebaseAnalytics(mockFirebaseAnalytics); + }); + + group('DerivFirebaseAnalytics', () { + test('should return correct instance of DerivFirebaseAnalytics', () async { + when(() => mockFirebaseAnalytics.setAnalyticsCollectionEnabled(true)) + .thenAnswer((_) async => true); + const FirebaseConfiguration firebaseConfiguration = + FirebaseConfiguration(isAnalyticsCollectionEnabled: true); + + await derivFirebaseAnalytics.setup(firebaseConfiguration); + + verify(() => mockFirebaseAnalytics.setAnalyticsCollectionEnabled( + firebaseConfiguration.isAnalyticsCollectionEnabled)).called(1); + }); + + test('should return correct NavigatorObserver', () async { + const String screenName = 'test_screen_name'; + + when(() => mockFirebaseAnalytics.logScreenView(screenName: screenName)) + .thenAnswer((_) async => true); + + await derivFirebaseAnalytics.setCurrentScreen(screenName: screenName); + + verify(() => mockFirebaseAnalytics.logScreenView(screenName: screenName)) + .called(1); + }); + + test('should call logAppOpen', () async { + when(() => mockFirebaseAnalytics.logAppOpen()) + .thenAnswer((_) async => true); + + await derivFirebaseAnalytics.logAppOpen(); + + verify(() => mockFirebaseAnalytics.logAppOpen()).called(1); + }); + + test('should call logLogin with correct parameters', () async { + const String loginMethod = 'test_login_method'; + + when(() => mockFirebaseAnalytics.logLogin(loginMethod: loginMethod)) + .thenAnswer((_) async => true); + + await derivFirebaseAnalytics.logLogin(loginMethod: loginMethod); + + verify(() => mockFirebaseAnalytics.logLogin(loginMethod: loginMethod)) + .called(1); + }); + + test('should call setUserId with correct parameters', () async { + const String userId = 'test_user_id'; + + when(() => mockFirebaseAnalytics.setUserId(id: userId)) + .thenAnswer((_) async => true); + + await derivFirebaseAnalytics.setUserId(id: userId); + + verify(() => mockFirebaseAnalytics.setUserId(id: userId)).called(1); + }); + + test('should call logEvent with correct parameters', () async { + const String name = 'test_name'; + const Map params = {}; + + when(() => mockFirebaseAnalytics.logEvent( + name: name, + parameters: params, + )).thenAnswer((_) async => true); + + await derivFirebaseAnalytics.logEvent( + name: name, + parameters: params, + ); + + verify(() => mockFirebaseAnalytics.logEvent( + name: name, + parameters: params, + )).called(1); + }); + + test('should call setUserProperty with correct parameters', () async { + const String name = 'test_name'; + const String value = 'test_value'; + + when(() => mockFirebaseAnalytics.setUserProperty( + name: name, + value: value, + )).thenAnswer((_) async => true); + + await derivFirebaseAnalytics.setUserProperty( + name: name, + value: value, + ); + + verify(() => mockFirebaseAnalytics.setUserProperty( + name: name, + value: value, + )).called(1); + }); + }); +} diff --git a/packages/analytics/test/rudderstack/core/deriv_rudderstack_configuration_test.dart b/packages/analytics/test/rudderstack/core/deriv_rudderstack_configuration_test.dart new file mode 100644 index 000000000..b0f121eb2 --- /dev/null +++ b/packages/analytics/test/rudderstack/core/deriv_rudderstack_configuration_test.dart @@ -0,0 +1,18 @@ +import 'package:analytics/sdk/rudderstack/core/rudderstack_configuration.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('RudderstackConfiguration', () { + test('RudderstackConfiguration', () { + const String dataPlaneUrl = 'https://test.dataplane.rudderstack.com'; + const String writeKey = 'test_write_key'; + const RudderstackConfiguration config = RudderstackConfiguration( + dataPlaneUrl: dataPlaneUrl, + writeKey: writeKey, + ); + + expect(config.dataPlaneUrl, dataPlaneUrl); + expect(config.writeKey, writeKey); + }); + }); +} diff --git a/packages/analytics/test/rudderstack/sdk/deriv_rudderstack_sdk_test.dart b/packages/analytics/test/rudderstack/sdk/deriv_rudderstack_sdk_test.dart new file mode 100644 index 000000000..0a4c3614f --- /dev/null +++ b/packages/analytics/test/rudderstack/sdk/deriv_rudderstack_sdk_test.dart @@ -0,0 +1,136 @@ +import 'package:analytics/core/logger.dart'; +import 'package:analytics/sdk/rudderstack/core/rudderstack_configuration.dart'; +import 'package:analytics/sdk/rudderstack/models/user_info.dart'; +import 'package:analytics/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:rudder_sdk_flutter/RudderController.dart'; + +class MockRudderController extends Mock implements RudderController {} + +class MockLogger extends Mock implements Logger {} + +void main() { + late DerivRudderstack derivRudderstack; + late MockRudderController mockRudderController; + late MockLogger mockLogger; + + setUp(() { + mockRudderController = MockRudderController(); + mockLogger = MockLogger(); + derivRudderstack = DerivRudderstack() + ..rudderClient = mockRudderController + ..logger = mockLogger; + }); + + group('DerivRudderstack', () { + test('identify calls rudderClient.identify', () async { + final UserInfo info = UserInfo(userId: 111); + final bool result = await derivRudderstack.identify(userInfo: info); + + expect(result, isTrue); + }); + + test('track calls rudderClient.track', () async { + const String eventName = 'test_event_name'; + const Map properties = { + 'action': 'open' + }; + + final bool result = await derivRudderstack.track( + eventName: eventName, + properties: properties, // Pass RudderProperty directly + ); + + expect(result, isTrue); + }); + + test('screen calls rudderClient.screen', () async { + const String screenName = 'test_screen_name'; + + final bool result = await derivRudderstack.screen(screenName: screenName); + + expect(result, isTrue); + verify(() => mockRudderController.screen(screenName)).called(1); + }); + + test('group calls rudderClient.group', () async { + const String groupId = 'test_group_id'; + + final bool result = await derivRudderstack.group(groupId: groupId); + + expect(result, isTrue); + verify(() => mockRudderController.group(groupId)).called(1); + }); + + test('alias calls rudderClient.alias', () async { + const String alias = 'test_alias'; + + final bool result = await derivRudderstack.alias(alias: alias); + + expect(result, isTrue); + verify(() => mockRudderController.alias(alias)).called(1); + }); + + test('should call initialize with correct parameters', () async { + const String dataPlaneUrl = 'https://test.dataplane.rudderstack.com'; + const String writeKey = 'test_write_key'; + + await derivRudderstack.setup(const RudderstackConfiguration( + dataPlaneUrl: dataPlaneUrl, writeKey: writeKey)); + verify(() => mockRudderController.initialize(writeKey, + config: any(named: 'config'))).called(1); + + final bool result = await derivRudderstack.setup( + const RudderstackConfiguration( + dataPlaneUrl: dataPlaneUrl, writeKey: writeKey)); + + expect(result, true); + }); + + test('should throw exception when initialize is called with wrong url', + () async { + const String dataPlaneUrl = 'wrong_url'; + const String writeKey = 'test_write_key'; + + when(() => mockRudderController.initialize(any(), + config: any(named: 'config'))).thenThrow(Exception()); + + final bool result = await derivRudderstack.setup( + const RudderstackConfiguration( + dataPlaneUrl: dataPlaneUrl, writeKey: writeKey)); + + expect(result, false); + }); + + test('reset calls rudderClient.reset', () async { + final bool result = await derivRudderstack.reset(); + + expect(result, isTrue); + verify(() => mockRudderController.reset()).called(1); + }); + + test('disable calls rudderClient.optOut(true)', () async { + final bool result = await derivRudderstack.disable(); + + expect(result, isTrue); + verify(() => mockRudderController.optOut(true)).called(1); + }); + + test('enable calls rudderClient.optOut(false)', () async { + final bool result = await derivRudderstack.enable(); + + expect(result, isTrue); + verify(() => mockRudderController.optOut(false)).called(1); + }); + + test('setContext calls rudderClient.putDeviceToken', () async { + const String token = 'test_token'; + + final bool result = await derivRudderstack.setContext(token: token); + + expect(result, isTrue); + verify(() => mockRudderController.putDeviceToken(token)).called(1); + }); + }); +} diff --git a/packages/deriv_app_performance/CHANGELOG.md b/packages/deriv_app_performance/CHANGELOG.md index 41cc7d819..5f68af6f1 100644 --- a/packages/deriv_app_performance/CHANGELOG.md +++ b/packages/deriv_app_performance/CHANGELOG.md @@ -1,3 +1,13 @@ +## 0.1.0 + +> Note: This release has breaking changes. + + - **BREAKING** **FIX**(deriv_app_performance): [DRGO-1247] Ramin/update dependencies ([#862](https://github.com/regentmarkets/flutter-deriv-packages/issues/862)). ([b0e7120b](https://github.com/regentmarkets/flutter-deriv-packages/commit/b0e7120bd1afc0b3244e14d0c251525005ee67c5)) + +## 0.0.1+1 + + - **FIX**(deriv_app_performance): return the trace object on start and stop tracing ([#782](https://github.com/regentmarkets/flutter-deriv-packages/issues/782)). ([7f146ac7](https://github.com/regentmarkets/flutter-deriv-packages/commit/7f146ac7a9cc71ca0bb79f5523a4ceb77d2df25b)) + ## 0.0.1 * TODO: Describe initial release. diff --git a/packages/deriv_app_performance/lib/deriv_app_performance.dart b/packages/deriv_app_performance/lib/deriv_app_performance.dart index d6f7b67c3..ef8fad51c 100644 --- a/packages/deriv_app_performance/lib/deriv_app_performance.dart +++ b/packages/deriv_app_performance/lib/deriv_app_performance.dart @@ -27,7 +27,7 @@ class AppPerformance { void disable() => _firebasePerformance.setPerformanceCollectionEnabled(false); /// Starts tracing the App Performance. - void startTracing({required String traceName}) { + Trace? startTracing({required String traceName}) { if (_traceMap.containsKey(traceName)) { stopTracing(traceName: traceName); } @@ -36,18 +36,22 @@ class AppPerformance { final Trace trace = _createTrace(traceName: traceName); _traceMap[traceName] = trace; trace.start(); + return trace; } catch (e) { log(e.toString()); } + return null; } /// Stops tracing the App Performance. - void stopTracing({required String traceName}) { + Trace? stopTracing({required String traceName}) { try { final Trace? retrievedTrace = _traceMap[traceName]; retrievedTrace?.stop(); + return retrievedTrace; } catch (e) { log(e.toString()); } + return null; } } diff --git a/packages/deriv_app_performance/pubspec.yaml b/packages/deriv_app_performance/pubspec.yaml index 70e646340..a8a468d20 100644 --- a/packages/deriv_app_performance/pubspec.yaml +++ b/packages/deriv_app_performance/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_app_performance description: A new Flutter package project. -version: 0.0.1 +version: 0.1.0 homepage: environment: @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - firebase_performance: ^0.9.2 + firebase_performance: ^0.10.0+8 dev_dependencies: flutter_test: diff --git a/packages/deriv_auth/.gitignore b/packages/deriv_auth/.gitignore index 65c34dc86..693a18d48 100644 --- a/packages/deriv_auth/.gitignore +++ b/packages/deriv_auth/.gitignore @@ -8,3 +8,33 @@ build/ # Omit committing pubspec.lock for library packages; see # https://dart.dev/guides/libraries/private-files#pubspeclock. pubspec.lock + +# 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 diff --git a/packages/deriv_auth/CHANGELOG.md b/packages/deriv_auth/CHANGELOG.md index effe43c82..ab86788ef 100644 --- a/packages/deriv_auth/CHANGELOG.md +++ b/packages/deriv_auth/CHANGELOG.md @@ -1,3 +1,542 @@ +## 7.0.7 + + - Update a dependency to the latest release. + +## 7.0.6 + + - Update a dependency to the latest release. + +## 7.0.5 + + - Update a dependency to the latest release. + +## 7.0.4 + + - Update a dependency to the latest release. + +## 7.0.3 + + - **FIX**(deriv_auth): Change the token to token getter for more flexibility ([#852](https://github.com/regentmarkets/flutter-deriv-packages/issues/852)). ([09ab8c56](https://github.com/regentmarkets/flutter-deriv-packages/commit/09ab8c56f6e4616ef8c162ea00d6ccb58e5a0f1f)) + +## 7.0.2 + + - Update a dependency to the latest release. + +## 7.0.1 + + - Update a dependency to the latest release. + +## 7.0.0 + +> Note: This release has breaking changes. + + - **BREAKING** **FIX**(deriv_auth): [DRGO-1247] update some depedencies ([#858](https://github.com/regentmarkets/flutter-deriv-packages/issues/858)). ([6a511b39](https://github.com/regentmarkets/flutter-deriv-packages/commit/6a511b39e91b95747fe594f40d6214b0da39d2e2)) + +## 6.8.5 + + - Update a dependency to the latest release. + +## 6.8.4 + + - Update a dependency to the latest release. + +## 6.8.3 + + - Update a dependency to the latest release. + +## 6.8.2 + + - Update a dependency to the latest release. + +## 6.8.1 + + - **FIX**: Updating Flutter Deriv API version to latest ([#843](https://github.com/regentmarkets/flutter-deriv-packages/issues/843)). ([db461495](https://github.com/regentmarkets/flutter-deriv-packages/commit/db46149557a9a93eb86a6691a8c7831bf709e8f1)) + +## 6.8.0 + + - **FIX**: [86c0e0nez] add disability check while filtering accounts ([#837](https://github.com/regentmarkets/flutter-deriv-packages/issues/837)). ([e64dfa54](https://github.com/regentmarkets/flutter-deriv-packages/commit/e64dfa54d774e79c3c88f63f3112c7eb0c0cb6b8)) + - **FEAT**(deriv_auth): [DERG-1396] akhil/1396/multi_user_level_authentication_poc_master ([#574](https://github.com/regentmarkets/flutter-deriv-packages/issues/574)). ([97ac8004](https://github.com/regentmarkets/flutter-deriv-packages/commit/97ac8004370762ed38ed0608e64699a020406b8e)) + +## 6.7.51 + + - **FIX**: [86c0e0nez] add disability check while filtering accounts ([#837](https://github.com/regentmarkets/flutter-deriv-packages/issues/837)). ([e64dfa54](https://github.com/regentmarkets/flutter-deriv-packages/commit/e64dfa54d774e79c3c88f63f3112c7eb0c0cb6b8)) + - **FIX**: [86c0e0nez] filter supported accounts while logging in ([#833](https://github.com/regentmarkets/flutter-deriv-packages/issues/833)). ([534e982c](https://github.com/regentmarkets/flutter-deriv-packages/commit/534e982c809b5e0e9380366a3f32a05f1ef2cf10)) + +## 6.7.50 + + - Update a dependency to the latest release. + +## 6.7.49 + + - **FIX**(deriv_auth): unassigned token for account created on web ([#756](https://github.com/regentmarkets/flutter-deriv-packages/issues/756)). ([abbb8905](https://github.com/regentmarkets/flutter-deriv-packages/commit/abbb8905263517c32c1e990fdc9dbfd2fb38ae9b)) + +## 6.7.48 + + - Update a dependency to the latest release. + +## 6.7.47 + + - Update a dependency to the latest release. + +## 6.7.46 + + - Update a dependency to the latest release. + +## 6.7.45 + + - Update a dependency to the latest release. + +## 6.7.44 + + - Update a dependency to the latest release. + +## 6.7.43 + + - Update a dependency to the latest release. + +## 6.7.42 + + - Update a dependency to the latest release. + +## 6.7.41 + + - Update a dependency to the latest release. + +## 6.7.40 + + - Update a dependency to the latest release. + +## 6.7.39 + + - Update a dependency to the latest release. + +## 6.7.38 + + - Update a dependency to the latest release. + +## 6.7.37 + + - Update a dependency to the latest release. + +## 6.7.36 + + - **FIX**(deriv_auth): validation_enhancement ([#775](https://github.com/regentmarkets/flutter-deriv-packages/issues/775)). ([fc4bbb1f](https://github.com/regentmarkets/flutter-deriv-packages/commit/fc4bbb1f9384334dced645e8213b8b5dade8f05d)) + +## 6.7.35 + + - **REFACTOR**(deriv_auth): Update_single_entry_feature ([#577](https://github.com/regentmarkets/flutter-deriv-packages/issues/577)). ([462d7bd6](https://github.com/regentmarkets/flutter-deriv-packages/commit/462d7bd6bdf60536fa632be3b95fae0ba377f142)) + +## 6.7.34 + + - Update a dependency to the latest release. + +## 6.7.33 + + - Update a dependency to the latest release. + +## 6.7.32 + + - Update a dependency to the latest release. + +## 6.7.31 + + - Update a dependency to the latest release. + +## 6.7.30 + + - **FIX**(deriv_auth): email and password text field validation ([#761](https://github.com/regentmarkets/flutter-deriv-packages/issues/761)). ([c75d00c4](https://github.com/regentmarkets/flutter-deriv-packages/commit/c75d00c4ef105a9a5ff3cdb3a8546ee43d76e997)) + +## 6.7.29 + + - **FIX**(deriv_auth): email text field validation ([#751](https://github.com/regentmarkets/flutter-deriv-packages/issues/751)). ([3e05c3fa](https://github.com/regentmarkets/flutter-deriv-packages/commit/3e05c3fa9315a75d19fbc3727ad9a161617e7fb8)) + +## 6.7.28 + + - Update a dependency to the latest release. + +## 6.7.27 + + - Update a dependency to the latest release. + +## 6.7.26 + + - Update a dependency to the latest release. + +## 6.7.25 + + - Update a dependency to the latest release. + +## 6.7.24 + + - Update a dependency to the latest release. + +## 6.7.23 + + - Update a dependency to the latest release. + +## 6.7.22 + + - Update a dependency to the latest release. + +## 6.7.21 + + - Update a dependency to the latest release. + +## 6.7.20 + + - Update a dependency to the latest release. + +## 6.7.19 + + - Update a dependency to the latest release. + +## 6.7.18 + + - Update a dependency to the latest release. + +## 6.7.17 + + - **REFACTOR**(version): updated the version of flutter deriv api ([#694](https://github.com/regentmarkets/flutter-deriv-packages/issues/694)). ([eac7e8cb](https://github.com/regentmarkets/flutter-deriv-packages/commit/eac7e8cba4e9310d30296e07a47731f08d4d7342)) + +## 6.7.16 + + - Update a dependency to the latest release. + +## 6.7.15 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +## 6.7.14 + + - Update a dependency to the latest release. + +## 6.7.13 + + - Update a dependency to the latest release. + +## 6.7.12 + + - Update a dependency to the latest release. + +## 6.7.10 + + - **FIX**(deriv_auth): minor change in readme file ([#669](https://github.com/regentmarkets/flutter-deriv-packages/issues/669)). ([b29d222c](https://github.com/regentmarkets/flutter-deriv-packages/commit/b29d222ce219d0664e3cafb6c302cd1041749905)) + +## 6.7.9 + + - **REFACTOR**(deriv_auth): Replace uni_links2 with app_links ([#664](https://github.com/regentmarkets/flutter-deriv-packages/issues/664)). ([f99554bc](https://github.com/regentmarkets/flutter-deriv-packages/commit/f99554bc134b7fe7fe0b3f5bf7555728868176c9)) + +## 6.7.8 + + - **FIX**(deriv_auth): fix_error_when_initializing_mock_auth_service ([#660](https://github.com/regentmarkets/flutter-deriv-packages/issues/660)). ([30aa969f](https://github.com/regentmarkets/flutter-deriv-packages/commit/30aa969f7ed3f083ab1610518eded103d0aa2eb7)) + +## 6.7.7 + + - **REFACTOR**(deriv_passkeys): update deriv api dependency ([#656](https://github.com/regentmarkets/flutter-deriv-packages/issues/656)). ([3425078b](https://github.com/regentmarkets/flutter-deriv-packages/commit/3425078b52baac4f387504c9d41063bda1dba249)) + +## 6.7.6 + + - Update a dependency to the latest release. + +## 6.7.5 + + - Update a dependency to the latest release. + +## 6.7.4 + + - Update a dependency to the latest release. + +## 6.7.3 + + - Update a dependency to the latest release. + +## 6.7.2 + + - Update a dependency to the latest release. + +## 6.7.1 + + - **FIX**(deriv_auth): Fix login provider null issue ([#641](https://github.com/regentmarkets/flutter-deriv-packages/issues/641)). ([e4181541](https://github.com/regentmarkets/flutter-deriv-packages/commit/e41815416eb76b3724481f8a0b980e943311ebeb)) + - **FIX**(deriv_web_view): update deriv ui version in deriv auth ([#639](https://github.com/regentmarkets/flutter-deriv-packages/issues/639)). ([11b46a9f](https://github.com/regentmarkets/flutter-deriv-packages/commit/11b46a9f7bd00482d3cac7820cf4cd5d61da0cad)) + +## 6.7.0 + + - **FIX**(deriv_web_view): update deriv ui version in deriv auth ([#639](https://github.com/regentmarkets/flutter-deriv-packages/issues/639)). ([11b46a9f](https://github.com/regentmarkets/flutter-deriv-packages/commit/11b46a9f7bd00482d3cac7820cf4cd5d61da0cad)) + - **FEAT**(deriv_auth): add log in user tracking events. ([#620](https://github.com/regentmarkets/flutter-deriv-packages/issues/620)). ([ae9556cf](https://github.com/regentmarkets/flutter-deriv-packages/commit/ae9556cf3af98196bb22e351d9a8eccbf534889c)) + +## 6.6.9 + + - Update a dependency to the latest release. + +## 6.6.8 + + - Update a dependency to the latest release. + +## 6.6.7 + + - Update a dependency to the latest release. + +## 6.6.6 + + - **REFACTOR**(deriv_auth): added a flag to allow hiding passkeys button ([#612](https://github.com/regentmarkets/flutter-deriv-packages/issues/612)). ([a4026a9d](https://github.com/regentmarkets/flutter-deriv-packages/commit/a4026a9d8164abc1c66beb327d48610d8ce30dde)) + +## 6.6.5 + + - Update a dependency to the latest release. + +## 6.6.4 + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + +## 6.6.3 + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + +## 6.6.2 + + - Update a dependency to the latest release. + +## 6.6.1 + + - **REFACTOR**(deriv_auth): update localization ([#594](https://github.com/regentmarkets/flutter-deriv-packages/issues/594)). ([5204c74f](https://github.com/regentmarkets/flutter-deriv-packages/commit/5204c74f609d946ea797e766e6bb652d82f76930)) + +## 6.6.0 + + - **REFACTOR**(deriv_auth): replaced deriv_localizations commit hash ref with tag ref. ([572aa5fd](https://github.com/regentmarkets/flutter-deriv-packages/commit/572aa5fd54241d09b9993b102c0eceb117594821)) + - **REFACTOR**(deriv_passkeys): updated deriv_passkeys and deriv_localizations. ([d6eccdca](https://github.com/regentmarkets/flutter-deriv-packages/commit/d6eccdcaf9fa37784ae3f9fb2bd13a98e874aae0)) + - **REFACTOR**: update deriv_passkeys. ([cd545c74](https://github.com/regentmarkets/flutter-deriv-packages/commit/cd545c74f7076010d7153d74c20288b2b8db016b)) + - **REFACTOR**: update deriv_passkeys. ([41fa9ed8](https://github.com/regentmarkets/flutter-deriv-packages/commit/41fa9ed899462b0eeef34fa3971ffe82e937ce91)) + - **REFACTOR**(deriv_auth): Updated deriv_localizations. ([4d43e258](https://github.com/regentmarkets/flutter-deriv-packages/commit/4d43e258d72e71da6c53f1bf2b241f95d4ba4c67)) + - **REFACTOR**(deriv_auth): updated deriv_passkeys and deriv_localizations versions. ([a3acdf1a](https://github.com/regentmarkets/flutter-deriv-packages/commit/a3acdf1ad34bdbba464d866273e165709d908159)) + - **REFACTOR**(deriv_auth): updated deriv_passkeys commit hash. ([ed0f0057](https://github.com/regentmarkets/flutter-deriv-packages/commit/ed0f005785991a0c166edaf4f93e03922826ee91)) + - **REFACTOR**(deriv_auth): updated deriv_passkeys commit hash. ([82de1610](https://github.com/regentmarkets/flutter-deriv-packages/commit/82de161098d7cd698dcf465a4ad4a2cdac1fdd75)) + - **REFACTOR**(deriv_auth): updated deriv_passkeys commit hash. ([dcda848b](https://github.com/regentmarkets/flutter-deriv-packages/commit/dcda848bb014f72e138e907c84c258be1a7c5c63)) + - **REFACTOR**(deriv_auth): updated deriv_passkeys commit hash. ([87ff83bb](https://github.com/regentmarkets/flutter-deriv-packages/commit/87ff83bbc8050c3893186b86003345ca3ed739bd)) + - **REFACTOR**(deriv_auth): updated deriv_passkeys commit hash. ([608dcf4b](https://github.com/regentmarkets/flutter-deriv-packages/commit/608dcf4b812945efa605ae5c553afa06e6a9f8db)) + - **REFACTOR**(deriv_auth): updated deriv_passkeys commit hash. ([6a2708eb](https://github.com/regentmarkets/flutter-deriv-packages/commit/6a2708eb6cb8a3b343fd8688a27ab038fd3c1acb)) + - **REFACTOR**(deriv_auth): updated deriv_ui. ([294ca423](https://github.com/regentmarkets/flutter-deriv-packages/commit/294ca42318568fd876f531856b61d0930efb05b8)) + - **REFACTOR**(deriv_passkeys): removed context parameter from onLoggedIn callback. ([02f390b2](https://github.com/regentmarkets/flutter-deriv-packages/commit/02f390b2cb4736993f876fe60f6513490682837a)) + - **REFACTOR**(deriv_auth): updated deriv_passkeys and deriv_localizations versions. ([14b85c22](https://github.com/regentmarkets/flutter-deriv-packages/commit/14b85c22c7edbd8046eb9f2feef68aa49aff8df2)) + - **REFACTOR**(deriv_auth): new deriv_passkeys commit hash ref. ([7bb5f71b](https://github.com/regentmarkets/flutter-deriv-packages/commit/7bb5f71b4a3c364b3ee4455fe1c97b8ac5e83dea)) + - **REFACTOR**(deriv_auth): updated passkeys dependencies. ([d966d9f2](https://github.com/regentmarkets/flutter-deriv-packages/commit/d966d9f28a743d652dd14174827da5e20a697664)) + - **REFACTOR**(deriv_auth): integrating deriv_passkeys. ([45cbbb6c](https://github.com/regentmarkets/flutter-deriv-packages/commit/45cbbb6c20274e84e4f932057d0816a3b4dc581c)) + - **REFACTOR**(deriv_auth): fixing deriv_passkeys dependencies. ([ac5dd589](https://github.com/regentmarkets/flutter-deriv-packages/commit/ac5dd5895caf6f2f2e60b2906a38f6ac27bfc11a)) + - **REFACTOR**(deriv_auth): linked onLoggedIn function in login_page. ([1e5ba631](https://github.com/regentmarkets/flutter-deriv-packages/commit/1e5ba63165ff214ec44e589daddb9566ec47b94a)) + - **REFACTOR**(deriv_auth): added context to onLoggedIn. ([c93046ab](https://github.com/regentmarkets/flutter-deriv-packages/commit/c93046abadefeb4af75cb0a2996f69a37415b33e)) + - **REFACTOR**(deriv_auth): updated flutter_deriv_api. ([c3187ba8](https://github.com/regentmarkets/flutter-deriv-packages/commit/c3187ba85494198bb3b11e568ad50ff714b69cfa)) + - **REFACTOR**(deriv_auth): updated `deriv_http_client`. ([c12a9ee4](https://github.com/regentmarkets/flutter-deriv-packages/commit/c12a9ee43fcc321a9f2ca404215a4f15069e4a71)) + - **FIX**(deriv_auth): fixed onPressed error. ([a3cce1fb](https://github.com/regentmarkets/flutter-deriv-packages/commit/a3cce1fb5884592d0329e67de4a5a6ca9b55f694)) + - **FIX**(deriv_auth): calling onLoggedIn correctly. ([5c19f3ae](https://github.com/regentmarkets/flutter-deriv-packages/commit/5c19f3aee610bd3ff9e9c25edae4a558ae8a97b8)) + - **FIX**(deriv_auth): fixed deriv_passkeys dependency. ([607eca12](https://github.com/regentmarkets/flutter-deriv-packages/commit/607eca127e5a394dbb58abe82a5d5d4b4f811156)) + - **FEAT**(deriv_auth): single entry ([#420](https://github.com/regentmarkets/flutter-deriv-packages/issues/420)). ([e594c05b](https://github.com/regentmarkets/flutter-deriv-packages/commit/e594c05b87c05d189913a7de8b9e1e4e6d03b76a)) + - **FEAT**(deriv_passkeys): connected passkeys functionality with deriv auth. ([7333af28](https://github.com/regentmarkets/flutter-deriv-packages/commit/7333af283e3e5e00970a2dd49930f243d7f1f558)) + - **FEAT**(deriv_auth): added deriv_passkeys to deriv_auth. ([e96c8127](https://github.com/regentmarkets/flutter-deriv-packages/commit/e96c81278999fa2dfae57e6adf5785bb82a51fe3)) + - **FEAT**(deriv_auth): single entry reset password and merge conflicts. ([f7930d66](https://github.com/regentmarkets/flutter-deriv-packages/commit/f7930d66880d2d091959646dd928e79189ce5704)) + - **FEAT**(deriv_auith): single entry setting page. ([dc29784f](https://github.com/regentmarkets/flutter-deriv-packages/commit/dc29784f2f74c34a7a5bf9910b4041adca591e74)) + - **FEAT**(deriv_auth): single entry signup page. ([f38cc253](https://github.com/regentmarkets/flutter-deriv-packages/commit/f38cc253209afdf3573e3a85fe8ec0cd7082c5d5)) + - **FEAT**(deriv_auth): single entry login and reset pass added. ([573a4a78](https://github.com/regentmarkets/flutter-deriv-packages/commit/573a4a787a6aa518537f2678c19a748cebcf0fb4)) + - **FEAT**(deriv_auth): adding login page to single entry. ([5ce30c3b](https://github.com/regentmarkets/flutter-deriv-packages/commit/5ce30c3b0fbd84ad3a4cc191d0c7949ec9691ef5)) + - **FEAT**(deriv_auth): single entry. ([c1e0067b](https://github.com/regentmarkets/flutter-deriv-packages/commit/c1e0067b7bbaf94cfd13f342cd05aaa8f65ba497)) + +## 6.5.2 + + - Update a dependency to the latest release. + +## 6.5.1 + + - Update a dependency to the latest release. + +## 6.5.0 + + - **FEAT**(deriv_auth): Add language selector in auth package ([#576](https://github.com/regentmarkets/flutter-deriv-packages/issues/576)). ([cd3768ef](https://github.com/regentmarkets/flutter-deriv-packages/commit/cd3768ef2b6bd7420e7957277461e95e78eee545)) + +## 6.4.3 + + - Update a dependency to the latest release. + +## 6.4.2 + + - Update a dependency to the latest release. + +## 6.4.1 + + - **FIX**(deriv_auth): Update settings page navigation in auth flow with callback ([#567](https://github.com/regentmarkets/flutter-deriv-packages/issues/567)). ([53857baf](https://github.com/regentmarkets/flutter-deriv-packages/commit/53857baf66ecc870e8a1452663c564b8ee57837a)) + +## 6.4.0 + + - **FEAT**(deriv_auth): [P2PS-2679] add widget keys to auth components ([#565](https://github.com/regentmarkets/flutter-deriv-packages/issues/565)). ([e7bc54b4](https://github.com/regentmarkets/flutter-deriv-packages/commit/e7bc54b4be3d80236c83f1fecbe6f012f8759690)) + +## 6.3.8 + + - Update a dependency to the latest release. + +## 6.3.7 + + - Update a dependency to the latest release. + +## 6.3.6 + + - **FIX**(auth_single_entry): add auth cubits provider. ([8f71d1d5](https://github.com/regentmarkets/flutter-deriv-packages/commit/8f71d1d570508666843119d7c1317210484fcc20)) + +## 6.3.5 + + - Update a dependency to the latest release. + +## 6.3.4 + + - Update a dependency to the latest release. + +## 6.3.3 + + - Update a dependency to the latest release. + + +## 6.3.2 + + - Update a dependency to the latest release. + +## 6.3.1 + + - **FIX**: deriv_toke_service_test ([#519](https://github.com/regentmarkets/flutter-deriv-packages/issues/519)). ([25b9dc45](https://github.com/regentmarkets/flutter-deriv-packages/commit/25b9dc454c222bb7299d859a21cb1b3cb874c1bf)) + +## 6.3.0 + + - **FEAT**(deriv_http_client): update to the latest deriv_http_client ([#501](https://github.com/regentmarkets/flutter-deriv-packages/issues/501)). ([d582995a](https://github.com/regentmarkets/flutter-deriv-packages/commit/d582995a226906c34c69c7716b3e5573c88c0c4e)) + +## 6.2.1 + + - Update a dependency to the latest release. + +## 6.2.0 + + - **REFACTOR**(deriv_ui): remove example and android folder ([#503](https://github.com/regentmarkets/flutter-deriv-packages/issues/503)). ([8c90e199](https://github.com/regentmarkets/flutter-deriv-packages/commit/8c90e1995a3a04c945923cb0f8f0e7480cde03b7)) + - **FIX**(deriv_auth): endpoint change not reflecting in social login ([#498](https://github.com/regentmarkets/flutter-deriv-packages/issues/498)). ([32e33a46](https://github.com/regentmarkets/flutter-deriv-packages/commit/32e33a464092dcd570f5ef8d7524e0dc9b369566)) + - **FIX**(deriv_auth): endpoint change not reflecting in social login ([#498](https://github.com/regentmarkets/flutter-deriv-packages/issues/498)). ([5714a5dd](https://github.com/regentmarkets/flutter-deriv-packages/commit/5714a5dd403c5fd0fc97d3ee634f9c76241b22da)) + - **FEAT**(deriv_auth): single entry ([#420](https://github.com/regentmarkets/flutter-deriv-packages/issues/420)). ([19130046](https://github.com/regentmarkets/flutter-deriv-packages/commit/19130046f21d24a28a5e135914308a411ee762e3)) + - **FEAT**(deriv_auth): single entry ([#420](https://github.com/regentmarkets/flutter-deriv-packages/issues/420)). ([e594c05b](https://github.com/regentmarkets/flutter-deriv-packages/commit/e594c05b87c05d189913a7de8b9e1e4e6d03b76a)) + +## 6.1.1 + + - **FIX**(deriv_auth): endpoint change not reflecting in social login ([#498](https://github.com/regentmarkets/flutter-deriv-packages/issues/498)). ([5714a5dd](https://github.com/regentmarkets/flutter-deriv-packages/commit/5714a5dd403c5fd0fc97d3ee634f9c76241b22da)) + + +## 6.1.0 + + - **FEAT**(deriv_auth): single entry ([#420](https://github.com/regentmarkets/flutter-deriv-packages/issues/420)). ([e594c05b](https://github.com/regentmarkets/flutter-deriv-packages/commit/e594c05b87c05d189913a7de8b9e1e4e6d03b76a)) + +## 6.0.2 + + - Update a dependency to the latest release. + +## 6.0.0 + +> Note: This release has breaking changes. + + - **BREAKING** **REFACTOR**(deriv_auth): ramin_make_auth_and_web_view_proxy_aware ([#483](https://github.com/regentmarkets/flutter-deriv-packages/issues/483)). ([e99afc92](https://github.com/regentmarkets/flutter-deriv-packages/commit/e99afc926531c0c36c567d0ac8c66e906fa27ea5)) + +## 5.0.3 + + - **REFACTOR**(deriv_auth): rename inavalid to invalid in auth state handler ([#490](https://github.com/regentmarkets/flutter-deriv-packages/issues/490)). ([2e9cfa75](https://github.com/regentmarkets/flutter-deriv-packages/commit/2e9cfa75007fce25b90394bd92905e9c3ca876cc)) + +## 5.0.2 + + - Update a dependency to the latest release. + +## 5.0.1 + + - Update a dependency to the latest release. + +## 5.0.0 + +> Note: This release has breaking changes. + + - **BREAKING** **REFACTOR**(deriv_auth): [MOBC-802] migrate to deriv_localizations package [#486](https://github.com/regentmarkets/flutter-deriv-packages/issues/486). ([e9297272](https://github.com/regentmarkets/flutter-deriv-packages/commit/e9297272c91235263cf2335b8ba69a3a1d9c1583)) + +## 4.1.1 + + - Update a dependency to the latest release. + +## 4.1.0 + + - **FIX**(deriv_auth): localCurrency issue. ([efbeb86d](https://github.com/regentmarkets/flutter-deriv-packages/commit/efbeb86d5ed69edf27f31625818092a921119ed6)) + - **FEAT**(deriv-auth): add ctrader to platformEnumMapper. ([38bc6f86](https://github.com/regentmarkets/flutter-deriv-packages/commit/38bc6f861e5c98b38e558aa2f7d54253f0a12807)) + +## 4.0.0 + +> Note: This release has breaking changes. + + - **BREAKING** **FEAT**(deriv_auth): handle webview logic for social login ([#446](https://github.com/regentmarkets/flutter-deriv-packages/issues/446)). ([94eeec1e](https://github.com/regentmarkets/flutter-deriv-packages/commit/94eeec1eeda0ffe7809d49e541cb76363a9b8326)) + +## 3.0.1 + + - **FIX**(deriv_auth): reset password design mismatch ([#456](https://github.com/regentmarkets/flutter-deriv-packages/issues/456)). ([efe30f1a](https://github.com/regentmarkets/flutter-deriv-packages/commit/efe30f1a8884ced0397d546dbe6144cea88d124c)) + +## 3.0.0 + +> Note: This release has breaking changes. + + - **BREAKING** **REFACTOR**(deriv_auth): add `isLinkExpired` boolean to reset password error state ([#428](https://github.com/regentmarkets/flutter-deriv-packages/issues/428)). ([29c93ac6](https://github.com/regentmarkets/flutter-deriv-packages/commit/29c93ac6fbb82ee48aee225050c2f1d3ddd79b39)) + +## 2.1.2 + + - Update a dependency to the latest release. + + +## 2.1.1 + + - Update a dependency to the latest release. + +## 2.1.0 + + - **FEAT**(deriv_ui): [MOBC-740] move UI related packages to deriv_ui ([#422](https://github.com/regentmarkets/flutter-deriv-packages/issues/422)). ([a2fd5b97](https://github.com/regentmarkets/flutter-deriv-packages/commit/a2fd5b97f81a0dbbc7a6bf07625027e04dfb9a5d)) + +## 2.0.8 + + - **REFACTOR**(deriv_auth): add reset state functionality to DerivResetPassCubit ([#423](https://github.com/regentmarkets/flutter-deriv-packages/issues/423)). ([99e8eb12](https://github.com/regentmarkets/flutter-deriv-packages/commit/99e8eb12211ea65157495d77ad9f9c9630c618e3)) + +## 2.0.7 + + - **FIX**(deriv_auth): fix 2fa for social login ([#418](https://github.com/regentmarkets/flutter-deriv-packages/issues/418)). ([5a1001c5](https://github.com/regentmarkets/flutter-deriv-packages/commit/5a1001c5d6411baa9c07407db4794bf594cec9a9)) + +## 2.0.6 + + - **REFACTOR**(deriv_auth): improve reset password error handling ([#417](https://github.com/regentmarkets/flutter-deriv-packages/issues/417)). ([cc2d2ebb](https://github.com/regentmarkets/flutter-deriv-packages/commit/cc2d2ebbbecf53de56aa7c067aec4ad505fcc6b5)) + +## 2.0.4 + + - **REFACTOR**(deriv_auth): renamed localization getter in ContextExtension ([#414](https://github.com/regentmarkets/flutter-deriv-packages/issues/414)). ([b625fbce](https://github.com/regentmarkets/flutter-deriv-packages/commit/b625fbce37e7acfd9454ba16b908207aee3c9a86)) + - **REFACTOR**(deriv_auth): Update package info plus deriv auth ([#410](https://github.com/regentmarkets/flutter-deriv-packages/issues/410)). ([e2e717f3](https://github.com/regentmarkets/flutter-deriv-packages/commit/e2e717f36e93039cadc706cc29b12b48a7c1b411)) + +## 2.0.3 + + - Update a dependency to the latest release. + +## 2.0.2 + + - **FIX**(deriv_auth): fix overflow issue in change password layout ([#404](https://github.com/regentmarkets/flutter-deriv-packages/issues/404)). ([abd05684](https://github.com/regentmarkets/flutter-deriv-packages/commit/abd056841f774ffb806e76569b60701d2fa74808)) + +## 2.0.1 + + - **REFACTOR**(deriv_auth): updated package_info_plus ([#401](https://github.com/regentmarkets/flutter-deriv-packages/issues/401)). ([16d402a4](https://github.com/regentmarkets/flutter-deriv-packages/commit/16d402a45e680df0734f04fb5dee7f4eb1067119)) + +## 2.0.0 + +> Note: This release has breaking changes. + + - **BREAKING** **REFACTOR**: [MOBC-701] [MOBC-702] combine deriv_auth_ui with deriv_auth and remove deriv_auth_ui ([#388](https://github.com/regentmarkets/flutter-deriv-packages/issues/388)). ([853bcbfa](https://github.com/regentmarkets/flutter-deriv-packages/commit/853bcbfaa63f86194fae3d262fdac736c9a15c48)) + +## 1.2.1 + + - **FIX**(deriv_auth): local currencies fromJson authorize_model.dart ([#367](https://github.com/regentmarkets/flutter-deriv-packages/issues/367)). ([4d39a44a](https://github.com/regentmarkets/flutter-deriv-packages/commit/4d39a44a2de970219b241db43bb06b2022f04a3f)) + +## 1.2.0 + + - **FEAT**(deriv_auth): [DERG-1299] add user agent to login ([#341](https://github.com/regentmarkets/flutter-deriv-packages/issues/341)). ([37f5b763](https://github.com/regentmarkets/flutter-deriv-packages/commit/37f5b763d8a679ff1d458fc4e9b82578f4eecf83)) + +## 1.1.0 + + - **FEAT**(deriv_auth): [DERG-1299] add user agent to login ([#341](https://github.com/regentmarkets/flutter-deriv-packages/issues/341)). ([37f5b763](https://github.com/regentmarkets/flutter-deriv-packages/commit/37f5b763d8a679ff1d458fc4e9b82578f4eecf83)) + ## 1.0.0 - Initial version. diff --git a/packages/deriv_auth/README.md b/packages/deriv_auth/README.md index dfe5a5535..6ec48dc2a 100644 --- a/packages/deriv_auth/README.md +++ b/packages/deriv_auth/README.md @@ -99,6 +99,7 @@ DerivAuthCubit( Some usage examples: + - First, start by getting the `DerivAuthCubit` instance. ```dart @@ -341,4 +342,4 @@ Some usage examples: ```dart /// Call the [getSocialAuthProviders] method. _cubit.getSocialAuthProviders(); - ``` \ No newline at end of file + ``` diff --git a/packages/deriv_auth/analysis_options.yaml b/packages/deriv_auth/analysis_options.yaml index e2bfedad3..fd638b40c 100644 --- a/packages/deriv_auth/analysis_options.yaml +++ b/packages/deriv_auth/analysis_options.yaml @@ -1,6 +1,4 @@ analyzer: - exclude: - - example/** errors: todo: ignore missing_required_param: warning diff --git a/packages/deriv_auth/assets/icons/ic_apple.svg b/packages/deriv_auth/assets/icons/ic_apple.svg new file mode 100644 index 000000000..c4d2a029c --- /dev/null +++ b/packages/deriv_auth/assets/icons/ic_apple.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/deriv_auth_ui/assets/icons/ic_choose_new_pass.svg b/packages/deriv_auth/assets/icons/ic_choose_new_pass.svg similarity index 100% rename from packages/deriv_auth_ui/assets/icons/ic_choose_new_pass.svg rename to packages/deriv_auth/assets/icons/ic_choose_new_pass.svg diff --git a/packages/deriv_auth_ui/assets/icons/ic_email_issue_1.svg b/packages/deriv_auth/assets/icons/ic_email_issue_1.svg similarity index 100% rename from packages/deriv_auth_ui/assets/icons/ic_email_issue_1.svg rename to packages/deriv_auth/assets/icons/ic_email_issue_1.svg diff --git a/packages/deriv_auth_ui/assets/icons/ic_email_issue_2.svg b/packages/deriv_auth/assets/icons/ic_email_issue_2.svg similarity index 100% rename from packages/deriv_auth_ui/assets/icons/ic_email_issue_2.svg rename to packages/deriv_auth/assets/icons/ic_email_issue_2.svg diff --git a/packages/deriv_auth_ui/assets/icons/ic_email_issue_3.svg b/packages/deriv_auth/assets/icons/ic_email_issue_3.svg similarity index 100% rename from packages/deriv_auth_ui/assets/icons/ic_email_issue_3.svg rename to packages/deriv_auth/assets/icons/ic_email_issue_3.svg diff --git a/packages/deriv_auth_ui/assets/icons/ic_email_issue_4.svg b/packages/deriv_auth/assets/icons/ic_email_issue_4.svg similarity index 100% rename from packages/deriv_auth_ui/assets/icons/ic_email_issue_4.svg rename to packages/deriv_auth/assets/icons/ic_email_issue_4.svg diff --git a/packages/deriv_auth_ui/assets/icons/ic_email_read.svg b/packages/deriv_auth/assets/icons/ic_email_read.svg similarity index 100% rename from packages/deriv_auth_ui/assets/icons/ic_email_read.svg rename to packages/deriv_auth/assets/icons/ic_email_read.svg diff --git a/packages/deriv_auth_ui/assets/icons/ic_email_unread.svg b/packages/deriv_auth/assets/icons/ic_email_unread.svg similarity index 100% rename from packages/deriv_auth_ui/assets/icons/ic_email_unread.svg rename to packages/deriv_auth/assets/icons/ic_email_unread.svg diff --git a/packages/deriv_auth/assets/icons/ic_facebook.svg b/packages/deriv_auth/assets/icons/ic_facebook.svg new file mode 100644 index 000000000..cb9d842fd --- /dev/null +++ b/packages/deriv_auth/assets/icons/ic_facebook.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/deriv_auth/assets/icons/ic_google.svg b/packages/deriv_auth/assets/icons/ic_google.svg new file mode 100644 index 000000000..8c0cc5a9d --- /dev/null +++ b/packages/deriv_auth/assets/icons/ic_google.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/deriv_auth_ui/assets/icons/ic_location.svg b/packages/deriv_auth/assets/icons/ic_location.svg similarity index 100% rename from packages/deriv_auth_ui/assets/icons/ic_location.svg rename to packages/deriv_auth/assets/icons/ic_location.svg diff --git a/packages/deriv_auth_ui/assets/icons/ic_logo_extended.svg b/packages/deriv_auth/assets/icons/ic_logo_extended.svg similarity index 100% rename from packages/deriv_auth_ui/assets/icons/ic_logo_extended.svg rename to packages/deriv_auth/assets/icons/ic_logo_extended.svg diff --git a/packages/deriv_auth_ui/assets/icons/ic_mail.svg b/packages/deriv_auth/assets/icons/ic_mail.svg similarity index 100% rename from packages/deriv_auth_ui/assets/icons/ic_mail.svg rename to packages/deriv_auth/assets/icons/ic_mail.svg diff --git a/packages/deriv_auth_ui/assets/icons/ic_password.svg b/packages/deriv_auth/assets/icons/ic_password.svg similarity index 100% rename from packages/deriv_auth_ui/assets/icons/ic_password.svg rename to packages/deriv_auth/assets/icons/ic_password.svg diff --git a/packages/deriv_auth_ui/assets/icons/ic_reset_pass.svg b/packages/deriv_auth/assets/icons/ic_reset_pass.svg similarity index 100% rename from packages/deriv_auth_ui/assets/icons/ic_reset_pass.svg rename to packages/deriv_auth/assets/icons/ic_reset_pass.svg diff --git a/packages/deriv_auth_ui/assets/icons/ic_two_factor_auth.svg b/packages/deriv_auth/assets/icons/ic_two_factor_auth.svg similarity index 100% rename from packages/deriv_auth_ui/assets/icons/ic_two_factor_auth.svg rename to packages/deriv_auth/assets/icons/ic_two_factor_auth.svg diff --git a/packages/deriv_auth_ui/assets/icons/ic_unsupported_country.svg b/packages/deriv_auth/assets/icons/ic_unsupported_country.svg similarity index 100% rename from packages/deriv_auth_ui/assets/icons/ic_unsupported_country.svg rename to packages/deriv_auth/assets/icons/ic_unsupported_country.svg diff --git a/packages/deriv_auth_ui/assets/images/charts.svg b/packages/deriv_auth/assets/images/charts.svg similarity index 100% rename from packages/deriv_auth_ui/assets/images/charts.svg rename to packages/deriv_auth/assets/images/charts.svg diff --git a/packages/deriv_auth_ui/assets/images/live_chat.svg b/packages/deriv_auth/assets/images/live_chat.svg similarity index 100% rename from packages/deriv_auth_ui/assets/images/live_chat.svg rename to packages/deriv_auth/assets/images/live_chat.svg diff --git a/packages/deriv_auth_ui/assets/images/markets.svg b/packages/deriv_auth/assets/images/markets.svg similarity index 100% rename from packages/deriv_auth_ui/assets/images/markets.svg rename to packages/deriv_auth/assets/images/markets.svg diff --git a/packages/deriv_auth_ui/assets/images/risk.svg b/packages/deriv_auth/assets/images/risk.svg similarity index 100% rename from packages/deriv_auth_ui/assets/images/risk.svg rename to packages/deriv_auth/assets/images/risk.svg diff --git a/packages/deriv_auth_ui/assets/images/triangles.svg b/packages/deriv_auth/assets/images/triangles.svg similarity index 100% rename from packages/deriv_auth_ui/assets/images/triangles.svg rename to packages/deriv_auth/assets/images/triangles.svg diff --git a/packages/deriv_auth/docs/auth_error_handler.md b/packages/deriv_auth/docs/auth_error_handler.md new file mode 100644 index 000000000..537149a44 --- /dev/null +++ b/packages/deriv_auth/docs/auth_error_handler.md @@ -0,0 +1,56 @@ +## AuthErrorHandler + +Since `DerivAuthCubit` influences many features like - login, signup, change password, etc. To be ease the error handling by making it needed to implement in only one place and to make sure all the auth error cases has been handled we have created a base class which client app can extend if they want changes in the default error handling. + +```dart +base class AuthErrorStateHandler { + /// {@macro default_auth_error_state_handler} + AuthErrorStateHandler({ + required this.context, + }); + + /// The [BuildContext] of the widget that is using this handler. + final BuildContext context; + + /// On invalid 2FA code. + void invalid2faCode(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.localization.informInvalid2FACode, + actionLabel: context.localization.actionTryAgain, + ); + } + + /// On invalid email or password. + void invalidEmailOrPassword(DerivAuthErrorState state) { + //.... + } + + // ... +} +``` +If client app wants to customize the error handling they can extend the `AuthErrorStateHandler` and override the methods they want to customize. + +```dart + +final class CustomAuthErrorStateHandler extends AuthErrorStateHandler { + CustomAuthErrorStateHandler({ + required BuildContext context, + }) : super(context: context); + + @override + void invalid2faCode(DerivAuthErrorState state) { + //... + } +} +``` +The client app can pass the custom error handler to the layout's constructor. + +```dart +DerivLoginLayout( + // ... + authErrorStateHandler: CustomAuthErrorStateHandler(context: context), + // ... +) +``` +The package handles the mapping of the error state to the corresponding method in the `AuthErrorStateHandler` class within the layout. diff --git a/packages/deriv_auth_ui/README.md b/packages/deriv_auth/docs/deriv_auth_layouts.md similarity index 68% rename from packages/deriv_auth_ui/README.md rename to packages/deriv_auth/docs/deriv_auth_layouts.md index 504ab5166..fe5f949e2 100644 --- a/packages/deriv_auth_ui/README.md +++ b/packages/deriv_auth/docs/deriv_auth_layouts.md @@ -1,55 +1,7 @@ -# Deriv Auth UI -This package contains the shared `Auth` UI for deriv apps. This was exctracted from `flutter-multipliers` and some modifications were made to make it more generic and flexible. - -## Features: - -- Provides basic layout for auth pages. -- Handles form validation. -- Handles state management for auth pages. - -## Getting Started: - -1. Add the package to your project: - -``` yaml -dependencies: - git: - url: git@github.com:regentmarkets/flutter-deriv-packages.git - path: packages/deriv_auth_ui - ref: dev -``` - -2. Import the package: - -``` dart -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; -``` - -3. Wrap your MaterialApp with `DerivThemeProvider`: - -```dart -DerivThemeProvider.builder( - initialTheme: ThemeMode.dark, - builder: (context) => MaterialApp( - /// ... - ), - ), -``` - -4. In your MaterialApp add the `DerivAuthUILocalization` delegate: - -```dart -MaterialApp( - localizationsDelegates: [ - DerivAuthUILocalization.delegate, - /// ... more localization delegates - ], -) -``` - -## Layouts provided: +## Deriv Auth Layouts ### - Get Started Flow + - **Get Started Layout** ``` dart DerivGetStartedLayout( @@ -62,13 +14,18 @@ MaterialApp( onSignupTapped: () {}, ); ``` + ### - Login Flow + - **Login Layout** ``` dart DerivLoginLayout( + titleKey: Key('title key'), + emailTextFieldKey: Key('email text field key'), + passwordTextFieldKey: Key('password text field key'), + forgotPasswordButtonKey: Key('forgot password button key'), + loginButtonKey: Key('login button key'), welcomeLabel: 'Welcome back!', - greetingLabel: - 'Log in to your Deriv account to start trading and investing.', onResetPassTapped: () { // Navigate to reset password page }, @@ -81,20 +38,30 @@ MaterialApp( onLoggedIn: (DerivAuthLoggedInState state) { // Navigate to home page }, - onSocialAuthButtonPressed: (SocialAuthProvider provider) { - // Handle social auth + onSocialAuthButtonPressed: (SocialAuthDto socialAuthDto) { + // Get access to dto from social auth to be used + // for navigating to 2FA page + }, + socialAuthStateHandler: (SocialAuthState state) { + // Handle social auth state }, ); ``` - **2FA Layout** ``` dart - Deriv2FALayout( + // For 2FA with email and password + Deriv2FALayout.systemLogin( email: email, password: password, ); + // For 2FA with social auth + Deriv2FALayout.socialLogin( + socialAuthDto: socialAuthDto, + ); ``` ### - Signup Flow + - **Signup Layout** ``` dart DerivSignupLayout( @@ -106,6 +73,13 @@ MaterialApp( onSingupEmailSent: (String email) {}, onSignupPressed: () {}, onLoginTapped: () {}, + socialAuthStateHandler: (SocialAuthState state) { + // Handle social auth state + }, + onSocialAuthButtonPressed: (SocialAuthDto socialAuthDto) { + // Get access to dto from social auth to be used + // for navigating to 2FA page + }, ); ``` - **Verify Email Layout** @@ -146,6 +120,7 @@ MaterialApp( residence: 'residence', ); ``` + ### - Reset Password Flow - **Reset Password Layout** @@ -158,7 +133,7 @@ MaterialApp( - **Choose New Password Layout** ``` dart DerivChooseNewPassLayout( - onResetPassError: (String? error) {}, + onResetPassError: ({required bool isLinkExpired, String? error}) {}, onResetPassSucceed: () {}, token: token, ), diff --git a/packages/deriv_auth_ui/example/.gitignore b/packages/deriv_auth/example/.gitignore similarity index 100% rename from packages/deriv_auth_ui/example/.gitignore rename to packages/deriv_auth/example/.gitignore diff --git a/packages/deriv_auth_ui/example/.metadata b/packages/deriv_auth/example/.metadata similarity index 100% rename from packages/deriv_auth_ui/example/.metadata rename to packages/deriv_auth/example/.metadata diff --git a/packages/deriv_auth_ui/example/README.md b/packages/deriv_auth/example/README.md similarity index 100% rename from packages/deriv_auth_ui/example/README.md rename to packages/deriv_auth/example/README.md diff --git a/packages/deriv_auth/example/analysis_options.yaml b/packages/deriv_auth/example/analysis_options.yaml new file mode 100644 index 000000000..1313ff64b --- /dev/null +++ b/packages/deriv_auth/example/analysis_options.yaml @@ -0,0 +1,27 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + depend_on_referenced_packages: false +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/deriv_auth/example/android/.gitignore b/packages/deriv_auth/example/android/.gitignore new file mode 100644 index 000000000..6f568019d --- /dev/null +++ b/packages/deriv_auth/example/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/packages/deriv_auth/example/android/app/build.gradle b/packages/deriv_auth/example/android/app/build.gradle new file mode 100644 index 000000000..1570e585d --- /dev/null +++ b/packages/deriv_auth/example/android/app/build.gradle @@ -0,0 +1,72 @@ +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 { + namespace "com.example.example" + compileSdkVersion 34 + 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.example" + // 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 19 + targetSdkVersion 34 + 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/packages/deriv_auth_ui/example/android/app/src/debug/AndroidManifest.xml b/packages/deriv_auth/example/android/app/src/debug/AndroidManifest.xml similarity index 100% rename from packages/deriv_auth_ui/example/android/app/src/debug/AndroidManifest.xml rename to packages/deriv_auth/example/android/app/src/debug/AndroidManifest.xml diff --git a/packages/deriv_auth_ui/example/android/app/src/main/AndroidManifest.xml b/packages/deriv_auth/example/android/app/src/main/AndroidManifest.xml similarity index 100% rename from packages/deriv_auth_ui/example/android/app/src/main/AndroidManifest.xml rename to packages/deriv_auth/example/android/app/src/main/AndroidManifest.xml diff --git a/packages/deriv_auth/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/deriv_auth/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 000000000..e793a000d --- /dev/null +++ b/packages/deriv_auth/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/packages/deriv_auth/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/deriv_auth/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/packages/deriv_auth/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/deriv_auth/example/android/app/src/main/res/drawable/launch_background.xml b/packages/deriv_auth/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/packages/deriv_auth/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/deriv_auth/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/deriv_auth/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..db77bb4b7 Binary files /dev/null and b/packages/deriv_auth/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/deriv_auth/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/deriv_auth/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..17987b79b Binary files /dev/null and b/packages/deriv_auth/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/deriv_auth/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/deriv_auth/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..09d439148 Binary files /dev/null and b/packages/deriv_auth/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/deriv_auth/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/deriv_auth/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..d5f1c8d34 Binary files /dev/null and b/packages/deriv_auth/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/deriv_auth/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/deriv_auth/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..4d6372eeb Binary files /dev/null and b/packages/deriv_auth/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/deriv_auth_ui/example/android/app/src/main/res/values-night/styles.xml b/packages/deriv_auth/example/android/app/src/main/res/values-night/styles.xml similarity index 100% rename from packages/deriv_auth_ui/example/android/app/src/main/res/values-night/styles.xml rename to packages/deriv_auth/example/android/app/src/main/res/values-night/styles.xml diff --git a/packages/deriv_auth_ui/example/android/app/src/main/res/values/styles.xml b/packages/deriv_auth/example/android/app/src/main/res/values/styles.xml similarity index 100% rename from packages/deriv_auth_ui/example/android/app/src/main/res/values/styles.xml rename to packages/deriv_auth/example/android/app/src/main/res/values/styles.xml diff --git a/packages/deriv_auth_ui/example/android/app/src/profile/AndroidManifest.xml b/packages/deriv_auth/example/android/app/src/profile/AndroidManifest.xml similarity index 100% rename from packages/deriv_auth_ui/example/android/app/src/profile/AndroidManifest.xml rename to packages/deriv_auth/example/android/app/src/profile/AndroidManifest.xml diff --git a/packages/deriv_auth_ui/example/android/build.gradle b/packages/deriv_auth/example/android/build.gradle similarity index 100% rename from packages/deriv_auth_ui/example/android/build.gradle rename to packages/deriv_auth/example/android/build.gradle diff --git a/packages/deriv_auth/example/android/gradle.properties b/packages/deriv_auth/example/android/gradle.properties new file mode 100644 index 000000000..94adc3a3f --- /dev/null +++ b/packages/deriv_auth/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/deriv_auth/example/android/settings.gradle b/packages/deriv_auth/example/android/settings.gradle new file mode 100644 index 000000000..44e62bcf0 --- /dev/null +++ b/packages/deriv_auth/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/packages/deriv_auth_ui/example/assets/images/charts.svg b/packages/deriv_auth/example/assets/images/charts.svg similarity index 100% rename from packages/deriv_auth_ui/example/assets/images/charts.svg rename to packages/deriv_auth/example/assets/images/charts.svg diff --git a/packages/deriv_auth_ui/example/assets/images/ic_logo_extended.svg b/packages/deriv_auth/example/assets/images/ic_logo_extended.svg similarity index 100% rename from packages/deriv_auth_ui/example/assets/images/ic_logo_extended.svg rename to packages/deriv_auth/example/assets/images/ic_logo_extended.svg diff --git a/packages/deriv_auth_ui/example/assets/images/live_chat.svg b/packages/deriv_auth/example/assets/images/live_chat.svg similarity index 100% rename from packages/deriv_auth_ui/example/assets/images/live_chat.svg rename to packages/deriv_auth/example/assets/images/live_chat.svg diff --git a/packages/deriv_auth_ui/example/assets/images/markets.svg b/packages/deriv_auth/example/assets/images/markets.svg similarity index 100% rename from packages/deriv_auth_ui/example/assets/images/markets.svg rename to packages/deriv_auth/example/assets/images/markets.svg diff --git a/packages/deriv_auth_ui/example/assets/images/triangles.svg b/packages/deriv_auth/example/assets/images/triangles.svg similarity index 100% rename from packages/deriv_auth_ui/example/assets/images/triangles.svg rename to packages/deriv_auth/example/assets/images/triangles.svg diff --git a/packages/deriv_auth/example/ios/.gitignore b/packages/deriv_auth/example/ios/.gitignore new file mode 100644 index 000000000..7a7f9873a --- /dev/null +++ b/packages/deriv_auth/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/packages/deriv_auth/example/ios/Flutter/AppFrameworkInfo.plist b/packages/deriv_auth/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..7c5696400 --- /dev/null +++ b/packages/deriv_auth/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 + 12.0 + + diff --git a/packages/deriv_auth/example/ios/Flutter/Debug.xcconfig b/packages/deriv_auth/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..ec97fc6f3 --- /dev/null +++ b/packages/deriv_auth/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/packages/deriv_auth/example/ios/Flutter/Release.xcconfig b/packages/deriv_auth/example/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..c4855bfe2 --- /dev/null +++ b/packages/deriv_auth/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/packages/deriv_auth/example/ios/Podfile b/packages/deriv_auth/example/ios/Podfile new file mode 100644 index 000000000..487163519 --- /dev/null +++ b/packages/deriv_auth/example/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '15.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/packages/deriv_auth/example/ios/Podfile.lock b/packages/deriv_auth/example/ios/Podfile.lock new file mode 100644 index 000000000..68f06f5d9 --- /dev/null +++ b/packages/deriv_auth/example/ios/Podfile.lock @@ -0,0 +1,92 @@ +PODS: + - connectivity_plus (0.0.1): + - Flutter + - ReachabilitySwift + - deriv_passkeys (0.0.1): + - Flutter + - device_info_plus (0.0.1): + - Flutter + - Flutter (1.0.0) + - flutter_deriv_api (0.0.1): + - Flutter + - flutter_inappwebview_ios (0.0.1): + - Flutter + - flutter_inappwebview_ios/Core (= 0.0.1) + - OrderedSet (~> 5.0) + - flutter_inappwebview_ios/Core (0.0.1): + - Flutter + - OrderedSet (~> 5.0) + - OrderedSet (5.0.0) + - package_info_plus (0.4.5): + - Flutter + - ReachabilitySwift (5.0.0) + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - uni_links2 (0.0.1): + - Flutter + - url_launcher_ios (0.0.1): + - Flutter + - webview_flutter_wkwebview (0.0.1): + - Flutter + +DEPENDENCIES: + - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) + - deriv_passkeys (from `.symlinks/plugins/deriv_passkeys/ios`) + - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) + - Flutter (from `Flutter`) + - flutter_deriv_api (from `.symlinks/plugins/flutter_deriv_api/ios`) + - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) + - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - uni_links2 (from `.symlinks/plugins/uni_links2/ios`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) + +SPEC REPOS: + trunk: + - OrderedSet + - ReachabilitySwift + +EXTERNAL SOURCES: + connectivity_plus: + :path: ".symlinks/plugins/connectivity_plus/ios" + deriv_passkeys: + :path: ".symlinks/plugins/deriv_passkeys/ios" + device_info_plus: + :path: ".symlinks/plugins/device_info_plus/ios" + Flutter: + :path: Flutter + flutter_deriv_api: + :path: ".symlinks/plugins/flutter_deriv_api/ios" + flutter_inappwebview_ios: + :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" + package_info_plus: + :path: ".symlinks/plugins/package_info_plus/ios" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + uni_links2: + :path: ".symlinks/plugins/uni_links2/ios" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + webview_flutter_wkwebview: + :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" + +SPEC CHECKSUMS: + connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d + deriv_passkeys: fadd039a48bae6f15ba8b955481a0f8f2b5bdede + device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + flutter_deriv_api: 9e29abd7cc5091b72303f9c8be549618415f1437 + flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0 + OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c + package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 + ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 + shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 + uni_links2: fbc37081577fc19c6e0f7e6cdbd3baa150023635 + url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 + webview_flutter_wkwebview: 4f3e50f7273d31e5500066ed267e3ae4309c5ae4 + +PODFILE CHECKSUM: 9c46fd01abff66081b39f5fa5767b3f1d0b11d76 + +COCOAPODS: 1.14.3 diff --git a/packages/deriv_auth_ui/example/ios/Runner.xcodeproj/project.pbxproj b/packages/deriv_auth/example/ios/Runner.xcodeproj/project.pbxproj similarity index 99% rename from packages/deriv_auth_ui/example/ios/Runner.xcodeproj/project.pbxproj rename to packages/deriv_auth/example/ios/Runner.xcodeproj/project.pbxproj index 3d5f8164c..0572134d3 100644 --- a/packages/deriv_auth_ui/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/deriv_auth/example/ios/Runner.xcodeproj/project.pbxproj @@ -215,7 +215,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 331C8080294A63A400263BE5 = { @@ -452,7 +452,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -579,7 +579,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -628,7 +628,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/packages/deriv_auth/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/deriv_auth/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/packages/deriv_auth/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/deriv_auth/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/deriv_auth/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/deriv_auth/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/deriv_auth/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/deriv_auth/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/deriv_auth/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/deriv_auth/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/deriv_auth/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..87131a09b --- /dev/null +++ b/packages/deriv_auth/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/deriv_auth/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/deriv_auth/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..21a3cc14c --- /dev/null +++ b/packages/deriv_auth/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/deriv_auth/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/deriv_auth/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/deriv_auth/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/deriv_auth/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/deriv_auth/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/deriv_auth/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/deriv_auth/example/ios/Runner/AppDelegate.swift b/packages/deriv_auth/example/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..70693e4a8 --- /dev/null +++ b/packages/deriv_auth/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/packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/packages/deriv_auth/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/packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 000000000..dc9ada472 Binary files /dev/null and b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to packages/deriv_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/packages/deriv_auth/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..0bedcf2fd --- /dev/null +++ b/packages/deriv_auth/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/packages/deriv_auth/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/deriv_auth/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/deriv_auth/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/deriv_auth/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/deriv_auth/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/deriv_auth/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/packages/deriv_auth/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/deriv_auth/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/packages/deriv_auth/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/deriv_auth/example/ios/Runner/Base.lproj/Main.storyboard b/packages/deriv_auth/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/packages/deriv_auth/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/deriv_auth/example/ios/Runner/Info.plist b/packages/deriv_auth/example/ios/Runner/Info.plist new file mode 100644 index 000000000..7f553465b --- /dev/null +++ b/packages/deriv_auth/example/ios/Runner/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example + 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/packages/deriv_auth/example/ios/Runner/Runner-Bridging-Header.h b/packages/deriv_auth/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/packages/deriv_auth/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/deriv_auth_ui/example/ios/RunnerTests/RunnerTests.swift b/packages/deriv_auth/example/ios/RunnerTests/RunnerTests.swift similarity index 100% rename from packages/deriv_auth_ui/example/ios/RunnerTests/RunnerTests.swift rename to packages/deriv_auth/example/ios/RunnerTests/RunnerTests.swift diff --git a/packages/deriv_auth_ui/example/lib/core/context_extension.dart b/packages/deriv_auth/example/lib/core/context_extension.dart similarity index 100% rename from packages/deriv_auth_ui/example/lib/core/context_extension.dart rename to packages/deriv_auth/example/lib/core/context_extension.dart diff --git a/packages/deriv_auth/example/lib/core/example_auth_error_state_handler.dart b/packages/deriv_auth/example/lib/core/example_auth_error_state_handler.dart new file mode 100644 index 000000000..c93d182fd --- /dev/null +++ b/packages/deriv_auth/example/lib/core/example_auth_error_state_handler.dart @@ -0,0 +1,16 @@ +import 'package:deriv_auth/core/states/states.dart'; +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:flutter/material.dart'; + +final class ExampleAuthErrorStateHandler extends AuthErrorStateHandler { + ExampleAuthErrorStateHandler({required super.context}); + + @override + void onInvalidCredentials(DerivAuthErrorState state) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.message), + ), + ); + } +} diff --git a/packages/deriv_auth_ui/example/lib/features/get_started/pages/get_started_page.dart b/packages/deriv_auth/example/lib/features/get_started/pages/get_started_page.dart similarity index 60% rename from packages/deriv_auth_ui/example/lib/features/get_started/pages/get_started_page.dart rename to packages/deriv_auth/example/lib/features/get_started/pages/get_started_page.dart index d4ea64fe0..97b058a48 100644 --- a/packages/deriv_auth_ui/example/lib/features/get_started/pages/get_started_page.dart +++ b/packages/deriv_auth/example/lib/features/get_started/pages/get_started_page.dart @@ -1,6 +1,8 @@ -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; -import 'package:example/features/login/pages/login_page.dart'; -import 'package:example/features/signup/pages/signup_page.dart'; +import 'package:deriv_auth/features/get_started/models/deriv_get_started_slide_model.dart'; +import 'package:deriv_auth/features/get_started/presentation/layouts/deriv_get_started_layout.dart'; +import 'package:deriv_auth/features/single_entry/features/login/pages/login_page.dart'; +import 'package:deriv_auth/features/single_entry/features/settings/pages/settings_page.dart'; +import 'package:deriv_auth/features/single_entry/features/signup/pages/signup_page.dart'; import 'package:flutter/material.dart'; class GetStartedPage extends StatelessWidget { @@ -30,5 +32,13 @@ class GetStartedPage extends StatelessWidget { builder: (context) => const SignupPage(), ), ), + onTapNavigation: (context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => const SettingsPage(), + ), + ); + }, ); } diff --git a/packages/deriv_auth_ui/example/lib/features/home/pages/home_page.dart b/packages/deriv_auth/example/lib/features/home/pages/home_page.dart similarity index 100% rename from packages/deriv_auth_ui/example/lib/features/home/pages/home_page.dart rename to packages/deriv_auth/example/lib/features/home/pages/home_page.dart diff --git a/packages/deriv_auth_ui/example/lib/features/login/pages/login_page.dart b/packages/deriv_auth/example/lib/features/login/pages/login_page.dart similarity index 59% rename from packages/deriv_auth_ui/example/lib/features/login/pages/login_page.dart rename to packages/deriv_auth/example/lib/features/login/pages/login_page.dart index 7d1e66a2a..ff78bc3ea 100644 --- a/packages/deriv_auth_ui/example/lib/features/login/pages/login_page.dart +++ b/packages/deriv_auth/example/lib/features/login/pages/login_page.dart @@ -1,8 +1,7 @@ -import 'package:deriv_auth/features/auth/cubit/deriv_auth_cubit.dart'; -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; -import 'package:example/features/home/pages/home_page.dart'; -import 'package:example/features/reset_pass/pages/reset_pass_page.dart'; -import 'package:example/features/signup/pages/signup_page.dart'; +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth/features/single_entry/features/home/pages/home_page.dart'; +import 'package:deriv_auth/features/single_entry/features/reset_pass/pages/reset_pass_page.dart'; +import 'package:deriv_auth/features/single_entry/features/signup/pages/signup_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -23,15 +22,18 @@ class _LoginPageState extends State { @override Widget build(BuildContext context) { return DerivLoginLayout( + socialAuthStateHandler: (context, _) {}, + redirectURL: 'deriv://example', + onWebViewError: (String error) {}, welcomeLabel: 'Welcome back!', - greetingLabel: - 'Log in to your Deriv account to start trading and investing.', - onLoggedIn: (_) => Navigator.pushReplacement( + onLoggedIn: (BuildContext context, DerivAuthLoggedInState state) => + Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => const HomePage(), ), ), + authErrorStateHandler: AuthErrorStateHandler(context: context), onLoginError: (_) {}, onResetPassTapped: () => Navigator.push( context, @@ -45,7 +47,7 @@ class _LoginPageState extends State { builder: (context) => const SignupPage(), ), ), - onSocialAuthButtonPressed: (_) {}, + onSocialAuthButtonPressed: (SocialAuthDto socialAuthDto) {}, ); } } diff --git a/packages/deriv_auth_ui/example/lib/features/login/repositories/example_login_repository.dart b/packages/deriv_auth/example/lib/features/login/repositories/example_login_repository.dart similarity index 65% rename from packages/deriv_auth_ui/example/lib/features/login/repositories/example_login_repository.dart rename to packages/deriv_auth/example/lib/features/login/repositories/example_login_repository.dart index 2e7f72a29..c675e71c3 100644 --- a/packages/deriv_auth_ui/example/lib/features/login/repositories/example_login_repository.dart +++ b/packages/deriv_auth/example/lib/features/login/repositories/example_login_repository.dart @@ -3,23 +3,28 @@ import 'package:deriv_auth/deriv_auth.dart'; class ExampleLoginRepository implements BaseAuthRepository { @override - Future authorize(String? token) => + Future authorize( + String? token, { + List? tokenList, + }) => Future.value(const AuthorizeResponseEntity()); @override - Future getDefaultAccount() => Future.value(AccountModel( - accountId: "accountId", - )); + Future getDefaultAccount() => Future.value( + AccountModel( + accountId: "accountId", + ), + ); @override Future> getLatestAccounts() => Future.value([ AccountModel( accountId: "accountId", - ) + ), ]); @override - Future logout() => Future.value(); + Future logout({String? loginId}) => Future.value(); @override Future onLogin(AuthorizeEntity authorizeEntity) => Future.value(); @@ -32,5 +37,7 @@ class ExampleLoginRepository implements BaseAuthRepository { @override Future getLandingCompany(String? countryCode) => - Future.value(const LandingCompanyEntity()); + Future.value( + const LandingCompanyEntity(), + ); } diff --git a/packages/deriv_auth_ui/example/lib/features/login/services/example_login_service.dart b/packages/deriv_auth/example/lib/features/login/services/example_login_service.dart similarity index 80% rename from packages/deriv_auth_ui/example/lib/features/login/services/example_login_service.dart rename to packages/deriv_auth/example/lib/features/login/services/example_login_service.dart index 66c517885..8e912259a 100644 --- a/packages/deriv_auth_ui/example/lib/features/login/services/example_login_service.dart +++ b/packages/deriv_auth/example/lib/features/login/services/example_login_service.dart @@ -2,30 +2,31 @@ import 'package:deriv_auth/core/models/account_model.dart'; import 'package:deriv_auth/core/models/authorize_model.dart'; import 'package:deriv_auth/core/models/landig_comany_model.dart'; import 'package:deriv_auth/core/services/token/models/login_request.dart'; -import 'package:deriv_auth/features/auth/repository/base_auth_repository.dart'; import 'package:deriv_auth/features/auth/services/base_auth_service.dart'; /// `DerivGO` implementation of [BaseAuthService]. class ExampleLoginService extends BaseAuthService { /// Initializes a [ExampleLoginService] class. ExampleLoginService({ - required this.authRepository, + required super.authRepository, + required super.jwtService, + required super.connectionInfo, + required super.tokenService, }); - /// Interface of all client related functions. - final BaseAuthRepository authRepository; - @override - Future onLoginRequest( - GetTokensRequestModel request, [ - Function? onInvalidJwtToken, - ]) async => + Future onLoginRequest({ + required GetTokensRequestModel request, + String? userAgent, + bool useMultiToken = false, + }) async => const AuthorizeEntity(); @override Future login( String token, { required List accounts, + List? tokenList, String? signupProvider, String? refreshToken, }) async => diff --git a/packages/deriv_auth_ui/example/lib/features/reset_pass/pages/choose_new_password_page.dart b/packages/deriv_auth/example/lib/features/reset_pass/pages/choose_new_password_page.dart similarity index 56% rename from packages/deriv_auth_ui/example/lib/features/reset_pass/pages/choose_new_password_page.dart rename to packages/deriv_auth/example/lib/features/reset_pass/pages/choose_new_password_page.dart index 6ce9f1198..e83aa1d0e 100644 --- a/packages/deriv_auth_ui/example/lib/features/reset_pass/pages/choose_new_password_page.dart +++ b/packages/deriv_auth/example/lib/features/reset_pass/pages/choose_new_password_page.dart @@ -1,6 +1,6 @@ import 'package:deriv_auth/features/reset_password/cubit/reset_password_cubit.dart'; -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; -import 'package:example/features/get_started/pages/get_started_page.dart'; +import 'package:deriv_auth/features/reset_password/presentation/layouts/deriv_choose_new_pass_layout.dart'; +import 'package:deriv_auth/features/single_entry/features/reset_pass/pages/reset_pass_success_page.dart'; import 'package:flutter/material.dart'; class ChooseNewPasswordPage extends StatelessWidget { @@ -13,14 +13,13 @@ class ChooseNewPasswordPage extends StatelessWidget { return DerivChooseNewPassLayout( token: 'token', onResetPassSucceed: () { - Navigator.of(context).pushAndRemoveUntil( + Navigator.of(context).push( MaterialPageRoute( - builder: (context) => const GetStartedPage(), + builder: (context) => const ResetPassSuccessPage(), ), - (Route route) => false, ); }, - onResetPassError: (_) {}, + onResetPassError: ({required bool isLinkExpired, String? error}) {}, ); } } diff --git a/packages/deriv_auth_ui/example/lib/features/reset_pass/pages/reset_pass_page.dart b/packages/deriv_auth/example/lib/features/reset_pass/pages/reset_pass_page.dart similarity index 50% rename from packages/deriv_auth_ui/example/lib/features/reset_pass/pages/reset_pass_page.dart rename to packages/deriv_auth/example/lib/features/reset_pass/pages/reset_pass_page.dart index d060842f9..71f6f3384 100644 --- a/packages/deriv_auth_ui/example/lib/features/reset_pass/pages/reset_pass_page.dart +++ b/packages/deriv_auth/example/lib/features/reset_pass/pages/reset_pass_page.dart @@ -1,9 +1,9 @@ import 'dart:async'; import 'package:deriv_auth/deriv_auth.dart'; -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; -import 'package:example/features/reset_pass/pages/choose_new_password_page.dart'; -import 'package:example/features/reset_pass/services/example_reset_pass_service.dart'; +import 'package:deriv_auth/features/reset_password/presentation/layouts/deriv_reset_pass_layout.dart'; +import 'package:deriv_auth/features/reset_password/services/base_reset_password_service.dart'; +import 'package:deriv_auth/features/single_entry/features/reset_pass/pages/choose_new_password_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -14,6 +14,22 @@ class ResetPassPage extends StatefulWidget { State createState() => _ResetPassPageState(); } +class ExampleResetPasswordService extends BaseResetPasswordService { + @override + Future resetPassword( + {required String verificationCode, required String newPassword}) { + // TODO: implement resetPassword + throw UnimplementedError(); + } + + @override + Future sendVerificationEmail( + VerifyEmailRequestEntity request) { + // TODO: implement sendVerificationEmail + throw UnimplementedError(); + } +} + class _ResetPassPageState extends State { late final DerivResetPassCubit cubit; late final StreamSubscription _streamSubscription; @@ -22,7 +38,7 @@ class _ResetPassPageState extends State { void initState() { super.initState(); - cubit = DerivResetPassCubit(service: ExampleResetPassService()); + cubit = DerivResetPassCubit(service: ExampleResetPasswordService()); _streamSubscription = cubit.stream.listen((state) { if (state is DerivResetPassEmailSentState) { @@ -30,7 +46,7 @@ class _ResetPassPageState extends State { if (mounted) { Navigator.of(context).push(MaterialPageRoute( builder: (context) => BlocProvider.value( - value: cubit, child: ChooseNewPasswordPage(cubit: cubit)), + value: cubit, child: const ChooseNewPasswordPage(token: '')), )); } }); @@ -41,9 +57,13 @@ class _ResetPassPageState extends State { @override Widget build(BuildContext context) { return BlocProvider.value( - value: cubit, child: DerivResetPassLayout(onResetPassError: (_) {})); + value: cubit, + child: DerivResetPassLayout(onResetPassError: onResetPassError), + ); } + onResetPassError({required bool isLinkExpired, String? error}) {} + @override void dispose() { cubit.close(); diff --git a/packages/deriv_auth/example/lib/features/reset_pass/pages/reset_pass_success_page.dart b/packages/deriv_auth/example/lib/features/reset_pass/pages/reset_pass_success_page.dart new file mode 100644 index 000000000..0857ba275 --- /dev/null +++ b/packages/deriv_auth/example/lib/features/reset_pass/pages/reset_pass_success_page.dart @@ -0,0 +1,50 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth/features/single_entry/features/get_started/pages/get_started_page.dart'; +import 'package:deriv_auth/features/single_entry/features/login/pages/login_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class ResetPassSuccessPage extends StatefulWidget { + const ResetPassSuccessPage({super.key}); + + @override + State createState() => _ResetPassSuccessPageState(); +} + +class _ResetPassSuccessPageState extends State { + static const Duration _successPageHoldDuration = Duration(seconds: 2); + + @override + void initState() { + super.initState(); + + // wait for either [_successPageHoldDuration] or logout to finish + // then navigate to loginPage + Future.wait( + >[ + Future.delayed(_successPageHoldDuration), + BlocProvider.of(context).logout(), + ], + ).then( + (_) { + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (context) => const GetStartedPage(), + ), + (route) => false, + ); + + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const LoginPage(), + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return const DerivSuccessPassChangeLayout(); + } +} diff --git a/packages/deriv_auth_ui/example/lib/features/reset_pass/services/example_reset_pass_service.dart b/packages/deriv_auth/example/lib/features/reset_pass/services/example_reset_pass_service.dart similarity index 100% rename from packages/deriv_auth_ui/example/lib/features/reset_pass/services/example_reset_pass_service.dart rename to packages/deriv_auth/example/lib/features/reset_pass/services/example_reset_pass_service.dart diff --git a/packages/deriv_auth/example/lib/features/settings/pages/settings_page.dart b/packages/deriv_auth/example/lib/features/settings/pages/settings_page.dart new file mode 100644 index 000000000..6f8054f71 --- /dev/null +++ b/packages/deriv_auth/example/lib/features/settings/pages/settings_page.dart @@ -0,0 +1,29 @@ +import 'dart:developer' as logger; +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:flutter/material.dart'; + +class SettingsPage extends StatefulWidget { + const SettingsPage({super.key}); + + @override + State createState() => _SettingsPageState(); +} + +class _SettingsPageState extends State { + @override + Widget build(BuildContext context) => const DerivSettingLayout( + updateFlavorConfigs: _updateFlavorConfig, + appLabel: 'appLabel', + saveValues: _saveValues, + ); +} + +_saveValues({required String appId, required String endpoint}) { + logger.log('appId is $appId'); + logger.log('endpoint is $endpoint'); +} + +Future _updateFlavorConfig( + {required String appId, required String endpoint}) async { + logger.log('Flavor CONFIG is updated'); +} diff --git a/packages/deriv_auth_ui/example/lib/features/signup/pages/country_selection_page.dart b/packages/deriv_auth/example/lib/features/signup/pages/country_selection_page.dart similarity index 74% rename from packages/deriv_auth_ui/example/lib/features/signup/pages/country_selection_page.dart rename to packages/deriv_auth/example/lib/features/signup/pages/country_selection_page.dart index f3000d32d..838961327 100644 --- a/packages/deriv_auth_ui/example/lib/features/signup/pages/country_selection_page.dart +++ b/packages/deriv_auth/example/lib/features/signup/pages/country_selection_page.dart @@ -1,5 +1,6 @@ -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; -import 'package:example/features/signup/pages/set_password_page.dart'; +import 'package:deriv_auth/features/signup/models/deriv_residence_model.dart'; +import 'package:deriv_auth/features/signup/presentation/layouts/deriv_country_selection_layout.dart'; +import 'package:deriv_auth/features/single_entry/features/signup/pages/set_password_page.dart'; import 'package:flutter/material.dart'; class CountrySelectionPage extends StatelessWidget { diff --git a/packages/deriv_auth_ui/example/lib/features/signup/pages/set_password_page.dart b/packages/deriv_auth/example/lib/features/signup/pages/set_password_page.dart similarity index 63% rename from packages/deriv_auth_ui/example/lib/features/signup/pages/set_password_page.dart rename to packages/deriv_auth/example/lib/features/signup/pages/set_password_page.dart index e009e632e..4fe8b51d0 100644 --- a/packages/deriv_auth_ui/example/lib/features/signup/pages/set_password_page.dart +++ b/packages/deriv_auth/example/lib/features/signup/pages/set_password_page.dart @@ -1,6 +1,4 @@ import 'package:deriv_auth/deriv_auth.dart'; -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; -import 'package:example/features/home/pages/home_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -12,16 +10,7 @@ class SetPasswordPage extends StatelessWidget { @override Widget build(BuildContext context) { return DerivSetPasswordLayout( - onDerivAuthState: (context, state) { - if (state is DerivAuthLoggedInState) { - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute( - builder: (context) => const HomePage(), - ), - (Route route) => false, - ); - } - }, + authErrorStateHandler: AuthErrorStateHandler(context: context), onDerivSignupState: (context, state) { if (state is DerivSignupDoneState) { context diff --git a/packages/deriv_auth_ui/example/lib/features/signup/pages/signup_page.dart b/packages/deriv_auth/example/lib/features/signup/pages/signup_page.dart similarity index 65% rename from packages/deriv_auth_ui/example/lib/features/signup/pages/signup_page.dart rename to packages/deriv_auth/example/lib/features/signup/pages/signup_page.dart index c4667ba7f..ff6a14605 100644 --- a/packages/deriv_auth_ui/example/lib/features/signup/pages/signup_page.dart +++ b/packages/deriv_auth/example/lib/features/signup/pages/signup_page.dart @@ -1,6 +1,6 @@ -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; -import 'package:example/features/login/pages/login_page.dart'; -import 'package:example/features/signup/pages/verify_email_page.dart'; +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth/features/single_entry/features/login/pages/login_page.dart'; +import 'package:deriv_auth/features/single_entry/features/signup/pages/verify_email_page.dart'; import 'package:flutter/material.dart'; class SignupPage extends StatefulWidget { @@ -13,10 +13,14 @@ class SignupPage extends StatefulWidget { class _SignupPageState extends State { @override Widget build(BuildContext context) => DerivSignupLayout( + socialAuthStateHandler: (context, _) => {}, + redirectURL: 'deriv://example', + onWebViewError: (String error) {}, + authErrorStateHandler: AuthErrorStateHandler(context: context), signupPageLabel: 'Start trading with Deriv', signupPageDescription: 'Join over 1 million traders worldwide who loves trading at Deriv.', - onSocialAuthButtonPressed: (_) {}, + onSocialAuthButtonPressed: (SocialAuthDto socialAuthDto) {}, onSingupError: (_) {}, onSingupEmailSent: (email) { Navigator.pushReplacement( diff --git a/packages/deriv_auth_ui/example/lib/features/signup/pages/verification_done_page.dart b/packages/deriv_auth/example/lib/features/signup/pages/verification_done_page.dart similarity index 74% rename from packages/deriv_auth_ui/example/lib/features/signup/pages/verification_done_page.dart rename to packages/deriv_auth/example/lib/features/signup/pages/verification_done_page.dart index fe851c472..e7b1a42c2 100644 --- a/packages/deriv_auth_ui/example/lib/features/signup/pages/verification_done_page.dart +++ b/packages/deriv_auth/example/lib/features/signup/pages/verification_done_page.dart @@ -1,5 +1,5 @@ -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; -import 'package:example/features/signup/pages/country_selection_page.dart'; +import 'package:deriv_auth/features/signup/presentation/layouts/deriv_verification_done_layout.dart'; +import 'package:deriv_auth/features/single_entry/features/signup/pages/country_selection_page.dart'; import 'package:flutter/material.dart'; class VerificationDonePage extends StatelessWidget { diff --git a/packages/deriv_auth_ui/example/lib/features/signup/pages/verify_email_page.dart b/packages/deriv_auth/example/lib/features/signup/pages/verify_email_page.dart similarity index 81% rename from packages/deriv_auth_ui/example/lib/features/signup/pages/verify_email_page.dart rename to packages/deriv_auth/example/lib/features/signup/pages/verify_email_page.dart index 326d8ce97..cbd5a43a5 100644 --- a/packages/deriv_auth_ui/example/lib/features/signup/pages/verify_email_page.dart +++ b/packages/deriv_auth/example/lib/features/signup/pages/verify_email_page.dart @@ -1,7 +1,8 @@ import 'dart:async'; -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; -import 'package:example/features/signup/pages/verification_done_page.dart'; +import 'package:deriv_auth/features/signup/presentation/layouts/deriv_email_not_received_layout.dart'; +import 'package:deriv_auth/features/signup/presentation/layouts/deriv_verify_email_layout.dart'; +import 'package:deriv_auth/features/single_entry/features/signup/pages/verification_done_page.dart'; import 'package:flutter/material.dart'; class VerifyEmailPage extends StatefulWidget { diff --git a/packages/deriv_auth_ui/example/lib/features/signup/repositories/example_referral_repository.dart b/packages/deriv_auth/example/lib/features/signup/repositories/example_referral_repository.dart similarity index 100% rename from packages/deriv_auth_ui/example/lib/features/signup/repositories/example_referral_repository.dart rename to packages/deriv_auth/example/lib/features/signup/repositories/example_referral_repository.dart diff --git a/packages/deriv_auth_ui/example/lib/features/signup/repositories/example_signup_repository.dart b/packages/deriv_auth/example/lib/features/signup/repositories/example_signup_repository.dart similarity index 100% rename from packages/deriv_auth_ui/example/lib/features/signup/repositories/example_signup_repository.dart rename to packages/deriv_auth/example/lib/features/signup/repositories/example_signup_repository.dart diff --git a/packages/deriv_auth/example/lib/features/social_auth/deriv_auth_connection_info.dart b/packages/deriv_auth/example/lib/features/social_auth/deriv_auth_connection_info.dart new file mode 100644 index 000000000..4bc773d42 --- /dev/null +++ b/packages/deriv_auth/example/lib/features/social_auth/deriv_auth_connection_info.dart @@ -0,0 +1,11 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:flutter_deriv_api/state/connection/connection_cubit.dart'; + +/// Class that contains ConnectionInfo +class DerivAuthConnectionInfo implements AuthConnectionInfo { + @override + String get appId => ConnectionCubit.appId; + + @override + String get endpoint => ConnectionCubit.endpoint; +} diff --git a/packages/deriv_auth/example/lib/main.dart b/packages/deriv_auth/example/lib/main.dart new file mode 100644 index 000000000..f3283433a --- /dev/null +++ b/packages/deriv_auth/example/lib/main.dart @@ -0,0 +1,94 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_http_client/deriv_http_client.dart'; +import 'package:deriv_passkeys/deriv_passkeys.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_localizations/l10n/generated/deriv_auth/deriv_auth_localizations.dart'; +import 'package:device_preview/device_preview.dart'; +import 'package:example/features/get_started/pages/get_started_page.dart'; +import 'package:example/features/login/repositories/example_login_repository.dart'; +import 'package:example/features/signup/repositories/example_referral_repository.dart'; +import 'package:example/features/signup/repositories/example_signup_repository.dart'; +import 'package:example/features/social_auth/deriv_auth_connection_info.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'features/login/services/example_login_service.dart'; + +void main() { + /// Added [DevicePreview] while fixing on UI issue + /// that was only in smaller devices. This later can be removed + /// when we are fully using widget book. + runApp( + DevicePreview( + builder: (context) => const MyApp(), + ), + ); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => DerivAuthCubit( + authService: ExampleLoginService( + authRepository: ExampleLoginRepository(), + jwtService: DerivJwtService( + repository: DerivJwtRepository( + client: HttpClient(), + connectionInfo: DerivAuthConnectionInfo(), + getAppToken: ()=>'test_app_token', + ), + ), + connectionInfo: DerivAuthConnectionInfo(), + tokenService: DerivTokenService(), + ), + ), + ), + BlocProvider( + create: (context) => DerivSignupCubit( + service: DerivSignupService(repository: ExampleSignupRepository()), + referralService: ExampleReferralRepository(), + ), + ), + BlocProvider( + create: (context) => SocialAuthCubit( + socialAuthService: DerivSocialAuthService( + client: HttpClient(), + connectionInfo: DerivAuthConnectionInfo(), + ), + ), + ), + BlocProvider( + create: (context) => DerivPasskeysBloc( + getJwtToken: () async => "jwtToken", + derivPasskeysService: DerivPasskeysService( + DerivPasskeysRepository( + DerivPasskeysDataSource( + mapper: DerivPasskeysMapper(), + client: HttpClient(), + ), + ), + ), + connectionInfo: PasskeysConnectionInfoEntity( + appId: DerivAuthConnectionInfo().appId, + endpoint: DerivAuthConnectionInfo().endpoint, + ), + ), + ), + ], + child: DerivThemeProvider.builder( + initialTheme: ThemeMode.dark, + builder: (context) => MaterialApp( + theme: context.themeData, + builder: DevicePreview.appBuilder, + localizationsDelegates: const [DerivAuthLocalizations.delegate], + home: const GetStartedPage(), + ), + ), + ); + } +} diff --git a/packages/deriv_auth_ui/example/pubspec.yaml b/packages/deriv_auth/example/pubspec.yaml similarity index 53% rename from packages/deriv_auth_ui/example/pubspec.yaml rename to packages/deriv_auth/example/pubspec.yaml index d72faeb0d..0ccf29d07 100644 --- a/packages/deriv_auth_ui/example/pubspec.yaml +++ b/packages/deriv_auth/example/pubspec.yaml @@ -12,21 +12,18 @@ dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2 - deriv_auth_ui: - path: ../ deriv_auth: - git: - url: git@github.com:regentmarkets/flutter-deriv-packages.git - path: packages/deriv_auth - ref: d5bef178341a318f911b42b8105d8c3d50c6b63e - deriv_theme: - path: ../../deriv_theme - # git: - # url: git@github.com:regentmarkets/flutter-deriv-packages.git - # path: packages/deriv_theme - # ref: dev + path: .././ + flutter_bloc: ^8.1.3 http: ^0.13.6 + device_preview: ^1.1.0 + +dependency_overrides: + flutter_deriv_api: + git: + url: git@github.com:deriv-com/flutter-deriv-api.git + ref: v1.3.0 dev_dependencies: flutter_test: diff --git a/packages/deriv_auth/lib/core/analytics/data/auth_tracking_data.dart b/packages/deriv_auth/lib/core/analytics/data/auth_tracking_data.dart new file mode 100644 index 000000000..9ac3b2559 --- /dev/null +++ b/packages/deriv_auth/lib/core/analytics/data/auth_tracking_data.dart @@ -0,0 +1,164 @@ +import 'package:deriv_auth/core/analytics/enums.dart'; + +/// Get user tracking data. +Map getUserTrackingData( + LoginAction action, + String appId, { + required LoginProvider provider, + String? errorMessage, +}) => + switch (action) { + LoginAction.openLoginForm => { + 'event': 'open', + 'properties': { + 'app_id': appId, + }, + }, + LoginAction.startLogin => _getLoginStartedTrackingData( + provider, + appId, + ), + LoginAction.finishLogin => _getLoginFinishedTrackingData( + provider, + appId, + ), + LoginAction.loginError => _getLoginErrorTrackingData( + provider, + appId, + errorMessage ?? (throw Exception('Error message is required.')), + ), + }; + +/// Get login started tracking data. +Map _getLoginStartedTrackingData( + LoginProvider? provider, String appId) => + switch (provider) { + LoginProvider.email => { + 'event': 'login_started', + 'properties': { + 'app_id': appId, + 'login_provider': 'email', + }, + }, + LoginProvider.passkey => { + 'event': 'login_started', + 'properties': { + 'app_id': appId, + 'login_provider': 'passkey', + }, + }, + LoginProvider.google => { + 'event': 'login_started', + 'properties': { + 'app_id': appId, + 'login_provider': 'google', + }, + }, + LoginProvider.facebook => { + 'event': 'login_started', + 'properties': { + 'app_id': appId, + 'login_provider': 'facebook', + }, + }, + LoginProvider.apple => { + 'event': 'login_started', + 'properties': { + 'app_id': appId, + 'login_provider': 'apple', + }, + }, + null => throw Exception('Null LoginProvider is being passed.'), + }; + +/// Get login finished tracking data. +Map _getLoginFinishedTrackingData( + LoginProvider? provider, String appId) => + switch (provider) { + LoginProvider.email => { + 'event': 'login_finished', + 'properties': { + 'app_id': appId, + 'login_provider': 'email', + }, + }, + LoginProvider.passkey => { + 'event': 'login_finished', + 'properties': { + 'app_id': appId, + 'login_provider': 'passkey', + }, + }, + LoginProvider.google => { + 'event': 'login_finished', + 'properties': { + 'app_id': appId, + 'login_provider': 'google', + }, + }, + LoginProvider.facebook => { + 'event': 'login_finished', + 'properties': { + 'app_id': appId, + 'login_provider': 'facebook', + }, + }, + LoginProvider.apple => { + 'event': 'login_finished', + 'properties': { + 'app_id': appId, + 'login_provider': 'apple', + }, + }, + null => throw Exception('Null LoginProvider is being passed.'), + }; + +/// Get login error tracking data. +Map _getLoginErrorTrackingData( + LoginProvider? provider, + String appId, + String errorMessage, +) => + switch (provider) { + LoginProvider.email => { + 'event': 'login_flow_error', + 'properties': { + 'app_id': appId, + 'login_provider': 'email', + 'error_message': errorMessage, + }, + }, + LoginProvider.passkey => { + 'event': 'login_flow_error', + 'properties': { + 'app_id': appId, + 'login_provider': 'passkey', + 'error_message': errorMessage, + }, + }, + LoginProvider.google => { + 'event': 'login_flow_error', + 'properties': { + 'app_id': appId, + 'login_provider': 'google', + 'error_message': errorMessage, + }, + }, + LoginProvider.facebook => { + 'event': 'login_flow_error', + 'properties': { + 'app_id': appId, + 'login_provider': 'facebook', + 'error_message': errorMessage, + }, + }, + LoginProvider.apple => { + 'event': 'login_flow_error', + 'properties': { + 'app_id': appId, + 'login_provider': 'apple', + 'error_message': errorMessage, + }, + }, + null => throw Exception('Null LoginProvider is being passed.'), + }; diff --git a/packages/deriv_auth/lib/core/analytics/data/auth_tracking_repository.dart b/packages/deriv_auth/lib/core/analytics/data/auth_tracking_repository.dart new file mode 100644 index 000000000..b10449b1c --- /dev/null +++ b/packages/deriv_auth/lib/core/analytics/data/auth_tracking_repository.dart @@ -0,0 +1,144 @@ +import 'package:analytics/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart'; +import 'package:deriv_auth/core/services/token/models/enums.dart'; +import 'package:deriv_auth/core/analytics/data/auth_tracking_data.dart'; +import 'package:deriv_auth/core/analytics/domain/auth_user_tracking_interface.dart'; +import 'package:deriv_auth/core/analytics/enums.dart'; + +/// Repository to track user events. +class AuthTrackingRepository implements AuthUserTrackingInterface { + /// Constructor for [AnalyticsRepository]. + AuthTrackingRepository._( + this._appId, + this._derivRudderstack, + ); + + static AuthTrackingRepository? _instance; + + /// Singleton instance of [AnalyticsRepository]. + static AuthTrackingRepository get instance => _instance ??= + throw Exception('AuthTrackingRepository is not initialized'); + + /// Instance of [DerivRudderstack]. + final DerivRudderstack _derivRudderstack; + + /// Initialize [AnalyticsRepository]. + static void init( + String appId, { + required DerivRudderstack derivRudderstack, + }) => + _instance = AuthTrackingRepository._( + appId, + derivRudderstack, + ); + + /// Deriv client app ID. + final String _appId; + + LoginProvider? _loginProvider; + + @override + void trackUserOpenedLoginForm() { + if (_loginProvider == null) { + return; + } + + final Map data = getUserTrackingData( + LoginAction.openLoginForm, + _appId, + provider: _loginProvider!, + ); + _derivRudderstack.track( + eventName: data['event'] as String, + properties: data['properties'] as Map, + ); + } + + @override + void trackError(String errorMessage) { + if (_loginProvider == null) { + return; + } + + final Map data = getUserTrackingData( + LoginAction.loginError, + _appId, + provider: _loginProvider!, + errorMessage: errorMessage, + ); + + _derivRudderstack.track( + eventName: data['event'] as String, + properties: data['properties'] as Map, + ); + } + + @override + void trackSystemLoginPressed() { + final Map data = getUserTrackingData( + LoginAction.startLogin, + _appId, + provider: LoginProvider.email, + ); + + _loginProvider = LoginProvider.email; + + _derivRudderstack.track( + eventName: data['event'] as String, + properties: data['properties'] as Map, + ); + } + + @override + void trackPasskeyLoginPressed() { + final Map data = getUserTrackingData( + LoginAction.startLogin, + _appId, + provider: LoginProvider.passkey, + ); + + _loginProvider = LoginProvider.passkey; + + _derivRudderstack.track( + eventName: data['event'] as String, + properties: data['properties'] as Map, + ); + } + + @override + void trackSocialLoginPressed(SocialAuthProvider type) { + _loginProvider = switch (type) { + SocialAuthProvider.google => LoginProvider.google, + SocialAuthProvider.facebook => LoginProvider.facebook, + SocialAuthProvider.apple => LoginProvider.apple, + }; + + final Map data = getUserTrackingData( + LoginAction.startLogin, + _appId, + provider: _loginProvider!, + ); + + _derivRudderstack.track( + eventName: data['event'] as String, + properties: data['properties'] as Map, + ); + } + + @override + void trackLoginFinished() { + if (_loginProvider == null) { + return; + } + + final Map data = getUserTrackingData( + LoginAction.finishLogin, + _appId, + provider: _loginProvider!, + ); + + _derivRudderstack.track( + eventName: data['event'] as String, + properties: data['properties'] as Map, + ); + } +} diff --git a/packages/deriv_auth/lib/core/analytics/domain/auth_user_tracking_interface.dart b/packages/deriv_auth/lib/core/analytics/domain/auth_user_tracking_interface.dart new file mode 100644 index 000000000..3db7ab4f6 --- /dev/null +++ b/packages/deriv_auth/lib/core/analytics/domain/auth_user_tracking_interface.dart @@ -0,0 +1,24 @@ +import 'package:deriv_auth/deriv_auth.dart'; + +/// Interface to define the methods for tracking user events. +abstract interface class AuthUserTrackingInterface { + /// Track user opened login form. + void trackUserOpenedLoginForm(); + + /// Track user clicks log after entering email and password. + void trackSystemLoginPressed(); + + /// Track user clicks on Passkey login button. + void trackPasskeyLoginPressed(); + + /// Track user clicks on social login button. + void trackSocialLoginPressed( + SocialAuthProvider type, + ); + + /// Track login finished. + void trackLoginFinished(); + + /// Track login error. + void trackError(String errorMessage); +} diff --git a/packages/deriv_auth/lib/core/analytics/enums.dart b/packages/deriv_auth/lib/core/analytics/enums.dart new file mode 100644 index 000000000..7d0d6c118 --- /dev/null +++ b/packages/deriv_auth/lib/core/analytics/enums.dart @@ -0,0 +1,32 @@ +/// Enum for login actions +enum LoginAction { + /// Open login form. + openLoginForm, + + /// Start system login. + startLogin, + + /// Finish login. + finishLogin, + + /// Login error. + loginError, +} + +/// Enum for login provider. +enum LoginProvider { + /// Email login. + email, + + /// Passkey login. + passkey, + + /// Google login. + google, + + /// Facebook login. + facebook, + + /// Apple login. + apple, +} diff --git a/packages/deriv_auth/lib/core/analytics/service/auth_tracking_mixin.dart b/packages/deriv_auth/lib/core/analytics/service/auth_tracking_mixin.dart new file mode 100644 index 000000000..5c5c931de --- /dev/null +++ b/packages/deriv_auth/lib/core/analytics/service/auth_tracking_mixin.dart @@ -0,0 +1,36 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth/core/analytics/data/auth_tracking_repository.dart'; + +/// Mixin for tracking user authentication actions. +mixin AuthTrackingMixin { + late final AuthTrackingRepository _repository = + AuthTrackingRepository.instance; + + /// Track user opened login form. + void trackUserOpenedLoginForm() => _repository.trackUserOpenedLoginForm(); + + /// Track login error. + void trackError(String errorMessage) => _repository.trackError(errorMessage); + + /// Track login started. + void trackLoginWithEmailAndPassword() => + _repository.trackSystemLoginPressed(); + + /// Track Passkey login started. + void trackLoginWithPasskey() => _repository.trackPasskeyLoginPressed(); + + /// Track Google login started. + void trackLoginWithGoogle() => + _repository.trackSocialLoginPressed(SocialAuthProvider.google); + + /// Track Facebook login started. + void trackLoginWithFacebook() => + _repository.trackSocialLoginPressed(SocialAuthProvider.facebook); + + /// Track Apple login started. + void trackLoginWithApple() => + _repository.trackSocialLoginPressed(SocialAuthProvider.apple); + + /// Track login finished. + void trackLoginFinished() => _repository.trackLoginFinished(); +} diff --git a/packages/deriv_auth/lib/core/constants/constants.dart b/packages/deriv_auth/lib/core/constants/constants.dart index 0b81b3504..768c4a5e6 100644 --- a/packages/deriv_auth/lib/core/constants/constants.dart +++ b/packages/deriv_auth/lib/core/constants/constants.dart @@ -61,3 +61,6 @@ const String accountOpeningEmailType = 'account_opening'; /// Supported date format when signing up. const String dateFormat = 'yyyy-MM-dd'; + +/// Reset password invalid link error +const String resetPasswordInvalidLinkError = 'InvalidToken'; diff --git a/packages/deriv_auth/lib/core/core.dart b/packages/deriv_auth/lib/core/core.dart new file mode 100644 index 000000000..d90ac1ac4 --- /dev/null +++ b/packages/deriv_auth/lib/core/core.dart @@ -0,0 +1,16 @@ +export 'connection_info.dart'; +export 'exceptions/deriv_auth_exception.dart'; +export 'extensions/extensions.dart'; +export 'layouts/deriv_unavailable_country_layout.dart'; +export 'models/account_model.dart'; +export 'models/auth_error/auth_error.dart'; +export 'models/auth_error/auth_error_model.dart'; +export 'models/authorize_model.dart'; +export 'models/verify_email_model.dart'; +export 'services/jwt/repository/deriv_jwt_repository.dart'; +export 'services/jwt/services/deriv_jwt_service.dart'; +export 'services/referral/models/my_affiliate_referral_code_request_model.dart'; +export 'services/token/models/enums.dart'; +export 'services/token/services/deriv_token_service.dart'; +export 'states/states.dart'; +export 'helpers/helpers.dart'; diff --git a/packages/deriv_auth/lib/core/extensions/authorize_extensions.dart b/packages/deriv_auth/lib/core/extensions/authorize_extensions.dart index 02d7dd01b..9e7249683 100644 --- a/packages/deriv_auth/lib/core/extensions/authorize_extensions.dart +++ b/packages/deriv_auth/lib/core/extensions/authorize_extensions.dart @@ -1,4 +1,4 @@ -import 'package:deriv_auth/core/models/account_model.dart'; +import 'package:deriv_auth/deriv_auth.dart'; /// Extensions on [AccountModel]. extension AccountModelExtension on AccountModel { @@ -9,3 +9,17 @@ extension AccountModelExtension on AccountModel { accountId.toUpperCase().contains('VRTC') || accountId.toUpperCase().contains('VRW'); } + +/// Extensions on [AccountModel]. +extension AccountListItemExtension on AccountListItem { + /// Check if [AccountModel] is supported or not. + bool get isSupported => + + // CR will also cover CRW. and loginid should always be present. + loginid!.toUpperCase().contains('CR') || + loginid!.toUpperCase().contains('VRTC') || + loginid!.toUpperCase().contains('VRW'); + + /// checks if [AccountListItem] is disabled or not. + bool get isNotDisabled => !isDisabled!; +} diff --git a/packages/deriv_auth/lib/core/extensions/context_extension.dart b/packages/deriv_auth/lib/core/extensions/context_extension.dart new file mode 100644 index 000000000..d239c5090 --- /dev/null +++ b/packages/deriv_auth/lib/core/extensions/context_extension.dart @@ -0,0 +1,9 @@ +import 'package:deriv_localizations/l10n/generated/deriv_auth/deriv_auth_localizations.dart'; +import 'package:flutter/material.dart'; + +/// Extension methods for [BuildContext]. +extension ContextExtension on BuildContext { + /// Gets [DerivAuthLocalizations]. + DerivAuthLocalizations get derivAuthLocalization => + DerivAuthLocalizations.of(this)!; +} diff --git a/packages/deriv_auth/lib/core/extensions/extensions.dart b/packages/deriv_auth/lib/core/extensions/extensions.dart index e1d16d2c7..da2356fa7 100644 --- a/packages/deriv_auth/lib/core/extensions/extensions.dart +++ b/packages/deriv_auth/lib/core/extensions/extensions.dart @@ -1,3 +1,4 @@ export 'account_extensions.dart'; export 'authorize_extensions.dart'; export 'string_extensions.dart'; +export 'context_extension.dart'; diff --git a/packages/deriv_auth/lib/core/extensions/string_extensions.dart b/packages/deriv_auth/lib/core/extensions/string_extensions.dart index 6943c2421..dc5727920 100644 --- a/packages/deriv_auth/lib/core/extensions/string_extensions.dart +++ b/packages/deriv_auth/lib/core/extensions/string_extensions.dart @@ -1,3 +1,5 @@ +import 'package:deriv_ui/utils/regex_helpers.dart'; + /// String extensions. extension StringExtension on String { /// Returns all contents between first XML [tag]. @@ -10,3 +12,22 @@ extension StringExtension on String { (Match match) => '_${match[0]}', ).toLowerCase(); } + +/// Extension methods for input validation on [String] using regex. +extension RegexExtension on String { + /// Indicates whether the input is a valid email. + bool get isValidEmail => validEmailRegex.hasMatch(this); + + /// Apart from signup (8-char password), login should support 6-char password as there will be legacy accounts having 6 chars. + bool get isValidLoginPasswordLength => + validLoginPasswordLengthRegex.hasMatch(this); + + /// Signup valid Password Regex. + bool get isValidSignupPassword => validPasswordRegex.hasMatch(this); +} + +/// Extension methods for capitalizing [String]. +extension Capitalize on String { + /// Capitalize the first letter of the string. + String get capitalize => '${this[0].toUpperCase()}${substring(1)}'; +} diff --git a/packages/deriv_auth_ui/lib/src/core/helpers/assets.dart b/packages/deriv_auth/lib/core/helpers/assets.dart similarity index 100% rename from packages/deriv_auth_ui/lib/src/core/helpers/assets.dart rename to packages/deriv_auth/lib/core/helpers/assets.dart diff --git a/packages/deriv_auth/lib/core/helpers/country_selection_helper.dart b/packages/deriv_auth/lib/core/helpers/country_selection_helper.dart new file mode 100644 index 000000000..06664331c --- /dev/null +++ b/packages/deriv_auth/lib/core/helpers/country_selection_helper.dart @@ -0,0 +1,86 @@ +import 'package:deriv_auth/core/extensions/extensions.dart'; +import 'package:flutter/material.dart'; + +/// Returns `true` if the country is not in the [notAllowedCountryCodes] set, +/// otherwise returns `false`. +bool isAllowedCountry({required String? countryCode}) => + !notAllowedCountryCodes.contains(countryCode); + +/// Returns `true` if consent is required for the given [countryCode], +/// otherwise returns `false`. +bool isConsentRequired({required String? countryCode}) { + if (countryCode == null) { + return false; + } + + return countriesRequiringConsent.contains(countryCode.toLowerCase()); +} + +/// Retrieves the consent message for a given country code. +/// +/// If the country requires consent, the corresponding consent message is returned. +/// Otherwise, an empty string is returned. +/// +/// When a new country is added to the set of [countriesRequiringConsent], +/// update the [consentMessages] map. +String getCountryConsentMessage( + BuildContext context, { + required String? countryCode, +}) { + if (countryCode == null) { + return ''; + } + + final Map consentMessages = { + 'br': context.derivAuthLocalization.labelCountryConsentBrazil, + // Add more countries and consent messages here. + }; + + return consentMessages[countryCode.toLowerCase()] ?? ''; +} + +/// Set of country codes that are not allowed to create an account. +const Set notAllowedCountryCodes = { + // MF country codes. + 'de', + 'es', + 'fr', + 'gr', + 'it', + 'lu', + 'mf', + // MLT country codes. + 'at', + 'be', + 'bg', + 'cy', + 'cz', + 'dk', + 'ee', + 'fi', + 'hr', + 'hu', + 'ie', + 'lt', + 'lv', + 'nl', + 'pl', + 'pt', + 'ro', + 'se', + 'si', + 'sk', + // MX country codes. + 'gb', + 'im', +}; + +/// [countriesRequiringConsent] is a set of country codes that are required +/// to show consent. +/// +/// If any country is required to show consent, add the country code +/// in lowercase to the set. +const Set countriesRequiringConsent = { + 'br', + // Add countries here. +}; diff --git a/packages/deriv_auth/lib/core/helpers/endpoint_helper.dart b/packages/deriv_auth/lib/core/helpers/endpoint_helper.dart new file mode 100644 index 000000000..c7ad6b961 --- /dev/null +++ b/packages/deriv_auth/lib/core/helpers/endpoint_helper.dart @@ -0,0 +1,36 @@ +/// Default auth endpoint. +const String defaultAuthEndpoint = 'oauth.deriv.com'; + +/// Default ws endpoint. +const String defaultEndpoint = 'blue.binaryws.com'; + +/// Default app id. +const String defaultAppId = '23789'; + +/// Parses an [endpoint] argument and generates a url that points to QA servers +/// if [endpoint] starts with `qa` or a url that points to production servers if +/// [endpoint] contains `derivws` or `binaryws` and [isAuthUrl] is `true`. +/// +/// [endpoint] argument is required. +/// [isAuthUrl] is optional and has a default value of `false`. +String? generateEndpointUrl({ + required String? endpoint, + bool isAuthUrl = false, +}) { + if (endpoint == null) { + return null; + } + + final RegExp qaRegExp = RegExp('^(qa[0-9]+)\$', caseSensitive: false); + final RegExp derivRegExp = + RegExp('(binary|deriv)ws\.(com|app)\$', caseSensitive: false); + + if (isAuthUrl && derivRegExp.hasMatch(endpoint)) { + // Since Deriv app is under Deriv.app, the oauth url should be always `oauth.deriv.com`. + return defaultAuthEndpoint; + } else if (qaRegExp.hasMatch(endpoint)) { + return '$endpoint.deriv.dev'; + } + + return endpoint; +} diff --git a/packages/deriv_auth/lib/core/helpers/helpers.dart b/packages/deriv_auth/lib/core/helpers/helpers.dart index ab5f4b441..bc80c9688 100644 --- a/packages/deriv_auth/lib/core/helpers/helpers.dart +++ b/packages/deriv_auth/lib/core/helpers/helpers.dart @@ -2,3 +2,5 @@ export 'account_helpers.dart'; export 'bool_helper.dart'; export 'date_time_helper.dart'; export 'number_helper.dart'; +export 'semantic_labels.dart'; +export 'validation_helper.dart'; diff --git a/packages/deriv_auth/lib/core/helpers/semantic_labels.dart b/packages/deriv_auth/lib/core/helpers/semantic_labels.dart new file mode 100644 index 000000000..b0f7e49df --- /dev/null +++ b/packages/deriv_auth/lib/core/helpers/semantic_labels.dart @@ -0,0 +1,41 @@ +/// A class that contains semantic labels for various UI elements in the auth UI flow. +class SemanticsLabels { + // Signup Page + + /// A semantic label for the email field on the signup page. + static String signupEmailFieldSemantic = 'signup_email_field_semantic'; + + /// A semantic label for the signup button on the signup page. + static String signupButtonSemantic = 'signup_button_semantic'; + + /// A semantic label for the referral text field on the signup page. + static String signupReferralTextFieldSemantic = + 'signup_referral_field_semantic'; + + // Login Page + + /// A semantic label for the email field on the login page. + static String loginEmailFieldSemantic = 'login_email_field_semantic'; + + /// A semantic label for the password field on the login page. + static String loginPasswordFieldSemantic = 'login_password_field_semantic'; + + // Reset Password Page + + /// A semantic label for the email field on the reset password page. + static String resetPasswordEmailFieldSemantic = + 'reset_password_email_field_semantic'; + + /// A semantic label for the reset password button on the reset password page. + static String resetPasswordButtonSemantic = 'reset_password_button_semantic'; + + // Starter Page + + /// A semantic label for the signup button on the starter page. + static String starterPageSignupButtonSemantic = + 'starter_page_signup_button_semantic'; + + /// A semantic label for the login button on the starter page. + static String starterPageLoginButtonSemantic = + 'starter_page_login_button_semantic'; +} diff --git a/packages/deriv_auth/lib/core/helpers/validation_helper.dart b/packages/deriv_auth/lib/core/helpers/validation_helper.dart new file mode 100644 index 000000000..8aa632fe8 --- /dev/null +++ b/packages/deriv_auth/lib/core/helpers/validation_helper.dart @@ -0,0 +1,26 @@ +import 'package:deriv_auth/deriv_auth.dart'; + +/// Validates the email input and returns an error message if invalid. +/// +/// If the input is empty or a valid email, the function returns `null`. +/// Otherwise, it returns the provided [errorMessage]. +String? emailValidator(String input, String errorMessage) { + if (input.isEmpty || input.isValidEmail) { + return null; + } + + return errorMessage; +} + +/// Validates the password input and returns an error message if invalid. +/// +/// If the input is empty or meets the valid password length criteria, +/// the function returns `null`. Otherwise, it returns the provided [errorMessage]. +/// +String? passwordValidator(String input, String errorMessage) { + if (input.isEmpty || (input.isValidLoginPasswordLength)) { + return null; + } + + return errorMessage; +} \ No newline at end of file diff --git a/packages/deriv_auth_ui/lib/src/core/layouts/deriv_unavailable_country_layout.dart b/packages/deriv_auth/lib/core/layouts/deriv_unavailable_country_layout.dart similarity index 86% rename from packages/deriv_auth_ui/lib/src/core/layouts/deriv_unavailable_country_layout.dart rename to packages/deriv_auth/lib/core/layouts/deriv_unavailable_country_layout.dart index 689ed4674..b00eedf59 100644 --- a/packages/deriv_auth_ui/lib/src/core/layouts/deriv_unavailable_country_layout.dart +++ b/packages/deriv_auth/lib/core/layouts/deriv_unavailable_country_layout.dart @@ -1,5 +1,5 @@ -import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; -import 'package:deriv_auth_ui/src/core/helpers/assets.dart'; +import 'package:deriv_auth/core/extensions/extensions.dart'; +import 'package:deriv_auth/core/helpers/assets.dart'; import 'package:deriv_theme/deriv_theme.dart'; import 'package:deriv_ui/deriv_ui.dart'; import 'package:deriv_ui/models/custom_icon_data_model.dart'; @@ -28,7 +28,7 @@ class DerivUnavailableCountryLayout extends StatelessWidget { Widget build(BuildContext context) => Scaffold( appBar: AppBar( elevation: ThemeProvider.zeroMargin, - title: Text(context.localization.labelNotAvailable), + title: Text(context.derivAuthLocalization.labelNotAvailable), ), body: Container( padding: @@ -50,14 +50,15 @@ class DerivUnavailableCountryLayout extends StatelessWidget { children: [ const SizedBox(height: ThemeProvider.margin16), Text( - context.localization.warnNotAvailableCountriesTitle(appName), + context.derivAuthLocalization + .warnNotAvailableCountriesTitle(appName), style: TextStyles.title, textAlign: TextAlign.center, ), const SizedBox(height: ThemeProvider.margin08), RichText( text: TextSpan( - text: context.localization.warnNotAvailableCountries, + text: context.derivAuthLocalization.warnNotAvailableCountries, style: context.theme.textStyle( textStyle: TextStyles.body1, color: context.theme.colors.general, @@ -65,7 +66,7 @@ class DerivUnavailableCountryLayout extends StatelessWidget { children: [ buildTextSpanHyperlink( context: context, - label: context.localization.labelLiveChat, + label: context.derivAuthLocalization.labelLiveChat, onTap: onLiveChatPressed, style: context.theme.textStyle( textStyle: TextStyles.body1Bold, diff --git a/packages/deriv_auth/lib/core/models/authorize_model.dart b/packages/deriv_auth/lib/core/models/authorize_model.dart index 7818116f7..21f8cefad 100644 --- a/packages/deriv_auth/lib/core/models/authorize_model.dart +++ b/packages/deriv_auth/lib/core/models/authorize_model.dart @@ -75,6 +75,7 @@ class AuthorizeResponseEntity extends AuthorizeResponseEntityModel { /// PlatformEnum mapper. final Map platformEnumMapper = { + 'ctrader': PlatformEnum.ctrader, 'derivez': PlatformEnum.derivez, 'dtrade': PlatformEnum.dtrade, 'dwallet': PlatformEnum.dwallet, @@ -84,6 +85,9 @@ final Map platformEnumMapper = { /// Platform Enum indicating the account is on which platform. enum PlatformEnum { + /// ctrader + ctrader, + /// derivez. derivez, @@ -264,17 +268,13 @@ class AuthorizeEntity extends AuthorizeModel { ), ), // TODOAuth(): fromjson(.tojson) wont work for localCurrencies . - // localCurrencies: json['local_currencies'] == null - // ? null - // : Map.fromEntries( - // json['local_currencies'] - // .entries - // .map>( - // (MapEntry entry) => - // MapEntry( - // entry.key, - // LocalCurrenciesPropertyEntity.fromJson( - // entry.value)))), + // fast fix: localCurrencies: json['local_currencies'] + // the tojson method in deriv-api not converting local_currencies + // properly so we need to fix the usage of these duplicate classes + // in deriv-auth and deriv-api + localCurrencies: json['local_currencies'] == null + ? null + : convertLocalCurrency(json), loginid: json['loginid'], preferredLanguage: json['preferred_language'], scopes: json['scopes'] == null @@ -295,6 +295,28 @@ class AuthorizeEntity extends AuthorizeModel { userId: json['user_id'], ); + /// Converts local_currencies + static Map? convertLocalCurrency( + Map json) { + // adding try catch for DerivGo as DerivP2P not having any issue with local_currencies + try { + return Map.fromEntries( + json['local_currencies'] + .entries + .map>( + (MapEntry entry) => + MapEntry( + entry.key, + LocalCurrenciesPropertyEntity.fromJson(entry.value), + ), + ), + ); + // ignore: avoid_catches_without_on_clauses + } catch (e) { + return null; + } + } + /// Converts an instance to JSON. Map toJson() { final Map resultMap = {}; @@ -537,7 +559,6 @@ class AccountListItem extends AccountListItemModel { ); } - /// Linked to item model class. abstract class LinkedToItemModel { /// Initializes Linked to item model class . @@ -722,7 +743,6 @@ class TradingLinkedToItem extends TradingLinkedToItemModel { ); } - /// Wallet linked to item2 model class. abstract class WalletLinkedToItem2Model { /// Initializes Wallet linked to item2 model class . diff --git a/packages/deriv_auth/lib/core/services/jwt/repository/deriv_jwt_repository.dart b/packages/deriv_auth/lib/core/services/jwt/repository/deriv_jwt_repository.dart index 44e89a237..f51c71bbd 100644 --- a/packages/deriv_auth/lib/core/services/jwt/repository/deriv_jwt_repository.dart +++ b/packages/deriv_auth/lib/core/services/jwt/repository/deriv_jwt_repository.dart @@ -13,7 +13,7 @@ class DerivJwtRepository implements BaseJwtRepository { DerivJwtRepository({ required this.client, required this.connectionInfo, - required this.appToken, + required this.getAppToken, }); /// Http client. @@ -23,7 +23,7 @@ class DerivJwtRepository implements BaseJwtRepository { final AuthConnectionInfo connectionInfo; /// Private client app token. - final String appToken; + final String Function() getAppToken; @override Future authorizeApp({ @@ -54,7 +54,7 @@ class DerivJwtRepository implements BaseJwtRepository { return AppAuthorizationChallengeResponseModel.fromJson( jsonResponse, - appToken: appToken, + appToken: getAppToken(), ); } diff --git a/packages/deriv_auth/lib/core/services/token/services/base_token_service.dart b/packages/deriv_auth/lib/core/services/token/services/base_token_service.dart index c9030276c..b9c4d7a82 100644 --- a/packages/deriv_auth/lib/core/services/token/services/base_token_service.dart +++ b/packages/deriv_auth/lib/core/services/token/services/base_token_service.dart @@ -1,15 +1,14 @@ import 'package:deriv_auth/core/connection_info.dart'; import 'package:deriv_auth/core/services/token/models/login_request.dart'; import 'package:deriv_auth/core/services/token/models/login_response.dart'; -import 'package:deriv_http_client/deriv_http_client.dart'; /// An interface for functionalities which a token service should provide. abstract class BaseTokenService { /// Fetches all account tokens of a single user + his single refresh token. Future getUserTokens({ required GetTokensRequestModel request, - required BaseHttpClient client, required String jwtToken, required AuthConnectionInfo connectionInfo, + String? userAgent, }); } diff --git a/packages/deriv_auth/lib/core/services/token/services/deriv_token_service.dart b/packages/deriv_auth/lib/core/services/token/services/deriv_token_service.dart index 949de9c0a..a2fa5129d 100644 --- a/packages/deriv_auth/lib/core/services/token/services/deriv_token_service.dart +++ b/packages/deriv_auth/lib/core/services/token/services/deriv_token_service.dart @@ -6,23 +6,35 @@ import 'package:deriv_http_client/deriv_http_client.dart'; /// Deriv Implementation of a [BaseTokenService]. class DerivTokenService implements BaseTokenService { + /// Create a [DerivTokenService] instance. + DerivTokenService([this.client]); + + /// The client to make the http request. + final BaseHttpClient? client; + @override Future getUserTokens({ required GetTokensRequestModel request, - required BaseHttpClient client, required String jwtToken, required AuthConnectionInfo connectionInfo, + BaseHttpClient? httpClient, + String? userAgent, }) async { /// Extract login url from connection info. final String baseUrl = 'https://${connectionInfo.endpoint}/oauth2/api/v1'; final String loginUrl = '$baseUrl/login'; + final BaseHttpClient httpClient = client ?? HttpClient(); + /// Call API. - final Map jsonResponse = await client.post( + final Map jsonResponse = await httpClient.post( url: loginUrl, jsonBody: request.copyWith(appId: int.parse(connectionInfo.appId)).toJson(), - headers: {'Authorization': 'Bearer $jwtToken'}, + headers: { + 'Authorization': 'Bearer $jwtToken', + 'User-Agent': userAgent ?? 'Dart/3.0 (dart:io)' + }, ); return GetTokensResponseModel.fromJson(jsonResponse); diff --git a/packages/deriv_auth/lib/core/states/auth_error_state_handler.dart b/packages/deriv_auth/lib/core/states/auth_error_state_handler.dart new file mode 100644 index 000000000..e473dfcc8 --- /dev/null +++ b/packages/deriv_auth/lib/core/states/auth_error_state_handler.dart @@ -0,0 +1,126 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; + +/// {@template default_auth_error_state_handler} +/// Base class for handling [DerivAuthErrorState]s. Client app can extend this +/// class and override the methods to handle the error states based on their +/// customization. +/// {@endtemplate} +base class AuthErrorStateHandler { + /// {@macro default_auth_error_state_handler} + AuthErrorStateHandler({ + required this.context, + }); + + /// The [BuildContext] of the widget that is using this handler. + final BuildContext context; + + /// On invalid 2FA code. + void invalid2faCode(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.derivAuthLocalization.informInvalid2FACode, + actionLabel: context.derivAuthLocalization.actionTryAgain, + ); + } + + /// On account is not activated. + void onAccountUnavailable(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.derivAuthLocalization.informDeactivatedAccount, + actionLabel: context.derivAuthLocalization.actionTryAgain, + ); + } + + /// On expired account. + void onExpiredAccount(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.derivAuthLocalization.informExpiredAccount, + actionLabel: context.derivAuthLocalization.actionTryAgain, + ); + } + + /// On failed authorization. + void onFailedAuthorization(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.derivAuthLocalization.informFailedAuthorization, + actionLabel: context.derivAuthLocalization.actionTryAgain, + ); + } + + /// User is trying to authenticate from an unsupported residence. + void onInvalidResidence(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.derivAuthLocalization.informInvalidResidence, + actionLabel: context.derivAuthLocalization.actionTryAgain, + ); + } + + /// On invalid credentials. + void onInvalidCredentials(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.derivAuthLocalization.informInvalidCredentials, + actionLabel: context.derivAuthLocalization.actionTryAgain, + ); + } + + /// User has set up 2FA and needs to enter 2FA. + void onMissingOtp(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.derivAuthLocalization.informMissingOtp, + actionLabel: context.derivAuthLocalization.actionTryAgain, + ); + } + + /// On self closed account. + void onSelfClosed(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.derivAuthLocalization.informSelfClosed, + actionLabel: context.derivAuthLocalization.actionTryAgain, + ); + } + + /// On unexpected error. + void onUnexpectedError(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.derivAuthLocalization.informUnexpectedError, + actionLabel: context.derivAuthLocalization.actionTryAgain, + ); + } + + /// Account is not supported in the country. + void onUnsupportedCountry(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.derivAuthLocalization.informUnsupportedCountry, + actionLabel: context.derivAuthLocalization.actionTryAgain, + ); + } + + /// On connection error. + void onConnectionError(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.derivAuthLocalization.informConnectionError, + actionLabel: context.derivAuthLocalization.actionTryAgain, + ); + } + + /// On switch account error. + void onSwitchAccountError(DerivAuthErrorState state) { + showErrorDialog( + context: context, + errorMessage: context.derivAuthLocalization.informSwitchAccountError, + actionLabel: context.derivAuthLocalization.actionTryAgain, + ); + } +} diff --git a/packages/deriv_auth/lib/core/states/auth_error_state_mapper.dart b/packages/deriv_auth/lib/core/states/auth_error_state_mapper.dart new file mode 100644 index 000000000..430d18fd7 --- /dev/null +++ b/packages/deriv_auth/lib/core/states/auth_error_state_mapper.dart @@ -0,0 +1,50 @@ +import 'package:deriv_auth/deriv_auth.dart'; + +/// Maps the [DerivAuthErrorState] to the corresponding [AuthErrorStateHandler]. +void authErrorStateMapper({ + required DerivAuthErrorState authErrorState, + required AuthErrorStateHandler authErrorStateHandler, + List? ignoredExceptions, +}) { + if (ignoredExceptions?.contains(authErrorState.type) ?? false) { + return; + } + switch (authErrorState.type) { + case AuthErrorType.missingOtp: + authErrorStateHandler.onMissingOtp(authErrorState); + return; + case AuthErrorType.selfClosed: + authErrorStateHandler.onSelfClosed(authErrorState); + return; + case AuthErrorType.unsupportedCountry: + authErrorStateHandler.onUnsupportedCountry(authErrorState); + return; + case AuthErrorType.accountUnavailable: + authErrorStateHandler.onAccountUnavailable(authErrorState); + return; + case AuthErrorType.invalidCredential: + authErrorStateHandler.onInvalidCredentials(authErrorState); + return; + case AuthErrorType.invalid2faCode: + authErrorStateHandler.invalid2faCode(authErrorState); + return; + case AuthErrorType.failedAuthorization: + authErrorStateHandler.onFailedAuthorization(authErrorState); + return; + case AuthErrorType.invalidResidence: + authErrorStateHandler.onInvalidResidence(authErrorState); + return; + case AuthErrorType.expiredAccount: + authErrorStateHandler.onExpiredAccount(authErrorState); + return; + case AuthErrorType.connectionError: + authErrorStateHandler.onConnectionError(authErrorState); + return; + case AuthErrorType.switchAccountError: + authErrorStateHandler.onSwitchAccountError(authErrorState); + return; + default: + authErrorStateHandler.onUnexpectedError(authErrorState); + return; + } +} diff --git a/packages/deriv_auth/lib/core/states/auth_state_listener.dart b/packages/deriv_auth/lib/core/states/auth_state_listener.dart new file mode 100644 index 000000000..9fb0e14f6 --- /dev/null +++ b/packages/deriv_auth/lib/core/states/auth_state_listener.dart @@ -0,0 +1,62 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +/// {@template auth_state_listener} +/// A [Widget] that listens to the [DerivAuthCubit] state changes. +/// This was created to make it easier to use [AuthErrorStateHandler] by +/// handling the mapping of auth error states with [AuthErrorStateHandler]'s method'. +/// {@endtemplate} +class DerivAuthStateListener extends StatelessWidget { + /// {@macro auth_state_listener} + const DerivAuthStateListener({ + required this.child, + super.key, + this.onLoggedIn, + this.onLoggedOut, + this.onLoading, + this.onError, + this.authErrorStateHandler, + }); + + /// The [Widget] that is using this [DerivAuthStateListener]. + final Widget child; + + /// Callback to be called when user is logged in. + final Function(DerivAuthLoggedInState)? onLoggedIn; + + /// Callback to be called when user is logged out. + final VoidCallback? onLoggedOut; + + /// Callback to be called when user is logging in. + final VoidCallback? onLoading; + + /// Callback to be called when an error occurs. + final Function(DerivAuthErrorState)? onError; + + /// Extension of base [AuthErrorStateHandler]. If not provided, base implementation will be used. + final AuthErrorStateHandler? authErrorStateHandler; + + @override + Widget build(BuildContext context) => + BlocListener( + listener: (BuildContext context, DerivAuthState state) { + if (state is DerivAuthLoggedInState) { + onLoggedIn?.call(state); + } else if (state is DerivAuthLoggedOutState) { + onLoggedOut?.call(); + } else if (state is DerivAuthLoadingState) { + onLoading?.call(); + } else if (state is DerivAuthErrorState) { + onError?.call(state); + + authErrorStateMapper( + authErrorState: state, + authErrorStateHandler: authErrorStateHandler ?? + AuthErrorStateHandler(context: context), + ); + } + }, + child: child, + ); +} diff --git a/packages/deriv_auth/lib/core/states/states.dart b/packages/deriv_auth/lib/core/states/states.dart new file mode 100644 index 000000000..3fcce922f --- /dev/null +++ b/packages/deriv_auth/lib/core/states/states.dart @@ -0,0 +1,3 @@ +export 'auth_error_state_mapper.dart'; +export 'auth_error_state_handler.dart'; +export 'auth_state_listener.dart'; diff --git a/packages/deriv_auth/lib/deriv_auth.dart b/packages/deriv_auth/lib/deriv_auth.dart index b01f8f76e..3f246a444 100644 --- a/packages/deriv_auth/lib/deriv_auth.dart +++ b/packages/deriv_auth/lib/deriv_auth.dart @@ -1,26 +1,2 @@ -export 'core/connection_info.dart'; -export 'core/exceptions/deriv_auth_exception.dart'; -export 'core/extensions/extensions.dart'; -export 'core/models/account_model.dart'; -export 'core/models/auth_error/auth_error.dart'; -export 'core/models/auth_error/auth_error_model.dart'; -export 'core/models/authorize_model.dart'; -export 'core/models/verify_email_model.dart'; -export 'core/services/jwt/repository/deriv_jwt_repository.dart'; -export 'core/services/jwt/services/deriv_jwt_service.dart'; -export 'core/services/token/models/enums.dart'; -export 'core/services/token/services/deriv_token_service.dart'; -export 'core/services/referral/models/my_affiliate_referral_code_request_model.dart'; -export 'features/auth/cubit/deriv_auth_cubit.dart'; -export 'features/auth/models/logout/logout_response.dart'; -export 'features/auth/repository/base_auth_repository.dart'; -export 'features/auth/services/deriv_auth_service.dart'; -export 'features/reset_password/cubit/reset_password_cubit.dart'; -export 'features/reset_password/repository/base_reset_password_repository.dart'; -export 'features/reset_password/services/deriv_reset_password_service.dart'; -export 'features/signup/cubit/signup_cubit.dart'; -export 'features/signup/models/new_virtual_account_request_model.dart'; -export 'features/signup/models/signup_error_type.dart'; -export 'features/signup/repository/base_signup_repository.dart'; -export 'features/signup/services/deriv_signup_service.dart'; -export 'features/social_auth/social_auth.dart'; +export 'core/core.dart'; +export 'features/features.dart'; diff --git a/packages/deriv_auth/lib/features/auth/auth.dart b/packages/deriv_auth/lib/features/auth/auth.dart new file mode 100644 index 000000000..5c56748d0 --- /dev/null +++ b/packages/deriv_auth/lib/features/auth/auth.dart @@ -0,0 +1,4 @@ +export 'cubit/deriv_auth_cubit.dart'; +export 'models/logout/logout_response.dart'; +export 'repository/base_auth_repository.dart'; +export 'services/deriv_auth_service.dart'; diff --git a/packages/deriv_auth/lib/features/auth/cubit/deriv_auth_cubit.dart b/packages/deriv_auth/lib/features/auth/cubit/deriv_auth_cubit.dart index d963069c9..2d58f0ad6 100644 --- a/packages/deriv_auth/lib/features/auth/cubit/deriv_auth_cubit.dart +++ b/packages/deriv_auth/lib/features/auth/cubit/deriv_auth_cubit.dart @@ -1,5 +1,7 @@ +import 'package:analytics/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart'; import 'package:bloc/bloc.dart'; - +import 'package:deriv_auth/core/analytics/data/auth_tracking_repository.dart'; +import 'package:deriv_auth/core/analytics/service/auth_tracking_mixin.dart'; import 'package:deriv_auth/core/exceptions/deriv_auth_exception.dart'; import 'package:deriv_auth/core/models/account_model.dart'; import 'package:deriv_auth/core/models/auth_error/auth_error.dart'; @@ -15,9 +17,18 @@ part 'deriv_auth_state.dart'; /// This Cubit is the single source of truth for user login status, /// and it is responsible for all login functionality. -class DerivAuthCubit extends Cubit implements DerivAuthIO { +class DerivAuthCubit extends Cubit + with AuthTrackingMixin + implements DerivAuthIO { /// Initialize a [DerivAuthCubit]. - DerivAuthCubit({required this.authService}) : super(DerivAuthLoadingState()); + DerivAuthCubit({ + required this.authService, + }) : super(DerivAuthLoadingState()) { + AuthTrackingRepository.init( + authService.connectionInfo.appId, + derivRudderstack: DerivRudderstack(), + ); + } /// [BaseAuthService] handles all login logic of cubit. final BaseAuthService authService; @@ -29,17 +40,23 @@ class DerivAuthCubit extends Cubit implements DerivAuthIO { required String email, required String password, String? otp, + String? userAgent, + bool useMultiToken = false, }) async { + trackLoginWithEmailAndPassword(); + emit(DerivAuthLoadingState()); await _loginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.system, email: email, password: password, otp: otp, ), - false, + isSocialLogin: false, + userAgent: userAgent, + useMultiToken: useMultiToken, ); } @@ -48,17 +65,21 @@ class DerivAuthCubit extends Cubit implements DerivAuthIO { required String oneAllConnectionToken, final String? signupProvider, String? otp, + String? userAgent, + bool useMultiToken = false, }) async { emit(DerivAuthLoadingState()); await _loginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.social, oneAllConnectionToken: oneAllConnectionToken, signupProvider: signupProvider, otp: otp, ), - true, + isSocialLogin: true, + userAgent: userAgent, + useMultiToken: useMultiToken, ); } @@ -66,16 +87,41 @@ class DerivAuthCubit extends Cubit implements DerivAuthIO { Future socialAuth({ required SocialAuthDto socialAuthDto, String? otp, + String? userAgent, + bool useMultiToken = false, }) async { emit(DerivAuthLoadingState()); await _loginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.socialLogin, socialAuthDto: socialAuthDto, otp: otp, ), - true, + isSocialLogin: true, + userAgent: userAgent, + useMultiToken: useMultiToken, + ); + } + + @override + Future multiTokenAuthorize(String? token) async { + emit(DerivAuthLoadingState()); + + final List accountList = + await authService.getLatestAccounts(); + final List tokenList = accountList + .where((AccountModel account) => account.token != null) + .map((AccountModel account) => account.token!) + .toList(); + + if (token != null && !tokenList.contains(token)) { + tokenList.add(token); + } + await _tokenLoginRequest( + 'MULTI', + tokenList: tokenList, + accounts: accountList, ); } @@ -89,14 +135,25 @@ class DerivAuthCubit extends Cubit implements DerivAuthIO { ); } - Future _loginRequest( - GetTokensRequestModel request, bool isSocialLogin) async { + Future _loginRequest({ + required GetTokensRequestModel request, + required bool isSocialLogin, + String? userAgent, + bool useMultiToken = false, + }) async { try { - final AuthorizeEntity authorizeEntity = - await authService.onLoginRequest(request); + final AuthorizeEntity authorizeEntity = await authService.onLoginRequest( + request: request, + userAgent: userAgent, + useMultiToken: useMultiToken, + ); + final LandingCompanyEntity landingCompanyEntity = await authService.getLandingCompany(authorizeEntity.country); _isUserMigrated = _checkUserMigrated(authorizeEntity); + + trackLoginFinished(); + emit(DerivAuthLoggedInState( DerivAuthModel( authorizeEntity: authorizeEntity, @@ -115,19 +172,25 @@ class DerivAuthCubit extends Cubit implements DerivAuthIO { Future _tokenLoginRequest( String token, { required List accounts, + List? tokenList, }) async { try { - final AuthorizeEntity authorizeEntity = - await authService.login(token, accounts: accounts); + final AuthorizeEntity authorizeEntity = await authService.login(token, + accounts: accounts, tokenList: tokenList); final LandingCompanyEntity landingCompanyEntity = await authService.getLandingCompany(authorizeEntity.country); _isUserMigrated = _checkUserMigrated(authorizeEntity); - emit(DerivAuthLoggedInState( - DerivAuthModel( - authorizeEntity: authorizeEntity, - landingCompany: landingCompanyEntity, + + trackLoginFinished(); + + emit( + DerivAuthLoggedInState( + DerivAuthModel( + authorizeEntity: authorizeEntity, + landingCompany: landingCompanyEntity, + ), ), - )); + ); } on DerivAuthException catch (error) { emit(DerivAuthErrorState( message: error.message, @@ -137,6 +200,37 @@ class DerivAuthCubit extends Cubit implements DerivAuthIO { } } + @override + Future multiAuthorizeAllAccounts() async { + emit(DerivAuthLoadingState()); + + List tokenList = []; + final List accountList = + await authService.getLatestAccounts(); + final String? defaultAccountToken = + (await authService.getDefaultAccount())?.token; + + if (defaultAccountToken == null) { + emit(DerivAuthLoggedOutState()); + + return; + } else { + tokenList = accountList + .where((AccountModel account) => account.token != null) + .map((AccountModel account) => account.token!) + .toList(); + + if (!tokenList.contains(defaultAccountToken)) { + tokenList.add(defaultAccountToken); + } + } + await _tokenLoginRequest( + 'MULTI', + accounts: accountList, + tokenList: tokenList, + ); + } + @override Future authorizeDefaultAccount() async { emit(DerivAuthLoadingState()); @@ -182,11 +276,26 @@ class DerivAuthCubit extends Cubit implements DerivAuthIO { bool get isMigratedToWallets => _isUserMigrated; bool _checkUserMigrated(AuthorizeEntity authorizeEntity) => - authorizeEntity.accountList?.any( - (AccountListItem account) => + authorizeEntity.accountList?.any((AccountListItem account) => account.accountCategory == AccountCategoryEnum.wallet) ?? - false; + false; @override Stream get output => stream; + + @override + void onChange(Change change) { + if (change.nextState is DerivAuthErrorState) { + trackError( + (change.nextState as DerivAuthErrorState).message, + ); + } + + super.onChange(change); + } + + /// This function clears the JWT token when needed. + void clearJwtToken() { + authService.jwtService.clearJwtToken(); + } } diff --git a/packages/deriv_auth/lib/features/auth/deriv_auth_io.dart b/packages/deriv_auth/lib/features/auth/deriv_auth_io.dart index 7a3f4b48d..5500ef95b 100644 --- a/packages/deriv_auth/lib/features/auth/deriv_auth_io.dart +++ b/packages/deriv_auth/lib/features/auth/deriv_auth_io.dart @@ -9,30 +9,45 @@ abstract class DerivAuthIO { required String email, required String password, String? otp, + bool useMultiToken = false, }); /// Social login/signup with [oneAllConnectionToken] using one-all service. /// Using it as fallback plan of [socialAuth]. Will be removed in the future. + @Deprecated( + 'OneAll has been removed so this method is no longer useful. Use socialAuth instead.') Future socialLogin({ required String oneAllConnectionToken, String? otp, + bool useMultiToken = false, }); /// Social login/signup using custom in-house service. Future socialAuth({ required SocialAuthDto socialAuthDto, String? otp, + bool useMultiToken = false, }); /// Log user in with [token] after reset password or sign up. + @deprecated Future tokenLogin(String token); + /// Log user in with multi token authorization + /// Add [token] to the list of authorized tokens. + /// And authorize the user with the new list of tokens. + Future multiTokenAuthorize(String? token); + /// Log user out. Future logout(); /// Log default user in. + @deprecated Future authorizeDefaultAccount(); + /// Uses multi authorization for all user accounts. + Future multiAuthorizeAllAccounts(); + /// Deriv auth output. Stream get output; } diff --git a/packages/deriv_auth/lib/features/auth/repository/base_auth_repository.dart b/packages/deriv_auth/lib/features/auth/repository/base_auth_repository.dart index 3d5aa3e5c..73e58b950 100644 --- a/packages/deriv_auth/lib/features/auth/repository/base_auth_repository.dart +++ b/packages/deriv_auth/lib/features/auth/repository/base_auth_repository.dart @@ -5,13 +5,18 @@ import 'package:deriv_auth/core/models/landig_comany_model.dart'; /// Interface of all authentication functions required from client. abstract class BaseAuthRepository { /// Authorize user with [token]. - Future authorize(String? token); + Future authorize( + String? token, { + List? tokenList, + }); /// Client functionality after user logs in. Future onLogin(AuthorizeEntity authorizeEntity); /// Log user out. - Future logout(); + Future logout({ + String? loginId, + }); /// Functionality on user logs out. Future onLogout(); diff --git a/packages/deriv_auth/lib/features/auth/services/base_auth_service.dart b/packages/deriv_auth/lib/features/auth/services/base_auth_service.dart index e8304cb80..7dd103c59 100644 --- a/packages/deriv_auth/lib/features/auth/services/base_auth_service.dart +++ b/packages/deriv_auth/lib/features/auth/services/base_auth_service.dart @@ -1,19 +1,46 @@ +import 'package:deriv_auth/core/connection_info.dart'; import 'package:deriv_auth/core/models/account_model.dart'; import 'package:deriv_auth/core/models/authorize_model.dart'; import 'package:deriv_auth/core/models/landig_comany_model.dart'; +import 'package:deriv_auth/core/services/jwt/services/base_jwt_service.dart'; import 'package:deriv_auth/core/services/token/models/login_request.dart'; +import 'package:deriv_auth/core/services/token/services/base_token_service.dart'; +import 'package:deriv_auth/features/auth/repository/base_auth_repository.dart'; /// Interface to define all authentication-related functionality. abstract class BaseAuthService { + /// Constructor for [BaseAuthService]. + BaseAuthService({ + required this.connectionInfo, + required this.jwtService, + required this.authRepository, + required this.tokenService, + }); + + /// Client connection info. + final AuthConnectionInfo connectionInfo; + + /// Interface for all jwtRelated functions. + final BaseJwtService jwtService; + + /// Interface of all client related functions. + final BaseAuthRepository authRepository; + + /// Token service. + final BaseTokenService tokenService; + /// Function before logging user in. - Future onLoginRequest( - GetTokensRequestModel request, - ); + Future onLoginRequest({ + required GetTokensRequestModel request, + String? userAgent, + bool useMultiToken = false, + }); /// Log in a user with [token]. Future login( String token, { required List accounts, + List? tokenList, String? signupProvider, String? refreshToken, }); diff --git a/packages/deriv_auth/lib/features/auth/services/deriv_auth_service.dart b/packages/deriv_auth/lib/features/auth/services/deriv_auth_service.dart index 3aabac973..46e09de5d 100644 --- a/packages/deriv_auth/lib/features/auth/services/deriv_auth_service.dart +++ b/packages/deriv_auth/lib/features/auth/services/deriv_auth_service.dart @@ -1,6 +1,4 @@ import 'package:collection/collection.dart'; - -import 'package:deriv_auth/core/connection_info.dart'; import 'package:deriv_auth/core/constants/constants.dart'; import 'package:deriv_auth/core/exceptions/deriv_auth_exception.dart'; import 'package:deriv_auth/core/extensions/extensions.dart'; @@ -8,11 +6,8 @@ import 'package:deriv_auth/core/models/account_model.dart'; import 'package:deriv_auth/core/models/auth_error/auth_error.dart'; import 'package:deriv_auth/core/models/authorize_model.dart'; import 'package:deriv_auth/core/models/landig_comany_model.dart'; -import 'package:deriv_auth/core/services/jwt/services/base_jwt_service.dart'; import 'package:deriv_auth/core/services/token/models/login_request.dart'; import 'package:deriv_auth/core/services/token/models/login_response.dart'; -import 'package:deriv_auth/core/services/token/services/base_token_service.dart'; -import 'package:deriv_auth/features/auth/repository/base_auth_repository.dart'; import 'package:deriv_http_client/deriv_http_client.dart'; import 'base_auth_service.dart'; @@ -21,56 +16,71 @@ import 'base_auth_service.dart'; class DerivAuthService extends BaseAuthService { /// Initializes a [DerivAuthService] class. DerivAuthService({ - required this.authRepository, - required this.jwtService, - required this.connectionInfo, - required this.tokenService, + required super.authRepository, + required super.jwtService, + required super.connectionInfo, + required super.tokenService, }); - /// Client connection info. - final AuthConnectionInfo connectionInfo; - - /// Interface for all jwtRelated functions. - final BaseJwtService jwtService; - - /// Interface of all client related functions. - final BaseAuthRepository authRepository; - - /// Token service. - final BaseTokenService tokenService; - @override - Future onLoginRequest( - GetTokensRequestModel request, [ + Future onLoginRequest({ + required GetTokensRequestModel request, + String? userAgent, Function? onInvalidJwtToken, - ]) async { + bool useMultiToken = false, + }) async { try { + List _tokenList = []; final String jwtToken = await jwtService.getJwtToken(); final GetTokensResponseModel _response = await tokenService.getUserTokens( request: request, - client: HttpClient(), jwtToken: jwtToken, connectionInfo: connectionInfo, + userAgent: userAgent, ); final List _supportedAccounts = _filterSupportedAccounts(_response.accounts); - final String? _defaultAccountToken = _supportedAccounts.first.token; - - if (_defaultAccountToken != null) { - return login( - _defaultAccountToken, - accounts: _supportedAccounts, - signupProvider: request.signupProvider, - refreshToken: _response.refreshToken, - ); + if (useMultiToken == false) { + final String? _defaultAccountToken = _supportedAccounts.isNotEmpty + ? _supportedAccounts.first.token + : null; + + if (_defaultAccountToken != null) { + return login( + _defaultAccountToken, + accounts: _supportedAccounts, + signupProvider: request.signupProvider, + refreshToken: _response.refreshToken, + ); + } else { + throw DerivAuthException( + message: accountUnavailableError, + type: AuthErrorType.accountUnavailable, + ); + } } else { - throw DerivAuthException( - message: accountUnavailableError, - type: AuthErrorType.accountUnavailable, - ); + if (_supportedAccounts.isNotEmpty) { + _tokenList = _supportedAccounts + .map((AccountModel account) => account.token) + .whereNotNull() + .toList(); + + return login( + 'MULTI', + tokenList: _tokenList.isEmpty ? null : _tokenList, + accounts: _supportedAccounts, + signupProvider: request.signupProvider, + refreshToken: _response.refreshToken, + ); + } else { + throw DerivAuthException( + message: accountUnavailableError, + type: AuthErrorType.accountUnavailable, + ); + } } } on HTTPClientException catch (error) { if (error.errorCode == invalidJwtTokenError) { @@ -78,7 +88,7 @@ class DerivAuthService extends BaseAuthService { jwtService.clearJwtToken(); - return onLoginRequest(request); + return onLoginRequest(request: request, userAgent: userAgent); } else { throw _mapHttpErrorToDerivAuthError(error); } @@ -89,33 +99,29 @@ class DerivAuthService extends BaseAuthService { Future login( String token, { required List accounts, + List? tokenList, String? signupProvider, String? refreshToken, }) async { try { final AuthorizeEntity? responseAuthorizeEntity = - (await authRepository.authorize(token)).authorize; + (await authRepository.authorize(token, tokenList: tokenList)) + .authorize; _checkAuthorizeValidity(responseAuthorizeEntity); + final List _filteredAccounts = + _filterSupportedAccountsFromAuthorizeResponse( + responseAuthorizeEntity?.accountList ?? []); + final AuthorizeEntity _enhancedAuthorizeEntity = responseAuthorizeEntity!.copyWith( signupProvider: signupProvider, refreshToken: refreshToken, - accountList: responseAuthorizeEntity.accountList - ?.map( - (AccountListItem accountListItem) => accountListItem.copyWith( - token: accounts - .where( - (AccountModel element) => - element.accountId == accountListItem.loginid, - ) - .firstOrNull - ?.token ?? - token, - ), - ) - .toList(), + accountList: _getAccountListWithToken( + _filteredAccounts, + accounts, + ), ); await authRepository.onLogin(_enhancedAuthorizeEntity); @@ -131,6 +137,28 @@ class DerivAuthService extends BaseAuthService { } } + List? _getAccountListWithToken( + List? accountListItems, + List accounts, + ) => + accountListItems?.map( + (AccountListItem accountListItem) { + final AccountModel? account = accounts.firstWhereOrNull( + (AccountModel element) => + element.accountId == accountListItem.loginid, + ); + + if (account == null) { + throw DerivAuthException( + message: 'Login is Expired', + type: AuthErrorType.expiredAccount, + ); + } + + return accountListItem.copyWith(token: account.token); + }, + ).toList(); + @override Future getDefaultAccount() => authRepository.getDefaultAccount(); @@ -162,6 +190,23 @@ class DerivAuthService extends BaseAuthService { return supportedAccounts; } + List _filterSupportedAccountsFromAuthorizeResponse( + List accounts) { + final List supportedAccounts = accounts + .where((AccountListItem account) => + account.isSupported && account.isNotDisabled) + .toList(); + + if (supportedAccounts.isEmpty) { + throw DerivAuthException( + message: notAvailableCountryMessage, + type: AuthErrorType.unsupportedCountry, + ); + } + + return supportedAccounts; + } + DerivAuthException _mapHttpErrorToDerivAuthError( HTTPClientException exception, ) { @@ -200,7 +245,7 @@ class DerivAuthService extends BaseAuthService { return DerivAuthException( type: AuthErrorType.invalidResidence, message: exception.message, - ); + ); default: return DerivAuthException( diff --git a/packages/deriv_auth/lib/features/features.dart b/packages/deriv_auth/lib/features/features.dart new file mode 100644 index 000000000..7712e281d --- /dev/null +++ b/packages/deriv_auth/lib/features/features.dart @@ -0,0 +1,8 @@ +export 'auth/auth.dart'; +export 'get_started/get_started.dart'; +export 'login/login.dart'; +export 'reset_password/reset_password.dart'; +export 'setting_page/setting_page.dart'; +export 'signup/signup.dart'; +export 'single_entry/single_entry.dart'; +export 'social_auth/social_auth.dart'; diff --git a/packages/deriv_auth/lib/features/get_started/get_started.dart b/packages/deriv_auth/lib/features/get_started/get_started.dart new file mode 100644 index 000000000..2604a2649 --- /dev/null +++ b/packages/deriv_auth/lib/features/get_started/get_started.dart @@ -0,0 +1,2 @@ +export 'models/deriv_get_started_slide_model.dart'; +export 'presentation/layouts/deriv_get_started_layout.dart'; diff --git a/packages/deriv_auth_ui/lib/src/features/get_started/models/deriv_get_started_slide_model.dart b/packages/deriv_auth/lib/features/get_started/models/deriv_get_started_slide_model.dart similarity index 100% rename from packages/deriv_auth_ui/lib/src/features/get_started/models/deriv_get_started_slide_model.dart rename to packages/deriv_auth/lib/features/get_started/models/deriv_get_started_slide_model.dart diff --git a/packages/deriv_auth_ui/lib/src/features/get_started/layouts/deriv_get_started_layout.dart b/packages/deriv_auth/lib/features/get_started/presentation/layouts/deriv_get_started_layout.dart similarity index 74% rename from packages/deriv_auth_ui/lib/src/features/get_started/layouts/deriv_get_started_layout.dart rename to packages/deriv_auth/lib/features/get_started/presentation/layouts/deriv_get_started_layout.dart index 160d26910..f9ec33a6d 100644 --- a/packages/deriv_auth_ui/lib/src/features/get_started/layouts/deriv_get_started_layout.dart +++ b/packages/deriv_auth/lib/features/get_started/presentation/layouts/deriv_get_started_layout.dart @@ -3,8 +3,11 @@ import 'dart:async'; import 'dart:math' as math; -import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; -import 'package:deriv_auth_ui/src/features/get_started/models/deriv_get_started_slide_model.dart'; +import 'package:deriv_auth/core/analytics/service/auth_tracking_mixin.dart'; +import 'package:deriv_auth/core/extensions/context_extension.dart'; +import 'package:deriv_auth/core/helpers/semantic_labels.dart'; +import 'package:deriv_auth/features/get_started/models/deriv_get_started_slide_model.dart'; +import 'package:deriv_language_selector/deriv_language_selector.dart'; import 'package:deriv_theme/deriv_theme.dart'; import 'package:deriv_ui/deriv_ui.dart'; import 'package:flutter/material.dart'; @@ -20,6 +23,7 @@ class DerivGetStartedLayout extends StatefulWidget { required this.backgroundImagePath, required this.onLoginTapped, required this.onSignupTapped, + required this.onTapNavigation, Key? key, }) : super(key: key); @@ -38,11 +42,15 @@ class DerivGetStartedLayout extends StatefulWidget { /// Callback to be called when signup button is tapped. final VoidCallback onSignupTapped; + /// Navigation to be called when screen is tapped seven times. + final Function(BuildContext context) onTapNavigation; + @override State createState() => _DerivGetStartedLayoutState(); } -class _DerivGetStartedLayoutState extends State { +class _DerivGetStartedLayoutState extends State + with AuthTrackingMixin { static const Duration _autoScrollInterval = Duration(seconds: 4); static const Duration _scrollAnimationDuration = Duration(seconds: 1); static const Alignment _slideImageAlignment = Alignment.center; @@ -102,7 +110,20 @@ class _DerivGetStartedLayoutState extends State { PreferredSizeWidget _buildAppBar(BuildContext context) => AppBar( backgroundColor: context.theme.colors.secondary, centerTitle: false, - title: SvgPicture.asset(widget.appLogoIconPath), + title: AppSettingGestureDetector( + onTapNavigation: widget.onTapNavigation, + child: SvgPicture.asset(widget.appLogoIconPath)), + actions: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: ThemeProvider.margin16, + vertical: ThemeProvider.margin08, + ), + child: LanguageSelector.button( + bottomsheetTitle: context.derivAuthLocalization.labelLanguage, + ), + ), + ], ); Timer _buildNewScrollTimer() => Timer.periodic( @@ -133,26 +154,37 @@ class _DerivGetStartedLayoutState extends State { Widget _buildButtons() => Column( mainAxisSize: MainAxisSize.min, children: [ - PrimaryButton( - onPressed: widget.onSignupTapped, - child: Center( - child: Text( - context.localization.actionGetAFreeAccount, - style: context.theme.textStyle( - textStyle: TextStyles.body2, - color: context.theme.colors.prominent, + Semantics( + explicitChildNodes: true, + label: SemanticsLabels.starterPageSignupButtonSemantic, + child: PrimaryButton( + onPressed: widget.onSignupTapped, + child: Center( + child: Text( + context.derivAuthLocalization.actionSignUpForFree, + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent, + ), ), ), ), ), - SecondaryButton( - onPressed: widget.onLoginTapped, - child: Center( - child: Text( - context.localization.actionLogin, - style: context.theme.textStyle( - textStyle: TextStyles.body2, - color: context.theme.colors.prominent, + Semantics( + explicitChildNodes: true, + label: SemanticsLabels.starterPageLoginButtonSemantic, + child: SecondaryButton( + onPressed: () { + trackUserOpenedLoginForm(); + widget.onLoginTapped(); + }, + child: Center( + child: Text( + context.derivAuthLocalization.actionLogin, + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent, + ), ), ), ), @@ -221,7 +253,7 @@ class _DerivGetStartedLayoutState extends State { supportingText, style: context.theme.textStyle( textStyle: TextStyles.title, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), textAlign: TextAlign.center, ), diff --git a/packages/deriv_auth/lib/features/login/login.dart b/packages/deriv_auth/lib/features/login/login.dart new file mode 100644 index 000000000..2dba1e7ff --- /dev/null +++ b/packages/deriv_auth/lib/features/login/login.dart @@ -0,0 +1,2 @@ +export 'presentation/layouts/deriv_2fa_layout.dart'; +export 'presentation/layouts/deriv_login_layout.dart'; diff --git a/packages/deriv_auth_ui/lib/src/features/login/layouts/deriv_2fa_layout.dart b/packages/deriv_auth/lib/features/login/presentation/layouts/deriv_2fa_layout.dart similarity index 73% rename from packages/deriv_auth_ui/lib/src/features/login/layouts/deriv_2fa_layout.dart rename to packages/deriv_auth/lib/features/login/presentation/layouts/deriv_2fa_layout.dart index 00009622c..86c688214 100644 --- a/packages/deriv_auth_ui/lib/src/features/login/layouts/deriv_2fa_layout.dart +++ b/packages/deriv_auth/lib/features/login/presentation/layouts/deriv_2fa_layout.dart @@ -1,6 +1,5 @@ +import 'package:deriv_auth/core/helpers/assets.dart'; import 'package:deriv_auth/deriv_auth.dart'; -import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; -import 'package:deriv_auth_ui/src/core/helpers/assets.dart'; import 'package:deriv_theme/deriv_theme.dart'; import 'package:deriv_ui/deriv_ui.dart'; import 'package:flutter/material.dart'; @@ -10,18 +9,30 @@ import 'package:flutter_svg/flutter_svg.dart'; /// Two-factor-authentication page. class Deriv2FALayout extends StatefulWidget { - /// Initializes the two-factor-authentication page. - const Deriv2FALayout({ + /// Initializes the two-factor-authentication page for system login. + const Deriv2FALayout.systemLogin({ required this.email, required this.password, Key? key, - }) : super(key: key); + }) : socialAuthDto = null, + super(key: key); + + /// Initializes the two-factor-authentication page for social login. + const Deriv2FALayout.socialLogin({ + required this.socialAuthDto, + Key? key, + }) : email = null, + password = null, + super(key: key); /// User entered email in previous page. - final String email; + final String? email; /// User entered password in previous page. - final String password; + final String? password; + + /// For in house social login with 2FA. + final SocialAuthDto? socialAuthDto; @override State createState() => _Deriv2FALayoutState(); @@ -39,7 +50,7 @@ class _Deriv2FALayoutState extends State { appBar: AppBar( elevation: ThemeProvider.zeroMargin, title: Text( - context.localization.labelLogIn, + context.derivAuthLocalization.labelLogIn, style: TextStyles.title, ), ), @@ -54,24 +65,24 @@ class _Deriv2FALayoutState extends State { children: [ SvgPicture.asset(Assets.twoFactorAuthIcon), Text( - context.localization.labelTwoFactorAuth, + context.derivAuthLocalization.labelTwoFactorAuth, style: context.theme.textStyle(textStyle: TextStyles.title), textAlign: TextAlign.center, ), const SizedBox(height: ThemeProvider.margin08), Text( - context.localization.informEnterTwoFactorAuthCode, + context.derivAuthLocalization.informEnterTwoFactorAuthCode, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), textAlign: TextAlign.center, ), const SizedBox(height: ThemeProvider.margin24), BaseTextField( controller: _otpController, - labelText: - context.localization.labelTwoFactorAuthenticationCode, + labelText: context + .derivAuthLocalization.labelTwoFactorAuthenticationCode, labelColor: context.theme.colors.disabled, focusNode: _otpFocusNode, enabled: !_isLoading(), @@ -101,7 +112,7 @@ class _Deriv2FALayoutState extends State { width: ThemeProvider.iconSize16, ) : Text( - context.localization.actionProceed, + context.derivAuthLocalization.actionProceed, style: TextStyles.body2.copyWith( color: context.theme.colors.prominent, ), @@ -118,11 +129,20 @@ class _Deriv2FALayoutState extends State { FocusManager.instance.primaryFocus?.unfocus(); if (!_isLoading()) { - context.read().systemLogin( - email: widget.email, - password: widget.password, - otp: _otpController.text, - ); + if (widget.socialAuthDto != null) { + context.read().socialAuth( + socialAuthDto: widget.socialAuthDto!, + otp: _otpController.text, + ); + } else if (widget.email != null && widget.password != null) { + context.read().systemLogin( + email: widget.email!, + password: widget.password!, + otp: _otpController.text, + ); + } + + _otpController.clear(); } } diff --git a/packages/deriv_auth_ui/lib/src/features/login/layouts/deriv_login_layout.dart b/packages/deriv_auth/lib/features/login/presentation/layouts/deriv_login_layout.dart similarity index 60% rename from packages/deriv_auth_ui/lib/src/features/login/layouts/deriv_login_layout.dart rename to packages/deriv_auth/lib/features/login/presentation/layouts/deriv_login_layout.dart index 4f89a1763..4c64f58b2 100644 --- a/packages/deriv_auth_ui/lib/src/features/login/layouts/deriv_login_layout.dart +++ b/packages/deriv_auth/lib/features/login/presentation/layouts/deriv_login_layout.dart @@ -1,10 +1,8 @@ import 'dart:async'; +import 'package:deriv_auth/core/analytics/service/auth_tracking_mixin.dart'; import 'package:deriv_auth/deriv_auth.dart'; -import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; -import 'package:deriv_auth_ui/src/core/extensions/string_extension.dart'; -import 'package:deriv_auth_ui/src/features/login/widgets/deriv_social_auth_divider.dart'; -import 'package:deriv_auth_ui/src/features/login/widgets/deriv_social_auth_panel.dart'; +import 'package:deriv_passkeys/deriv_passkeys.dart'; import 'package:deriv_theme/deriv_theme.dart'; import 'package:deriv_ui/deriv_ui.dart'; import 'package:flutter/material.dart'; @@ -16,12 +14,25 @@ class DerivLoginLayout extends StatefulWidget { const DerivLoginLayout({ required this.onResetPassTapped, required this.onSignupTapped, - required this.onLoginError, required this.onLoggedIn, - required this.onSocialAuthButtonPressed, required this.welcomeLabel, - required this.greetingLabel, + required this.socialAuthStateHandler, + required this.redirectURL, + required this.onWebViewError, + this.onSocialAuthButtonPressed, + this.isForgotPasswordEnabled = true, + this.isCreateAccountEnabled = true, + this.isSocialAuthEnabled = true, + this.isPasskeysEnabled = true, + this.authErrorStateHandler, + this.onLoginError, this.onLoginTapped, + this.titleKey, + this.emailTextFieldKey, + this.passwordTextFieldKey, + this.forgotPasswordButtonKey, + this.loginButtonKey, + this.twoFactorAuthNavigation, Key? key, }) : super(key: key); @@ -31,29 +42,71 @@ class DerivLoginLayout extends StatefulWidget { /// Callback to be called when signup button is tapped. final VoidCallback onSignupTapped; - /// Callback to be called when login button is tapped. - final Function(DerivAuthErrorState) onLoginError; + /// Extension of base [AuthErrorStateHandler]. If not provided, base implementation will be used. + final AuthErrorStateHandler? authErrorStateHandler; + + /// Callback to be called when login error occurs. + final Function(DerivAuthErrorState)? onLoginError; /// Callback to be called when user is logged in. - final Function(DerivAuthLoggedInState) onLoggedIn; + final Function(BuildContext, DerivAuthLoggedInState) onLoggedIn; /// Callback to be called when social auth button is tapped. - final void Function(SocialAuthProvider) onSocialAuthButtonPressed; + /// Give access to [SocialAuthDto] for 2FA. + final SocialAuthCallback? onSocialAuthButtonPressed; /// Callback to be called when login button is tapped. - final VoidCallback? onLoginTapped; + /// Give access to email and password. + final Function(String email, String password)? onLoginTapped; /// Welcome text to be displayed on login page. final String welcomeLabel; - /// Greeting text to be displayed on login page. - final String greetingLabel; + /// Whether to display social auth buttons. + final bool isSocialAuthEnabled; + + /// Whether to display forgot password section. + final bool isForgotPasswordEnabled; + + /// Whether to display create account section. + final bool isCreateAccountEnabled; + + /// Whether to display passkey button. + final bool isPasskeysEnabled; + + /// Social auth state handler. + final Function(BuildContext, SocialAuthState) socialAuthStateHandler; + + /// Redirect URL for social auth. + final String redirectURL; + + /// Callback for web view error. + final Function(String) onWebViewError; + + /// Widget key for title. + final Key? titleKey; + + /// Widget key for email text box. + final Key? emailTextFieldKey; + + /// Widget key for password text box. + final Key? passwordTextFieldKey; + + /// Widget key for forgot password button. + final Key? forgotPasswordButtonKey; + + /// Widget key for login button button. + final Key? loginButtonKey; + + /// 2FA flow navigation + final Function? twoFactorAuthNavigation; @override State createState() => _DerivLoginLayoutState(); } -class _DerivLoginLayoutState extends State { +class _DerivLoginLayoutState extends State + with AuthTrackingMixin { final GlobalKey _formKey = GlobalKey(); final TextEditingController _emailController = TextEditingController(); @@ -76,14 +129,14 @@ class _DerivLoginLayoutState extends State { appBar: AppBar( elevation: ThemeProvider.zeroMargin, title: Text( - context.localization.labelLogIn, + context.derivAuthLocalization.labelLogIn, + key: widget.titleKey, style: TextStyles.title, ), backgroundColor: context.theme.colors.secondary, centerTitle: false, ), body: BlocConsumer( - bloc: authCubit, listener: _onAuthState, builder: (BuildContext context, DerivAuthState state) => Form( key: _formKey, @@ -97,25 +150,39 @@ class _DerivLoginLayoutState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ ..._buildTopSection(), - const SizedBox(height: ThemeProvider.margin24), - const SizedBox(height: ThemeProvider.margin24), + const SizedBox(height: ThemeProvider.margin14), ..._buildTextFields( isEnabled: state is! DerivAuthLoadingState), const SizedBox(height: ThemeProvider.margin24), - _buildForgotPassButton(), + widget.isForgotPasswordEnabled + ? _buildForgotPassButton() + : const SizedBox(), const SizedBox(height: ThemeProvider.margin24), _buildLoginButton(), const SizedBox(height: ThemeProvider.margin24), DerivSocialAuthDivider( - label: context.localization.informLoginOptions, + label: context.derivAuthLocalization.informLoginOptions, + isVisible: widget.isSocialAuthEnabled, ), - const SizedBox(height: ThemeProvider.margin24), + if (widget.isSocialAuthEnabled) + const SizedBox(height: ThemeProvider.margin24), + widget.isPasskeysEnabled + ? ContinueWithPasskeyButton( + onTap: trackLoginWithPasskey, + ) + : const SizedBox.shrink(), DerivSocialAuthPanel( - onSocialAuthButtonPressed: - widget.onSocialAuthButtonPressed, + socialAuthStateHandler: widget.socialAuthStateHandler, + redirectURL: widget.redirectURL, + onPressed: widget.onSocialAuthButtonPressed, + isVisible: widget.isSocialAuthEnabled, + onWebViewError: widget.onWebViewError, ), - const SizedBox(height: ThemeProvider.margin24), - _buildFooterSection(), + if (widget.isSocialAuthEnabled) + const SizedBox(height: ThemeProvider.margin24), + widget.isCreateAccountEnabled + ? _buildFooterSection() + : const SizedBox(), ], ), ), @@ -129,25 +196,19 @@ class _DerivLoginLayoutState extends State { Text( widget.welcomeLabel, style: context.theme.textStyle( - textStyle: TextStyles.title, + textStyle: TextStyles.subheading, color: context.theme.colors.prominent, ), ), - const SizedBox(height: ThemeProvider.margin08), - Text( - widget.greetingLabel, - style: context.theme.textStyle( - textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, - ), - ), ]; List _buildTextFields({required bool isEnabled}) => [ BaseTextField( + key: widget.emailTextFieldKey, + semanticLabel: SemanticsLabels.loginEmailFieldSemantic, controller: _emailController, focusNode: _emailFocusNode, - labelText: context.localization.labelEmail, + labelText: context.derivAuthLocalization.labelEmail, borderColor: context.theme.colors.hover, focusedBorderColor: context.theme.colors.blue, keyboardType: TextInputType.emailAddress, @@ -159,9 +220,11 @@ class _DerivLoginLayoutState extends State { ), const SizedBox(height: ThemeProvider.margin32), BaseTextField( + key: widget.passwordTextFieldKey, + semanticLabel: SemanticsLabels.loginPasswordFieldSemantic, controller: _passwordController, focusNode: _passwordFocusNode, - labelText: context.localization.labelPassword, + labelText: context.derivAuthLocalization.labelPassword, obscureText: !_isPasswordVisible, enabled: isEnabled, suffixIcon: IconButton( @@ -185,9 +248,10 @@ class _DerivLoginLayoutState extends State { Widget _buildForgotPassButton() => Align( alignment: Alignment.centerRight, child: InkWell( + key: widget.forgotPasswordButtonKey, onTap: widget.onResetPassTapped, child: Text( - context.localization.actionForgotPassword, + context.derivAuthLocalization.actionForgotPassword, style: context.theme.textStyle( textStyle: TextStyles.body2, color: context.theme.colors.coral, @@ -199,6 +263,7 @@ class _DerivLoginLayoutState extends State { Widget _buildLoginButton() => BlocBuilder( bloc: authCubit, builder: (BuildContext context, DerivAuthState state) => ElevatedButton( + key: widget.loginButtonKey, style: ButtonStyle( backgroundColor: MaterialStateProperty.all( context.theme.colors.coral.withOpacity( @@ -220,7 +285,7 @@ class _DerivLoginLayoutState extends State { width: ThemeProvider.iconSize16, ) : Text( - context.localization.actionLogin, + context.derivAuthLocalization.actionLogin, style: context.theme.textStyle( textStyle: TextStyles.body2, color: context.theme.colors.prominent.withOpacity( @@ -237,10 +302,10 @@ class _DerivLoginLayoutState extends State { mainAxisSize: MainAxisSize.min, children: [ Text( - context.localization.labelDontHaveAnAccountYet, + context.derivAuthLocalization.labelDontHaveAnAccountYet, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), InkWell( @@ -248,7 +313,7 @@ class _DerivLoginLayoutState extends State { child: Padding( padding: const EdgeInsets.all(ThemeProvider.margin04), child: Text( - context.localization.actionCreateANewAccount, + context.derivAuthLocalization.actionCreateANewAccount, style: context.theme.textStyle( textStyle: TextStyles.body2, color: context.theme.colors.coral, @@ -262,35 +327,41 @@ class _DerivLoginLayoutState extends State { void _onAuthState(BuildContext context, DerivAuthState state) { if (state is DerivAuthErrorState) { - widget.onLoginError.call(state); + widget.onLoginError?.call(state); + + // Handling the 2FA Flow in case of missing OTP scanario + if (state.type == AuthErrorType.missingOtp && + widget.twoFactorAuthNavigation != null) { + widget.twoFactorAuthNavigation!(context); + } + + authErrorStateMapper( + authErrorState: state, + authErrorStateHandler: widget.authErrorStateHandler ?? + AuthErrorStateHandler(context: context), + ); } if (state is DerivAuthLoggedInState) { - widget.onLoggedIn.call(state); + widget.onLoggedIn(context, state); } } bool _isFormValid() => - _getEmailValue().isNotEmpty && _passwordController.text.isNotEmpty; + _getEmailValue().isValidEmail && + _passwordController.text.isValidLoginPasswordLength; - String? _emailValidator(String? input) { - if (_getEmailValue().isValidEmail) { - return null; - } + String? _emailValidator(String? input) => emailValidator( + _getEmailValue(), context.derivAuthLocalization.informInvalidEmailFormat); - return context.localization.informInvalidEmailFormat; - } - - String? _passwordValidator(String? input) { - if (input?.isValidLoginPasswordLength ?? false) { - return null; - } - - return context.localization.warnPasswordLength; - } + String? _passwordValidator(String? input) => passwordValidator( + _getPasswordValue(), context.derivAuthLocalization.warnPasswordLength); Future _onLoginTapped() async { - widget.onLoginTapped?.call(); + widget.onLoginTapped?.call( + _getEmailValue(), + _passwordController.text, + ); _emailFocusNode.unfocus(); _passwordFocusNode.unfocus(); @@ -308,6 +379,8 @@ class _DerivLoginLayoutState extends State { String _getEmailValue() => _emailController.text.trim(); + String _getPasswordValue() => _passwordController.text.trim(); + @override void dispose() { _emailFocusNode.dispose(); diff --git a/packages/deriv_auth/lib/features/reset_password/cubit/reset_password_cubit.dart b/packages/deriv_auth/lib/features/reset_password/cubit/reset_password_cubit.dart index cba8ff6c9..a66f21b78 100644 --- a/packages/deriv_auth/lib/features/reset_password/cubit/reset_password_cubit.dart +++ b/packages/deriv_auth/lib/features/reset_password/cubit/reset_password_cubit.dart @@ -4,6 +4,7 @@ import 'package:deriv_auth/core/constants/constants.dart'; import 'package:deriv_auth/core/models/verify_email_model.dart'; import 'package:deriv_auth/features/reset_password/deriv_reset_password_io.dart'; import 'package:deriv_auth/features/reset_password/services/base_reset_password_service.dart'; +import 'package:flutter_deriv_api/api/exceptions/base_api_exception.dart'; part 'reset_password_state.dart'; @@ -55,7 +56,14 @@ class DerivResetPassCubit extends Cubit /// Check if password is reset if (isPasswordReset) { emit(const DerivResetPassPasswordChangedState()); + } else { + emit(const DerivResetPassErrorState(isLinkExpired: true)); } + } on BaseAPIException catch (e) { + /// flutter_deriv_api throws BaseAPIException with specified error code on expired link. + emit(DerivResetPassErrorState( + errorMessage: e.toString(), + isLinkExpired: e.code == resetPasswordInvalidLinkError)); } on Exception catch (e) { emit(DerivResetPassErrorState(errorMessage: e.toString())); } @@ -63,4 +71,9 @@ class DerivResetPassCubit extends Cubit @override Stream get output => stream; + + @override + void resetState() { + emit(const DerivResetPassInitialState()); + } } diff --git a/packages/deriv_auth/lib/features/reset_password/cubit/reset_password_state.dart b/packages/deriv_auth/lib/features/reset_password/cubit/reset_password_state.dart index 7cb15b260..b4b83a9a0 100644 --- a/packages/deriv_auth/lib/features/reset_password/cubit/reset_password_state.dart +++ b/packages/deriv_auth/lib/features/reset_password/cubit/reset_password_state.dart @@ -28,9 +28,13 @@ class DerivResetPassPasswordChangedState extends DerivResetPassState { class DerivResetPassErrorState extends DerivResetPassState { /// Initializes Reset pass error State. const DerivResetPassErrorState({ - required this.errorMessage, + this.errorMessage, + this.isLinkExpired = false, }); /// Error message. final String? errorMessage; + + /// Set to true if the error state is caused by an expired link. + final bool isLinkExpired; } diff --git a/packages/deriv_auth/lib/features/reset_password/deriv_reset_password_io.dart b/packages/deriv_auth/lib/features/reset_password/deriv_reset_password_io.dart index a059eef51..5b617cf4c 100644 --- a/packages/deriv_auth/lib/features/reset_password/deriv_reset_password_io.dart +++ b/packages/deriv_auth/lib/features/reset_password/deriv_reset_password_io.dart @@ -13,4 +13,7 @@ abstract class DerivResetPasswordIO { /// Deriv reset pass output. Stream get output; + + /// Resets [DerivResetPassState] to [DerivResetPassInitialState]. + void resetState(); } diff --git a/packages/deriv_auth/lib/features/reset_password/presentation/layouts/deriv_choose_new_pass_layout.dart b/packages/deriv_auth/lib/features/reset_password/presentation/layouts/deriv_choose_new_pass_layout.dart new file mode 100644 index 000000000..a33551d5c --- /dev/null +++ b/packages/deriv_auth/lib/features/reset_password/presentation/layouts/deriv_choose_new_pass_layout.dart @@ -0,0 +1,206 @@ +import 'dart:async'; +import 'package:deriv_auth/core/helpers/assets.dart'; +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth/features/signup/presentation/widgets/password_policy_checker_widget.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +/// type definition for reset pass error callback. +typedef ResetPassErrorCallback = void Function( + {required bool isLinkExpired, String? error}); + +/// Choose new Pass page. +class DerivChooseNewPassLayout extends StatefulWidget { + /// Initializes choose new pass page. + const DerivChooseNewPassLayout({ + required this.token, + required this.onResetPassSucceed, + required this.onResetPassError, + Key? key, + }) : super(key: key); + + /// Access token for changing password. can be received from email verification step. + final String token; + + /// Callback to be called when reset pass fails. + final ResetPassErrorCallback onResetPassError; + + /// Callback to be called when reset pass succeeds. + final VoidCallback onResetPassSucceed; + + @override + State createState() => + _DerivChooseNewPassLayoutState(); +} + +class _DerivChooseNewPassLayoutState extends State { + final GlobalKey _formKey = GlobalKey(); + final TextEditingController _passController = TextEditingController(); + final FocusNode _passFocusNode = FocusNode(); + + bool _isBusy = false; + bool _isPasswordVisible = false; + + @override + Widget build(BuildContext context) => Scaffold( + backgroundColor: context.theme.colors.primary, + appBar: AppBar( + elevation: ThemeProvider.zeroMargin, + title: Text( + context.derivAuthLocalization.labelResetPassword, + style: TextStyles.title, + ), + ), + body: BlocListener( + listener: (BuildContext context, DerivResetPassState state) { + if (state is DerivResetPassPasswordChangedState) { + widget.onResetPassSucceed(); + } else if (state is DerivResetPassErrorState) { + widget.onResetPassError( + error: state.errorMessage, + isLinkExpired: state.isLinkExpired, + ); + } + }, + child: _buildChooseNewPassSection(context), + ), + ); + + Widget _buildChooseNewPassSection(BuildContext context) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: ThemeProvider.margin16, + ), + child: Center( + child: Form( + key: _formKey, + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: ThemeProvider.margin72), + _buildContent(), + const SizedBox(height: ThemeProvider.margin24), + _buildSubmitPassButton() + ], + ), + ), + ), + ), + ); + + Widget _buildContent() => + NotificationListener( + onNotification: (OverscrollIndicatorNotification overscroll) { + overscroll.disallowIndicator(); + + return true; + }, + child: Column( + children: [ + Center( + child: SvgPicture.asset( + Assets.chooseNewPassIcon, + package: 'deriv_auth', + width: ThemeProvider.iconSize96, + height: ThemeProvider.iconSize32, + ), + ), + const SizedBox(height: ThemeProvider.margin48), + Text( + context.derivAuthLocalization.labelChooseNewPass, + style: TextStyles.title, + ), + const SizedBox(height: ThemeProvider.margin24), + BaseTextField( + controller: _passController, + focusNode: _passFocusNode, + labelText: context.derivAuthLocalization.labelCreatePass, + obscureText: !_isPasswordVisible, + suffixIcon: IconButton( + icon: Icon( + _isPasswordVisible ? Icons.visibility : Icons.visibility_off, + color: context.theme.colors.disabled, + ), + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + onPressed: () => + setState(() => _isPasswordVisible = !_isPasswordVisible), + ), + validator: _passwordValidator, + onChanged: (_) => setState(() {}), + onEditingComplete: () => _formKey.currentState?.validate(), + ), + const SizedBox(height: ThemeProvider.margin40), + PasswordPolicyCheckerWidget( + passwordController: _passController, + policies: PasswordPolicyCheckerWidget.getDerivPasswordPolicies( + context, + ), + ), + ], + ), + ); + + Widget _buildSubmitPassButton() => ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + context.theme.colors.coral.withOpacity( + getOpacity(isEnabled: isFormValid()), + ), + ), + ), + onPressed: isFormValid() ? _onSubmitEmailTapped : null, + child: Center( + child: _isBusy + ? const LoadingIndicator( + valueColor: Colors.white, + strokeWidth: ThemeProvider.margin02, + height: ThemeProvider.iconSize16, + width: ThemeProvider.iconSize16, + ) + : Text( + context.derivAuthLocalization.actionResetPass, + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent.withOpacity( + getOpacity(isEnabled: isFormValid()), + ), + ), + ), + ), + ); + + Future _onSubmitEmailTapped() async { + _passFocusNode.unfocus(); + + if ((_formKey.currentState?.validate() ?? false) && !_isBusy) { + setState(() => _isBusy = true); + + await context.read().changePassword( + token: widget.token, + newPassword: _passController.text, + ); + + setState(() => _isBusy = false); + } + } + + String _getPasswordValue() => _passController.text.trim(); + + String? _passwordValidator(String? input) => passwordValidator( + _getPasswordValue(), + context.derivAuthLocalization.informInvalidPasswordFormat); + + bool isFormValid() => _passwordValidator(_passController.text) == null; + + @override + void dispose() { + _passController.dispose(); + _passFocusNode.dispose(); + + super.dispose(); + } +} diff --git a/packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_reset_pass_layout.dart b/packages/deriv_auth/lib/features/reset_password/presentation/layouts/deriv_reset_pass_layout.dart similarity index 70% rename from packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_reset_pass_layout.dart rename to packages/deriv_auth/lib/features/reset_password/presentation/layouts/deriv_reset_pass_layout.dart index 4614866aa..534b8feed 100644 --- a/packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_reset_pass_layout.dart +++ b/packages/deriv_auth/lib/features/reset_password/presentation/layouts/deriv_reset_pass_layout.dart @@ -1,9 +1,8 @@ import 'dart:async'; +import 'package:deriv_auth/core/helpers/assets.dart'; +import 'package:deriv_auth/core/helpers/semantic_labels.dart'; import 'package:deriv_auth/deriv_auth.dart'; -import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; -import 'package:deriv_auth_ui/src/core/extensions/string_extension.dart'; -import 'package:deriv_auth_ui/src/core/helpers/assets.dart'; import 'package:deriv_theme/deriv_theme.dart'; import 'package:deriv_ui/deriv_ui.dart'; import 'package:flutter/material.dart'; @@ -19,7 +18,7 @@ class DerivResetPassLayout extends StatefulWidget { }) : super(key: key); /// Callback to be called when reset pass fails. - final Function(String?) onResetPassError; + final ResetPassErrorCallback onResetPassError; @override State createState() => _DerivResetPassLayoutState(); @@ -45,7 +44,7 @@ class _DerivResetPassLayoutState extends State { appBar: AppBar( elevation: ThemeProvider.zeroMargin, title: Text( - context.localization.labelResetPassword, + context.derivAuthLocalization.labelResetPassword, style: TextStyles.title, ), ), @@ -58,7 +57,10 @@ class _DerivResetPassLayoutState extends State { curve: Curves.easeInOut, ); } else if (state is DerivResetPassErrorState) { - widget.onResetPassError(state.errorMessage); + widget.onResetPassError( + error: state.errorMessage, + isLinkExpired: state.isLinkExpired, + ); } }, child: PageView( @@ -81,21 +83,21 @@ class _DerivResetPassLayoutState extends State { Assets.mailIcon, width: ThemeProvider.iconSize96, height: ThemeProvider.iconSize96, - package: 'deriv_auth_ui', + package: 'deriv_auth', ), const SizedBox(height: ThemeProvider.margin24), Text( - context.localization.labelCheckEmail, + context.derivAuthLocalization.labelCheckEmail, style: TextStyles.title, ), const SizedBox(height: ThemeProvider.margin08), Text( - context.localization + context.derivAuthLocalization .informSendResetPasswordEmail(_emailController.text), textAlign: TextAlign.center, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), const SizedBox(height: kToolbarHeight), @@ -115,29 +117,31 @@ class _DerivResetPassLayoutState extends State { Assets.resetPassIcon, width: ThemeProvider.iconSize96, height: ThemeProvider.iconSize96, - package: 'deriv_auth_ui', + package: 'deriv_auth', ), const SizedBox(height: ThemeProvider.margin16), Text( - context.localization.labelResetPassword, + context.derivAuthLocalization.labelResetPassword, style: TextStyles.title, textAlign: TextAlign.center, ), const SizedBox(height: ThemeProvider.margin08), Center( child: Text( - context.localization.informResetPassByEmail, + context.derivAuthLocalization.informResetPassByEmail, + textAlign: TextAlign.center, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ), const SizedBox(height: ThemeProvider.margin24), BaseTextField( + semanticLabel: SemanticsLabels.resetPasswordEmailFieldSemantic, controller: _emailController, focusNode: _emailFocusNode, - labelText: context.localization.labelEmail, + labelText: context.derivAuthLocalization.labelEmail, keyboardType: TextInputType.emailAddress, borderColor: context.theme.colors.hover, validator: _emailValidator, @@ -152,32 +156,36 @@ class _DerivResetPassLayoutState extends State { ), ); - Widget _buildSubmitEmailButton() => ElevatedButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - context.theme.colors.coral.withOpacity( - getOpacity(isEnabled: _isFormValid()), + Widget _buildSubmitEmailButton() => Semantics( + explicitChildNodes: true, + label: SemanticsLabels.resetPasswordButtonSemantic, + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + context.theme.colors.coral.withOpacity( + getOpacity(isEnabled: _isFormValid()), + ), ), ), - ), - onPressed: _isFormValid() ? _onSubmitEmailTapped : null, - child: Center( - child: _isBusy - ? LoadingIndicator( - valueColor: context.theme.colors.prominent, - strokeWidth: ThemeProvider.margin02, - height: ThemeProvider.margin16, - width: ThemeProvider.margin16, - ) - : Text( - context.localization.actionResetPass, - style: context.theme.textStyle( - textStyle: TextStyles.body2, - color: context.theme.colors.prominent.withOpacity( - getOpacity(isEnabled: _isFormValid()), + onPressed: _isFormValid() ? _onSubmitEmailTapped : null, + child: Center( + child: _isBusy + ? LoadingIndicator( + valueColor: context.theme.colors.prominent, + strokeWidth: ThemeProvider.margin02, + height: ThemeProvider.margin16, + width: ThemeProvider.margin16, + ) + : Text( + context.derivAuthLocalization.actionResetPass, + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent.withOpacity( + getOpacity(isEnabled: _isFormValid()), + ), ), ), - ), + ), ), ); @@ -200,13 +208,8 @@ class _DerivResetPassLayoutState extends State { bool _isFormValid() => _getEmailValue().isNotEmpty && _getEmailValue().isValidEmail; - String? _emailValidator(String? input) { - if (_getEmailValue().isValidEmail) { - return null; - } - - return context.localization.informInvalidEmailFormat; - } + String? _emailValidator(String? input) => emailValidator( + _getEmailValue(), context.derivAuthLocalization.informInvalidEmailFormat); String _getEmailValue() => _emailController.text.trim(); diff --git a/packages/deriv_auth/lib/features/reset_password/presentation/layouts/deriv_success_pass_change_layout.dart b/packages/deriv_auth/lib/features/reset_password/presentation/layouts/deriv_success_pass_change_layout.dart new file mode 100644 index 000000000..4d6cd57ec --- /dev/null +++ b/packages/deriv_auth/lib/features/reset_password/presentation/layouts/deriv_success_pass_change_layout.dart @@ -0,0 +1,59 @@ +import 'package:deriv_auth/core/extensions/context_extension.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; + +/// Success pass change page layout. +class DerivSuccessPassChangeLayout extends StatelessWidget { + /// Initializes success pass change page. + const DerivSuccessPassChangeLayout({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) => Scaffold( + backgroundColor: context.theme.colors.primary, + appBar: AppBar( + elevation: ThemeProvider.zeroMargin, + title: Text( + context.derivAuthLocalization.labelResetPassword, + style: TextStyles.title, + ), + ), + body: Center( + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: ThemeProvider.margin16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: ThemeProvider.margin16, + width: ThemeProvider.margin16, + child: LoadingIndicator( + valueColor: Colors.white, + strokeWidth: 2.5, + ), + ), + const SizedBox( + height: ThemeProvider.margin16, + ), + Text( + context.derivAuthLocalization.informYourPassHasBeenReset, + style: TextStyles.title, + ), + const SizedBox( + height: ThemeProvider.margin08, + ), + Text( + context.derivAuthLocalization.informRedirectLogin, + textAlign: TextAlign.center, + style: context.theme.textStyle( + textStyle: TextStyles.body1, + color: context.theme.colors.general, + ), + ), + ], + ), + ), + ), + ); +} diff --git a/packages/deriv_auth/lib/features/reset_password/reset_password.dart b/packages/deriv_auth/lib/features/reset_password/reset_password.dart new file mode 100644 index 000000000..96b1adce5 --- /dev/null +++ b/packages/deriv_auth/lib/features/reset_password/reset_password.dart @@ -0,0 +1,6 @@ +export 'cubit/reset_password_cubit.dart'; +export 'presentation/layouts/deriv_choose_new_pass_layout.dart'; +export 'presentation/layouts/deriv_reset_pass_layout.dart'; +export 'presentation/layouts/deriv_success_pass_change_layout.dart'; +export 'repository/base_reset_password_repository.dart'; +export 'services/deriv_reset_password_service.dart'; diff --git a/packages/deriv_auth/lib/features/setting_page/presentation/layouts/deriv_setting_layout.dart b/packages/deriv_auth/lib/features/setting_page/presentation/layouts/deriv_setting_layout.dart new file mode 100644 index 000000000..e61f0d754 --- /dev/null +++ b/packages/deriv_auth/lib/features/setting_page/presentation/layouts/deriv_setting_layout.dart @@ -0,0 +1,212 @@ +import 'package:deriv_auth/core/extensions/context_extension.dart'; +import 'package:deriv_auth/core/helpers/endpoint_helper.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/utils/regex_helpers.dart'; +import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +/// Login setting page +/// +/// This page is used to apply necessary QA configurations for login process +/// Two fields can be set in this page 'endpoint' and 'app_id' +/// The applied values stored for future usage +class DerivSettingLayout extends StatefulWidget { + /// Login Setting Page Constructor + const DerivSettingLayout({ + required this.updateFlavorConfigs, + required this.appLabel, + required this.saveValues, + this.endpoint = defaultEndpoint, + this.appId = defaultAppId, + this.devApp = 'com.deriv.app.dev', + this.stagingApp = 'com.deriv.app.staging', + this.getAppEnv, + this.setAppEnv, + Key? key, + }) : super(key: key); + + /// Update flavor configurations + final Function({required String endpoint, required String appId}) + updateFlavorConfigs; + + /// Save values to shared preferences + final Function({required String endpoint, required String appId}) saveValues; + + /// End Point + final String endpoint; + + /// App Id + final String appId; + + /// Application label + final String appLabel; + + /// Dev flavor app. + final String devApp; + + /// Staging flavor app. + final String stagingApp; + + /// Gets environment variable + final Future? getAppEnv; + + /// Sets environment variable + final Future Function({required bool value})? setAppEnv; + + @override + _SettingsPageState createState() => _SettingsPageState(); +} + +class _SettingsPageState extends State { + final TextEditingController _endpointController = TextEditingController(); + final TextEditingController _appIdController = TextEditingController(); + + final GlobalKey _formKey = GlobalKey(); + late Future packageInfo; + + @override + void initState() { + super.initState(); + packageInfo = PackageInfo.fromPlatform(); + + _endpointController.text = widget.endpoint; + _appIdController.text = widget.appId; + } + + @override + Widget build(BuildContext context) => WillPopScope( + onWillPop: () async { + final String endpoint = _endpointController.text.isNotEmpty + ? _endpointController.text + : defaultEndpoint; + final String appId = _appIdController.text.isNotEmpty + ? _appIdController.text + : defaultAppId; + + // Save Values to shared preferences + await widget.saveValues(endpoint: endpoint, appId: appId); + + // Update Flavor Configurations before dismissing the page + await widget.updateFlavorConfigs(endpoint: endpoint, appId: appId); + + return true; + }, + child: Scaffold( + appBar: AppBar( + elevation: ThemeProvider.zeroMargin, + title: Text(widget.appLabel), + ), + body: Form( + key: _formKey, + child: ListView( + children: [ + _title, + const SizedBox(height: ThemeProvider.margin16), + _endpoint, + const SizedBox(height: ThemeProvider.margin16), + _appId, + const SizedBox(width: ThemeProvider.margin08), + _buildEnvironmentSwitcher, + ], + ), + ), + ), + ); + + Widget get _buildEnvironmentSwitcher => FutureBuilder( + future: packageInfo, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData && + (snapshot.data?.packageName == widget.devApp || + snapshot.data?.packageName == widget.stagingApp)) { + return Padding( + padding: + const EdgeInsets.symmetric(horizontal: ThemeProvider.margin16), + child: Row( + children: [ + const Text('Production environment'), + const SizedBox(width: ThemeProvider.margin08), + FutureBuilder( + future: widget.getAppEnv, + builder: + (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData && widget.setAppEnv != null) { + return Switch( + value: snapshot.data ?? false, + onChanged: (bool val) { + setState(() { + widget.setAppEnv!(value: val); + }); + }, + ); + } + return const SizedBox.shrink(); + }) + ], + ), + ); + } + return const SizedBox.shrink(); + }); + + Widget get _title => Padding( + padding: const EdgeInsets.only( + left: ThemeProvider.margin12, + top: ThemeProvider.margin24, + ), + child: Text( + context.derivAuthLocalization.labelDeveloper, + style: context.theme.textStyle( + textStyle: TextStyles.body1Bold, + color: context.theme.colors.coral, + ), + ), + ); + + Widget get _endpoint => _buildTextInputField( + label: context.derivAuthLocalization.labelEndpoint, + semantic: context.derivAuthLocalization.semanticEndpoint, + controller: _endpointController, + validator: (String? value) => hasOnlySmallLettersAndNumberInput(value!) + ? null + : context.derivAuthLocalization.warnInvalidEndpoint, + ); + + Widget get _appId => _buildTextInputField( + label: context.derivAuthLocalization.labelApplicationID, + semantic: context.derivAuthLocalization.semanticApplicationID, + controller: _appIdController, + validator: (String? value) => hasOnlyNumberInput(value!) + ? null + : context.derivAuthLocalization.warnInvalidApplicationID, + ); + + Widget _buildTextInputField({ + required String label, + required String semantic, + required TextEditingController controller, + required FormFieldValidator validator, + }) => + Padding( + padding: const EdgeInsets.symmetric(horizontal: ThemeProvider.margin16), + child: Semantics( + label: semantic, + explicitChildNodes: true, + child: TextFormField( + decoration: InputDecoration( + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: context.theme.colors.lessProminent, + ), + ), + labelText: label, + labelStyle: TextStyle(color: context.theme.colors.lessProminent), + border: const OutlineInputBorder(), + ), + controller: controller, + onChanged: (_) => _formKey.currentState!.validate(), + validator: validator, + ), + ), + ); +} diff --git a/packages/deriv_auth/lib/features/setting_page/setting_page.dart b/packages/deriv_auth/lib/features/setting_page/setting_page.dart new file mode 100644 index 000000000..224c7bff0 --- /dev/null +++ b/packages/deriv_auth/lib/features/setting_page/setting_page.dart @@ -0,0 +1 @@ +export 'presentation/layouts/deriv_setting_layout.dart'; diff --git a/packages/deriv_auth/lib/features/signup/cubit/deriv_country_selection_cubit.dart b/packages/deriv_auth/lib/features/signup/cubit/deriv_country_selection_cubit.dart new file mode 100644 index 000000000..13513a597 --- /dev/null +++ b/packages/deriv_auth/lib/features/signup/cubit/deriv_country_selection_cubit.dart @@ -0,0 +1,67 @@ +import 'package:deriv_auth/core/helpers/country_selection_helper.dart'; +import 'package:deriv_auth/features/signup/models/deriv_residence_model.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +part 'deriv_country_selection_state.dart'; + +/// Cubit to handle country selection screen. +class DerivCountrySelectionCubit extends Cubit { + /// Constructor for CountrySelectionCubit. + DerivCountrySelectionCubit(this.residences) + : super(const DerivCountrySelectionInitialState()); + + /// List of residence countries. + final Future> residences; + + /// Fetches residence countries. + Future fetchResidenceCountries() async { + final List countries = await residences; + + final List filteredCountries = countries + .where( + (DerivResidenceModel country) => + isAllowedCountry(countryCode: country.code), + ) + .toList(); + + emit(DerivCountrySelectionLoadedState(filteredCountries)); + } + + /// Changes the selected country and updates the state accordingly. + /// + /// If [selectedCountry] is not `null`, this method emits a [DerivCountryChangedState] + /// with the updated selected country. + /// + /// This method does not perform any action if [selectedCountry] is `null`. + Future changeSelectedCountry({ + DerivResidenceModel? selectedCountry, + }) async { + if (selectedCountry != null) { + emit( + DerivCountryChangedState( + state.countries, + selectedCountry: selectedCountry, + selectedCountryRequiresConsent: isConsentRequired( + countryCode: selectedCountry.code, + ), + ), + ); + } + } + + /// Updates the country consent status and triggers a state change. + /// + /// [agreedToTerms]: Whether the user has agreed to the terms for the + /// selected country. + Future updateCountryConsentStatus({bool? agreedToTerms = false}) async { + emit( + DerivCountryConsentChangedState( + state.countries, + selectedCountry: state.selectedCountry, + selectedCountryRequiresConsent: state.selectedCountryRequiresConsent, + agreedToTerms: agreedToTerms, + ), + ); + } +} diff --git a/packages/deriv_auth/lib/features/signup/cubit/deriv_country_selection_state.dart b/packages/deriv_auth/lib/features/signup/cubit/deriv_country_selection_state.dart new file mode 100644 index 000000000..ed48f1844 --- /dev/null +++ b/packages/deriv_auth/lib/features/signup/cubit/deriv_country_selection_state.dart @@ -0,0 +1,84 @@ +part of 'deriv_country_selection_cubit.dart'; + +/// Country selection state +abstract class DerivCountrySelectionState extends Equatable { + /// Initialize country selection state. + const DerivCountrySelectionState({ + this.countries = const [], + this.selectedCountry, + this.agreedToTerms = false, + this.selectedCountryRequiresConsent = false, + }); + + /// List of countries. + final List countries; + + /// Selected country. + final DerivResidenceModel? selectedCountry; + + /// If the selected country requires consent, value must be true to continue. + /// Default is null. + /// Example: For Brazil, Brazil requires consent to continue. + /// The user must agree to the terms to continue. + final bool agreedToTerms; + + /// If the selected country requires consent to continue, value is true. + /// Default is false. + final bool selectedCountryRequiresConsent; + + @override + List get props => [ + countries, + selectedCountry, + agreedToTerms, + selectedCountryRequiresConsent, + ]; +} + +/// Initial state. +class DerivCountrySelectionInitialState extends DerivCountrySelectionState { + /// Initialises initial state. + const DerivCountrySelectionInitialState() + : super(countries: const []); +} + +/// Country list loaded state. +class DerivCountrySelectionLoadedState extends DerivCountrySelectionState { + /// Initialise country list loaded state + const DerivCountrySelectionLoadedState( + List countries, { + DerivResidenceModel? selectedCountry, + }) : super(countries: countries, selectedCountry: selectedCountry); +} + +/// Country selection changed state. +class DerivCountryChangedState extends DerivCountrySelectionState { + /// Initialise country selection changed state. + const DerivCountryChangedState( + List countries, { + DerivResidenceModel? selectedCountry, + bool? selectedCountryRequiresConsent, + }) : super( + countries: countries, + selectedCountry: selectedCountry, + selectedCountryRequiresConsent: + selectedCountryRequiresConsent ?? false, + ); +} + +/// State to update country selection consent. +class DerivCountryConsentChangedState extends DerivCountrySelectionState { + /// Initialize country list loaded state + const DerivCountryConsentChangedState( + List countries, { + DerivResidenceModel? selectedCountry, + bool? selectedCountryRequiresConsent, + bool? agreedToTerms, + }) : super( + countries: countries, + selectedCountry: selectedCountry, + agreedToTerms: agreedToTerms ?? false, + selectedCountryRequiresConsent: + selectedCountryRequiresConsent ?? false, + ); +} diff --git a/packages/deriv_auth_ui/lib/src/features/signup/models/deriv_auth_utm_model.dart b/packages/deriv_auth/lib/features/signup/models/deriv_auth_utm_model.dart similarity index 100% rename from packages/deriv_auth_ui/lib/src/features/signup/models/deriv_auth_utm_model.dart rename to packages/deriv_auth/lib/features/signup/models/deriv_auth_utm_model.dart diff --git a/packages/deriv_auth_ui/lib/src/features/signup/models/deriv_password_policy_model.dart b/packages/deriv_auth/lib/features/signup/models/deriv_password_policy_model.dart similarity index 99% rename from packages/deriv_auth_ui/lib/src/features/signup/models/deriv_password_policy_model.dart rename to packages/deriv_auth/lib/features/signup/models/deriv_password_policy_model.dart index cf5f0ef13..9edfec3ba 100644 --- a/packages/deriv_auth_ui/lib/src/features/signup/models/deriv_password_policy_model.dart +++ b/packages/deriv_auth/lib/features/signup/models/deriv_password_policy_model.dart @@ -1,4 +1,3 @@ - /// Holds required information of a password policy. class DerivPasswordPolicyModel { /// Initializes [DerivPasswordPolicyModel]. diff --git a/packages/deriv_auth_ui/lib/src/features/signup/models/deriv_residence_model.dart b/packages/deriv_auth/lib/features/signup/models/deriv_residence_model.dart similarity index 100% rename from packages/deriv_auth_ui/lib/src/features/signup/models/deriv_residence_model.dart rename to packages/deriv_auth/lib/features/signup/models/deriv_residence_model.dart diff --git a/packages/deriv_auth/lib/features/signup/presentation/layouts/deriv_country_selection_layout.dart b/packages/deriv_auth/lib/features/signup/presentation/layouts/deriv_country_selection_layout.dart new file mode 100644 index 000000000..618a37450 --- /dev/null +++ b/packages/deriv_auth/lib/features/signup/presentation/layouts/deriv_country_selection_layout.dart @@ -0,0 +1,317 @@ +import 'package:analytics/core/logger.dart'; +import 'package:deriv_auth/core/extensions/context_extension.dart'; +import 'package:deriv_auth/core/helpers/assets.dart'; +import 'package:deriv_auth/core/helpers/country_selection_helper.dart'; +import 'package:deriv_auth/features/signup/cubit/deriv_country_selection_cubit.dart'; +import 'package:deriv_auth/features/signup/cubit/signup_cubit.dart'; +import 'package:deriv_auth/features/signup/models/deriv_residence_model.dart'; +import 'package:deriv_auth/features/signup/presentation/layouts/deriv_set_password_layout.dart'; +import 'package:deriv_auth/features/signup/presentation/widgets/country_selection_list_widget.dart'; +import 'package:deriv_auth/features/single_entry/core/auth_data.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'dart:developer'; + +/// Allows user to select a country from list +class DerivCountrySelectionLayout extends StatefulWidget { + /// constructor of country selection page + const DerivCountrySelectionLayout({ + required this.verificationCode, + required this.residences, + required this.onNextPressed, + this.affiliateToken, + this.countryConsentMessage, + Key? key, + }) : super(key: key); + + /// List of residences to be shown. + final Future> residences; + + /// Callback to be called when next button is tapped. + final VoidCallback onNextPressed; + + /// Verification Code from Previous Step. Saved for next step. + final String verificationCode; + + /// Affiliate token. + final String? affiliateToken; + + /// Message to be shown beside country consent checkbox. + final String? countryConsentMessage; + + @override + State createState() => + _DerivCountrySelectionLayoutState(); +} + +class _DerivCountrySelectionLayoutState + extends State { + final GlobalKey _formKey = GlobalKey(); + final FocusNode _focusNode = FocusNode(); + + late TextEditingController _textController; + + late DerivCountrySelectionCubit _countrySelectionCubit; + @override + void initState() { + super.initState(); + + _textController = TextEditingController(); + + _countrySelectionCubit = DerivCountrySelectionCubit(widget.residences); + _initializeResidences(); + } + + Future _initializeResidences() async { + try { + final List residencesList = await widget.residences; + + setState( + () => _countrySelectionCubit = DerivCountrySelectionCubit( + Future>.value(residencesList), + )..fetchResidenceCountries(), + ); + } on Exception catch (error) { + log('Error fetching residences: $error'); + } + } + + @override + Widget build(BuildContext context) => Scaffold( + backgroundColor: context.theme.colors.primary, + body: SafeArea( + child: BlocListener( + bloc: _countrySelectionCubit, + listener: (BuildContext context, DerivCountrySelectionState state) { + if (state.selectedCountry != null && + state.selectedCountry?.name != null) { + _textController.text = state.selectedCountry?.name ?? ''; + } + + WidgetsBinding.instance.addPostFrameCallback((_) { + _formKey.currentState!.validate(); + }); + }, + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Expanded(child: _buildUpperPage()), + Padding( + padding: const EdgeInsets.all(ThemeProvider.margin16), + child: _buildLowerPage(), + ), + ], + ), + ), + ), + ); + + Widget _buildUpperPage() => Padding( + padding: const EdgeInsets.all(ThemeProvider.margin16), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset(Assets.locationIcon, package: 'deriv_auth'), + const SizedBox(height: ThemeProvider.margin16), + Text( + context.derivAuthLocalization.labelSelectCountry, + style: TextStyles.title, + ), + const SizedBox(height: ThemeProvider.margin24), + _buildSelectionInput(), + const SizedBox(height: ThemeProvider.margin16), + _buildCountryConsentCheckbox( + countryConsentMessage: widget.countryConsentMessage, + ), + ], + ), + ); + + Widget _buildLowerPage() => Row( + children: [ + Expanded(child: _buildNextButton()), + ], + ); + + Widget _buildSelectionInput() => + BlocBuilder( + bloc: _countrySelectionCubit, + builder: (BuildContext context, DerivCountrySelectionState state) => + Form( + key: _formKey, + child: BaseTextField( + controller: _textController, + labelText: context.derivAuthLocalization.labelChooseCountry, + labelColor: context.theme.colors.disabled, + focusNode: _focusNode, + focusedLabelColor: context.theme.colors.blue, + suffixIcon: Icon( + Icons.keyboard_arrow_down, + color: context.theme.colors.prominent, + ), + readOnly: true, + enabled: _shouldEnableCountrySelectionField(state), + validator: (String? value) => _countrySelectionValidator(context, + selectedCountry: state.selectedCountry), + onTap: () => _onSelectCountryTap(state.countries), + ), + ), + ); + + Widget _buildNextButton() => + BlocBuilder( + bloc: _countrySelectionCubit, + builder: (BuildContext context, DerivCountrySelectionState state) => + PrimaryButton( + isEnabled: _shouldEnableNextButton( + state.selectedCountry, + isConsentRequired: state.selectedCountryRequiresConsent, + agreedToTerms: state.agreedToTerms, + ), + onPressed: AuthData().data.signupPageModel.handleFlowFromPackage + ? () { + Navigator.pushReplacement>( + context, + MaterialPageRoute( + builder: (BuildContext context) => DerivSetPasswordLayout( + onDerivSignupState: (BuildContext BuildContext, + DerivSignupState DerivSignupState) {}, + onPreviousPressed: () {}, + residence: state.selectedCountry?.code, + verificationCode: widget.verificationCode, + affiliateToken: widget.affiliateToken, + ), + ), + ); + } + : widget.onNextPressed, + child: Center( + child: Text( + context.derivAuthLocalization.actionNext, + style: TextStyles.button + .copyWith(color: context.theme.colors.prominent), + ), + ), + ), + ); + + void _onSelectCountryTap(List countries) { + final MediaQueryData mediaQuery = MediaQuery.of(context); + final double screenHeight = mediaQuery.size.height; + final double notchHeight = mediaQuery.padding.top; + + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (BuildContext context) => SizedBox( + height: screenHeight - notchHeight, + child: CountrySelectionListWidget( + countries: countries, + onChanged: (int index) => setState( + () { + _countrySelectionCubit.changeSelectedCountry( + selectedCountry: countries[index], + ); + }, + ), + ), + ), + ); + } + + Widget _buildCountryConsentCheckbox({String? countryConsentMessage}) => + BlocBuilder( + bloc: _countrySelectionCubit, + builder: (BuildContext context, DerivCountrySelectionState state) { + final DerivResidenceModel? selectedCountry = state.selectedCountry; + + if (selectedCountry == null || + !state.selectedCountryRequiresConsent) { + return const SizedBox.shrink(); + } + + return CustomCheckbox( + padding: const EdgeInsets.symmetric( + vertical: ThemeProvider.margin16, + ), + contentsVerticalAlignment: CrossAxisAlignment.start, + value: state.agreedToTerms, + onValueChanged: ({bool? isChecked}) => + _countrySelectionCubit.updateCountryConsentStatus( + agreedToTerms: isChecked, + ), + message: countryConsentMessage ?? + getCountryConsentMessage( + context, + countryCode: selectedCountry.code, + ), + ); + }, + ); + + /// Validates the country selection. + String? _countrySelectionValidator( + BuildContext context, { + DerivResidenceModel? selectedCountry, + }) { + if (selectedCountry != null && selectedCountry.isDisabled) { + return context.derivAuthLocalization.warnCountryNotAvailable; + } + + return null; + } + + /// Determines whether the next button should be enabled based on country selection. + /// + /// Returns `true` if the following conditions are met: + /// - A country is selected. + /// - The selected country is not disabled. + /// - If `isConsentRequired` is true, the user must agree to the terms. + /// + /// Otherwise, returns `false`. + bool _shouldEnableNextButton( + DerivResidenceModel? selectedCountry, { + bool agreedToTerms = false, + bool isConsentRequired = false, + }) { + if (selectedCountry == null) { + return false; + } + + bool shouldEnable = !selectedCountry.isDisabled; + + if (isConsentRequired) { + shouldEnable = shouldEnable && agreedToTerms; + } + + return shouldEnable; + } + + /// Determines whether the country selection field should be enabled + /// based on the given state. + bool _shouldEnableCountrySelectionField( + DerivCountrySelectionState countrySelectionState, + ) { + if (countrySelectionState is DerivCountrySelectionLoadedState || + countrySelectionState is DerivCountryChangedState || + countrySelectionState is DerivCountryConsentChangedState) { + return true; + } + + return false; + } + + @override + void dispose() { + _focusNode.dispose(); + + _countrySelectionCubit.close(); + + super.dispose(); + } +} diff --git a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_email_not_received_layout.dart b/packages/deriv_auth/lib/features/signup/presentation/layouts/deriv_email_not_received_layout.dart similarity index 81% rename from packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_email_not_received_layout.dart rename to packages/deriv_auth/lib/features/signup/presentation/layouts/deriv_email_not_received_layout.dart index bad1e2728..115beb538 100644 --- a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_email_not_received_layout.dart +++ b/packages/deriv_auth/lib/features/signup/presentation/layouts/deriv_email_not_received_layout.dart @@ -1,5 +1,5 @@ -import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; -import 'package:deriv_auth_ui/src/core/helpers/assets.dart'; +import 'package:deriv_auth/core/extensions/context_extension.dart'; +import 'package:deriv_auth/core/helpers/assets.dart'; import 'package:deriv_theme/deriv_theme.dart'; import 'package:deriv_ui/deriv_ui.dart'; import 'package:flutter/material.dart'; @@ -42,23 +42,23 @@ class DerivEmailNotReceivedLayout extends StatelessWidget { child: Column( children: [ Text( - context.localization.labelEmailIssueHeader, + context.derivAuthLocalization.labelEmailIssueHeader, style: context.theme.textStyle( textStyle: TextStyles.body2, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), const SizedBox(height: ThemeProvider.margin24), Row( children: [ - SvgPicture.asset(Assets.emailIssue1, package: 'deriv_auth_ui'), + SvgPicture.asset(Assets.emailIssue1, package: 'deriv_auth'), const SizedBox(width: ThemeProvider.margin24), Flexible( child: Text( - context.localization.labelEmailIssueSpam, + context.derivAuthLocalization.labelEmailIssueSpam, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ), @@ -67,14 +67,14 @@ class DerivEmailNotReceivedLayout extends StatelessWidget { const SizedBox(height: ThemeProvider.margin24), Row( children: [ - SvgPicture.asset(Assets.emailIssue2, package: 'deriv_auth_ui'), + SvgPicture.asset(Assets.emailIssue2, package: 'deriv_auth'), const SizedBox(width: ThemeProvider.margin24), Flexible( child: Text( - context.localization.labelEmailIssueWrongEmail, + context.derivAuthLocalization.labelEmailIssueWrongEmail, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ), @@ -83,14 +83,14 @@ class DerivEmailNotReceivedLayout extends StatelessWidget { const SizedBox(height: ThemeProvider.margin24), Row( children: [ - SvgPicture.asset(Assets.emailIssue3, package: 'deriv_auth_ui'), + SvgPicture.asset(Assets.emailIssue3, package: 'deriv_auth'), const SizedBox(width: ThemeProvider.margin24), Flexible( child: Text( - context.localization.labelEmailIssueTypo, + context.derivAuthLocalization.labelEmailIssueTypo, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ), @@ -99,14 +99,14 @@ class DerivEmailNotReceivedLayout extends StatelessWidget { const SizedBox(height: ThemeProvider.margin24), Row( children: [ - SvgPicture.asset(Assets.emailIssue4, package: 'deriv_auth_ui'), + SvgPicture.asset(Assets.emailIssue4, package: 'deriv_auth'), const SizedBox(width: ThemeProvider.margin24), Flexible( child: Text( - context.localization.labelEmailIssueFirewall, + context.derivAuthLocalization.labelEmailIssueFirewall, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ), @@ -127,7 +127,7 @@ class DerivEmailNotReceivedLayout extends StatelessWidget { onPressed: onReEnterEmailPressed, child: Center( child: Text( - context.localization.actionReenterEmail, + context.derivAuthLocalization.actionReenterEmail, style: context.theme.textStyle( textStyle: TextStyles.button .copyWith(color: context.theme.colors.prominent), diff --git a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_set_password_layout.dart b/packages/deriv_auth/lib/features/signup/presentation/layouts/deriv_set_password_layout.dart similarity index 67% rename from packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_set_password_layout.dart rename to packages/deriv_auth/lib/features/signup/presentation/layouts/deriv_set_password_layout.dart index dc4469142..ea3e937c1 100644 --- a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_set_password_layout.dart +++ b/packages/deriv_auth/lib/features/signup/presentation/layouts/deriv_set_password_layout.dart @@ -1,9 +1,7 @@ +import 'package:deriv_auth/core/helpers/assets.dart'; import 'package:deriv_auth/deriv_auth.dart'; -import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; -import 'package:deriv_auth_ui/src/core/extensions/string_extension.dart'; -import 'package:deriv_auth_ui/src/core/helpers/assets.dart'; -import 'package:deriv_auth_ui/src/features/signup/models/deriv_auth_utm_model.dart'; -import 'package:deriv_auth_ui/src/features/signup/widgets/password_policy_checker_widget.dart'; +import 'package:deriv_auth/features/signup/presentation/widgets/password_policy_checker_widget.dart'; +import 'package:deriv_auth/features/single_entry/core/auth_data.dart'; import 'package:deriv_theme/deriv_theme.dart'; import 'package:deriv_ui/deriv_ui.dart'; import 'package:flutter/material.dart'; @@ -14,12 +12,14 @@ import 'package:flutter_svg/flutter_svg.dart'; class DerivSetPasswordLayout extends StatefulWidget { /// constructor of country set password page const DerivSetPasswordLayout({ - required this.onDerivAuthState, required this.onDerivSignupState, required this.onPreviousPressed, required this.verificationCode, required this.residence, + this.authErrorStateHandler, this.utmModel, + this.onAuthError, + this.affiliateToken, Key? key, }) : super(key: key); @@ -32,8 +32,11 @@ class DerivSetPasswordLayout extends StatefulWidget { /// Utm model final DerivAuthUtmModel? utmModel; - /// Callback to be called when auth state changes. - final void Function(BuildContext, DerivAuthState) onDerivAuthState; + /// Extension of base [AuthErrorStateHandler]. If not provided, base implementation will be used. + final AuthErrorStateHandler? authErrorStateHandler; + + /// Callback to be called on [DerivAuthErrorState] + final Function(DerivAuthErrorState)? onAuthError; /// Callback to be called when signup state changes. final void Function(BuildContext, DerivSignupState) onDerivSignupState; @@ -41,6 +44,9 @@ class DerivSetPasswordLayout extends StatefulWidget { /// Callback to be called when previous button is tapped. final VoidCallback onPreviousPressed; + /// Affiliate token. + final String? affiliateToken; + @override State createState() => _DerivSetPasswordLayoutState(); } @@ -63,9 +69,9 @@ class _DerivSetPasswordLayoutState extends State { } @override - Widget build(BuildContext context) => - BlocListener( - listener: widget.onDerivAuthState, + Widget build(BuildContext context) => DerivAuthStateListener( + authErrorStateHandler: widget.authErrorStateHandler, + onError: widget.onAuthError, child: Scaffold( backgroundColor: context.theme.colors.primary, body: SafeArea( @@ -92,10 +98,10 @@ class _DerivSetPasswordLayoutState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox(height: ThemeProvider.margin96), - SvgPicture.asset(Assets.passwordIcon, package: 'deriv_auth_ui'), + SvgPicture.asset(Assets.passwordIcon, package: 'deriv_auth'), const SizedBox(height: ThemeProvider.margin16), Text( - context.localization.labelKeepPassword, + context.derivAuthLocalization.labelKeepPassword, style: TextStyles.title, textAlign: TextAlign.center, ), @@ -123,22 +129,43 @@ class _DerivSetPasswordLayoutState extends State { children: [ Expanded(child: _buildPreviousButton()), const SizedBox(width: ThemeProvider.margin08), - Expanded( - child: BlocConsumer( - listener: widget.onDerivSignupState, - builder: (BuildContext context, DerivSignupState state) => - _buildStartTradingButton(state), + BlocConsumer( + listener: _onAuthState, + builder: (BuildContext context, DerivAuthState state) => Expanded( + child: BlocConsumer( + listener: + AuthData().data.signupPageModel.handleFlowFromPackage + ? (BuildContext context, DerivSignupState state) => + _onSignupState(state) + : widget.onDerivSignupState, + builder: (BuildContext context, DerivSignupState state) => + _buildStartTradingButton(state), + ), ), ), ], ), ); + void _onAuthState(BuildContext context, DerivAuthState state) { + if (state is DerivAuthLoggedInState) { + AuthData().data.loginPageModel.onLoggedIn.call(context, state); + } + } + + Future _onSignupState(DerivSignupState state) async { + if (state is DerivSignupDoneState) { + await context.read().tokenLogin( + state.account?.token ?? 'invalid_token', + ); + } + } + Widget _buildPasswordInput() => BaseTextField( focusNode: _passwordFocusNode, controller: _passwordTextController, obscureText: !_isPasswordVisible, - labelText: context.localization.labelCreatePassword, + labelText: context.derivAuthLocalization.labelCreatePassword, labelColor: context.theme.colors.disabled, focusedLabelColor: context.theme.colors.blue, suffixIcon: IconButton( @@ -164,7 +191,7 @@ class _DerivSetPasswordLayoutState extends State { strokeWidth: 3, ) : Text( - context.localization.actionStartTrading, + context.derivAuthLocalization.actionStartTrading, style: TextStyles.button .copyWith(color: context.theme.colors.prominent), ), @@ -176,10 +203,26 @@ class _DerivSetPasswordLayoutState extends State { ); Widget _buildPreviousButton() => SecondaryButton( - onPressed: widget.onPreviousPressed, + onPressed: AuthData().data.signupPageModel.handleFlowFromPackage + ? () { + Navigator.pushReplacement>( + context, + MaterialPageRoute( + builder: (BuildContext context) => + DerivCountrySelectionLayout( + affiliateToken: widget.affiliateToken, + onNextPressed: () {}, + verificationCode: widget.verificationCode!, + residences: AuthData().data.signupPageModel.residences, + ), + ), + ); + } + : widget.onPreviousPressed, child: Center( child: Text( - context.localization.actionPrevious, + context.derivAuthLocalization.actionPrevious, style: context.theme.textStyle( textStyle: TextStyles.button, ), diff --git a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_signup_layout.dart b/packages/deriv_auth/lib/features/signup/presentation/layouts/deriv_signup_layout.dart similarity index 61% rename from packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_signup_layout.dart rename to packages/deriv_auth/lib/features/signup/presentation/layouts/deriv_signup_layout.dart index 925e3a2a4..1f666abc9 100644 --- a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_signup_layout.dart +++ b/packages/deriv_auth/lib/features/signup/presentation/layouts/deriv_signup_layout.dart @@ -1,8 +1,4 @@ import 'package:deriv_auth/deriv_auth.dart'; -import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; -import 'package:deriv_auth_ui/src/core/extensions/string_extension.dart'; -import 'package:deriv_auth_ui/src/features/login/widgets/deriv_social_auth_divider.dart'; -import 'package:deriv_auth_ui/src/features/login/widgets/deriv_social_auth_panel.dart'; import 'package:deriv_theme/deriv_theme.dart'; import 'package:deriv_ui/deriv_ui.dart'; import 'package:flutter/material.dart'; @@ -14,23 +10,37 @@ import 'package:flutter_bloc/flutter_bloc.dart'; class DerivSignupLayout extends StatefulWidget { /// Initializes [DerivSignupLayout]. const DerivSignupLayout({ - required this.onSocialAuthButtonPressed, required this.onSingupError, required this.onSingupEmailSent, required this.onSignupPressed, required this.onLoginTapped, required this.signupPageLabel, required this.signupPageDescription, + required this.socialAuthStateHandler, + required this.redirectURL, + required this.onWebViewError, + this.onSocialAuthButtonPressed, + this.isSocialAuthEnabled = true, + this.authErrorStateHandler, this.enableReferralSection = true, + this.onAuthError, Key? key, }) : super(key: key); - /// Callback to be called when social auth button is pressed. - final void Function(SocialAuthProvider) onSocialAuthButtonPressed; + /// Callback to be called when social auth button is tapped. + /// Give access to [SocialAuthDto] for 2FA. + final SocialAuthCallback? onSocialAuthButtonPressed; /// Callback to be called when signup error occurs. final Function(DerivSignupErrorState) onSingupError; + /// Extension of base [AuthErrorStateHandler]. If not provided, base implementation will be used. + final AuthErrorStateHandler? authErrorStateHandler; + + /// Callback to be called on [DerivAuthErrorState]. + /// Useful if needed to do anything additional to [authErrorStateHandler]. + final Function(DerivAuthErrorState)? onAuthError; + /// Callback to be called when signup email is sent. final Function(String) onSingupEmailSent; @@ -49,6 +59,18 @@ class DerivSignupLayout extends StatefulWidget { /// Description of signup page. final String signupPageDescription; + /// Whether to display social auth buttons. + final bool isSocialAuthEnabled; + + /// Social auth state handler. + final Function(BuildContext, SocialAuthState) socialAuthStateHandler; + + /// Redirect URL for social auth. + final String redirectURL; + + /// Callback for web view error. + final Function(String) onWebViewError; + @override State createState() => _DerivSignupLayoutState(); } @@ -66,6 +88,7 @@ class _DerivSignupLayoutState extends State { bool isReferralEnabled = false; String get referralCode => referralController.text.trim(); + String get email => emailController.text.trim(); @override @@ -73,43 +96,53 @@ class _DerivSignupLayoutState extends State { backgroundColor: context.theme.colors.primary, appBar: AppBar( elevation: ThemeProvider.zeroMargin, - title: - Text(context.localization.labelSignUp, style: TextStyles.title), + title: Text(context.derivAuthLocalization.labelSignUp, + style: TextStyles.title), backgroundColor: context.theme.colors.secondary, ), - body: BlocConsumer( - listener: _onSignUpState, - builder: (BuildContext context, DerivSignupState state) => Form( - key: formKey, - child: SingleChildScrollView( - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: ThemeProvider.margin16, - vertical: ThemeProvider.margin24, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ..._buildHeaderSection(), - const SizedBox(height: ThemeProvider.margin24), - _buildEmailTextField(), - const SizedBox(height: ThemeProvider.margin36), - if (widget.enableReferralSection) _buildReferralSection(), - const SizedBox(height: ThemeProvider.margin16), - _buildSignUpButton(), - const SizedBox(height: ThemeProvider.margin24), - DerivSocialAuthDivider( - label: context.localization.labelOrSignUpWith, - ), - const SizedBox(height: ThemeProvider.margin24), - DerivSocialAuthPanel( - isEnabled: !isReferralEnabled, - onSocialAuthButtonPressed: - widget.onSocialAuthButtonPressed, - ), - const SizedBox(height: ThemeProvider.margin24), - _buildFooterSection(), - ], + body: DerivAuthStateListener( + authErrorStateHandler: widget.authErrorStateHandler, + onError: widget.onAuthError, + child: BlocConsumer( + listener: _onSignUpState, + builder: (BuildContext context, DerivSignupState state) => Form( + key: formKey, + child: SingleChildScrollView( + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: ThemeProvider.margin16, + vertical: ThemeProvider.margin24, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ..._buildHeaderSection(), + const SizedBox(height: ThemeProvider.margin24), + _buildEmailTextField(), + const SizedBox(height: ThemeProvider.margin36), + if (widget.enableReferralSection) _buildReferralSection(), + const SizedBox(height: ThemeProvider.margin16), + _buildSignUpButton(), + const SizedBox(height: ThemeProvider.margin24), + DerivSocialAuthDivider( + label: context.derivAuthLocalization.labelOrSignUpWith, + isVisible: widget.isSocialAuthEnabled, + ), + if (widget.isSocialAuthEnabled) + const SizedBox(height: ThemeProvider.margin24), + DerivSocialAuthPanel( + isEnabled: !isReferralEnabled, + isVisible: widget.isSocialAuthEnabled, + socialAuthStateHandler: widget.socialAuthStateHandler, + redirectURL: widget.redirectURL, + onWebViewError: widget.onWebViewError, + onPressed: widget.onSocialAuthButtonPressed, + ), + if (widget.isSocialAuthEnabled) + const SizedBox(height: ThemeProvider.margin24), + _buildFooterSection(), + ], + ), ), ), ), @@ -133,15 +166,17 @@ class _DerivSignupLayoutState extends State { Row( children: [ InfoIconButton( - dialogTitle: context.localization.labelReferralInfoTitle, + dialogTitle: + context.derivAuthLocalization.labelReferralInfoTitle, dialogDescription: - context.localization.infoReferralInfoDescription, + context.derivAuthLocalization.infoReferralInfoDescription, + positiveActionLabel: context.derivAuthLocalization.actionOk, iconSize: ThemeProvider.iconSize24, ), const SizedBox(width: ThemeProvider.margin08), Expanded( child: Text( - context.localization.labelGotReferralCode, + context.derivAuthLocalization.labelGotReferralCode, style: context.theme.textStyle( textStyle: TextStyles.body1, color: context.theme.colors.prominent, @@ -187,6 +222,8 @@ class _DerivSignupLayoutState extends State { vertical: ThemeProvider.margin16, ), child: BaseTextField( + semanticLabel: + SemanticsLabels.signupReferralTextFieldSemantic, controller: referralController, onChanged: (_) { if (mounted) { @@ -194,7 +231,7 @@ class _DerivSignupLayoutState extends State { } }, focusNode: referralFocusNode, - labelText: context.localization.labelReferralCode, + labelText: context.derivAuthLocalization.labelReferralCode, borderColor: context.theme.colors.hover, focusedBorderColor: context.theme.colors.blue, textInputAction: TextInputAction.done, @@ -217,7 +254,7 @@ class _DerivSignupLayoutState extends State { widget.signupPageDescription, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ]; @@ -227,10 +264,10 @@ class _DerivSignupLayoutState extends State { mainAxisSize: MainAxisSize.min, children: [ Text( - context.localization.labelHaveAccount, + context.derivAuthLocalization.labelHaveAccount, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), InkWell( @@ -238,7 +275,7 @@ class _DerivSignupLayoutState extends State { child: Padding( padding: const EdgeInsets.all(ThemeProvider.margin04), child: Text( - context.localization.actionLogin, + context.derivAuthLocalization.actionLogin, style: context.theme.textStyle( textStyle: TextStyles.body2, color: context.theme.colors.coral, @@ -251,9 +288,10 @@ class _DerivSignupLayoutState extends State { ); Widget _buildEmailTextField() => BaseTextField( + semanticLabel: SemanticsLabels.signupEmailFieldSemantic, controller: emailController, focusNode: emailFocusNode, - labelText: context.localization.labelEmail, + labelText: context.derivAuthLocalization.labelEmail, borderColor: context.theme.colors.hover, focusedBorderColor: context.theme.colors.blue, keyboardType: TextInputType.emailAddress, @@ -266,25 +304,28 @@ class _DerivSignupLayoutState extends State { }, ); - Widget _buildSignUpButton() => PrimaryButton( - isEnabled: _isFormValid(), - onPressed: _onSignupTapped, - child: Center( - child: - context.read().state is DerivSignupProgressState - ? const LoadingIndicator( - valueColor: Colors.white, - strokeWidth: ThemeProvider.margin02, - height: ThemeProvider.iconSize16, - width: ThemeProvider.iconSize16, - ) - : Text( - context.localization.actionCreateAccount, - style: context.theme.textStyle( - textStyle: TextStyles.body2, - color: context.theme.colors.prominent, - ), + Widget _buildSignUpButton() => Semantics( + label: SemanticsLabels.signupButtonSemantic, + child: PrimaryButton( + isEnabled: _isFormValid(), + onPressed: _onSignupTapped, + child: Center( + child: context.read().state + is DerivSignupProgressState + ? const LoadingIndicator( + valueColor: Colors.white, + strokeWidth: ThemeProvider.margin02, + height: ThemeProvider.iconSize16, + width: ThemeProvider.iconSize16, + ) + : Text( + context.derivAuthLocalization.actionCreateAccount, + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent, ), + ), + ), ), ); @@ -303,20 +344,15 @@ class _DerivSignupLayoutState extends State { _emailValidator(emailController.text) == null && _referralValidator(referralController.text) == null; - String? _emailValidator(String? input) { - if (email.isValidEmail) { - return null; - } - - return context.localization.informInvalidEmailFormat; - } + String? _emailValidator(String? input) => emailValidator( + email, context.derivAuthLocalization.informInvalidEmailFormat); String? _referralValidator(String? input) { if (referralCode.isNotEmpty || !isReferralEnabled) { return null; } - return context.localization.informInvalidReferralCode; + return context.derivAuthLocalization.informInvalidReferralCode; } Future _onSignupTapped() async { diff --git a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_verification_done_layout.dart b/packages/deriv_auth/lib/features/signup/presentation/layouts/deriv_verification_done_layout.dart similarity index 57% rename from packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_verification_done_layout.dart rename to packages/deriv_auth/lib/features/signup/presentation/layouts/deriv_verification_done_layout.dart index 4df6eb34b..8ac024235 100644 --- a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_verification_done_layout.dart +++ b/packages/deriv_auth/lib/features/signup/presentation/layouts/deriv_verification_done_layout.dart @@ -1,5 +1,8 @@ -import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; -import 'package:deriv_auth_ui/src/core/helpers/assets.dart'; +import 'package:deriv_auth/core/extensions/context_extension.dart'; +import 'package:deriv_auth/core/helpers/assets.dart'; +import 'package:deriv_auth/features/signup/models/deriv_residence_model.dart'; +import 'package:deriv_auth/features/signup/presentation/layouts/deriv_country_selection_layout.dart'; +import 'package:deriv_auth/features/single_entry/core/auth_data.dart'; import 'package:deriv_theme/deriv_theme.dart'; import 'package:deriv_ui/deriv_ui.dart'; import 'package:flutter/material.dart'; @@ -12,6 +15,7 @@ class DerivVerificationDoneLayout extends StatelessWidget { required this.verificationCode, required this.onContinuePressed, this.affiliateToken, + this.residences, Key? key, }) : super(key: key); @@ -24,12 +28,15 @@ class DerivVerificationDoneLayout extends StatelessWidget { /// Callback to be called when continue button is tapped. final VoidCallback onContinuePressed; + /// List of residences to be shown. + final Future>? residences; + @override Widget build(BuildContext context) => Scaffold( backgroundColor: context.theme.colors.primary, appBar: AppBar( elevation: ThemeProvider.zeroMargin, - title: Text(context.localization.labelVerifyYourEmail), + title: Text(context.derivAuthLocalization.labelVerifyYourEmail), ), body: SafeArea( child: Column( @@ -52,19 +59,19 @@ class DerivVerificationDoneLayout extends StatelessWidget { children: [ SvgPicture.asset( Assets.emailReadIcon, - package: 'deriv_auth_ui', + package: 'deriv_auth', ), const SizedBox(height: ThemeProvider.margin16), Text( - context.localization.labelThanksEmail, + context.derivAuthLocalization.labelThanksEmail, style: TextStyles.title, ), const SizedBox(height: ThemeProvider.margin08), Text( - context.localization.informLetsContinue, + context.derivAuthLocalization.informLetsContinue, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ], @@ -77,10 +84,31 @@ class DerivVerificationDoneLayout extends StatelessWidget { ); Widget _buildContinueButton(BuildContext context) => PrimaryButton( - onPressed: onContinuePressed, + onPressed: AuthData().data.signupPageModel.handleFlowFromPackage + ? () async { + final List residencesList = + await residences!; + + // Navigate to the new page once the residences are loaded + await Navigator.pushReplacement>( + context, + MaterialPageRoute( + builder: (BuildContext context) => + DerivCountrySelectionLayout( + affiliateToken: affiliateToken, + verificationCode: verificationCode, + onNextPressed: () {}, + residences: Future>.value( + residencesList), + ), + ), + ); + } + : onContinuePressed, child: Center( child: Text( - context.localization.actionContinue, + context.derivAuthLocalization.actionContinue, style: context.theme.textStyle( textStyle: TextStyles.button .copyWith(color: context.theme.colors.prominent), diff --git a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_verify_email_layout.dart b/packages/deriv_auth/lib/features/signup/presentation/layouts/deriv_verify_email_layout.dart similarity index 84% rename from packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_verify_email_layout.dart rename to packages/deriv_auth/lib/features/signup/presentation/layouts/deriv_verify_email_layout.dart index 46a525d8b..eb2996d7d 100644 --- a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_verify_email_layout.dart +++ b/packages/deriv_auth/lib/features/signup/presentation/layouts/deriv_verify_email_layout.dart @@ -1,5 +1,5 @@ -import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; -import 'package:deriv_auth_ui/src/core/helpers/assets.dart'; +import 'package:deriv_auth/core/extensions/context_extension.dart'; +import 'package:deriv_auth/core/helpers/assets.dart'; import 'package:deriv_theme/deriv_theme.dart'; import 'package:deriv_ui/deriv_ui.dart'; import 'package:flutter/material.dart'; @@ -26,7 +26,7 @@ class DerivVerifyEmailLayout extends StatelessWidget { appBar: AppBar( elevation: ThemeProvider.zeroMargin, title: Text( - context.localization.labelVerifyYourEmail, + context.derivAuthLocalization.labelVerifyYourEmail, style: TextStyles.title, ), backgroundColor: context.theme.colors.secondary, @@ -52,19 +52,19 @@ class DerivVerifyEmailLayout extends StatelessWidget { children: [ SvgPicture.asset( Assets.emailUnreadIcon, - package: 'deriv_auth_ui', + package: 'deriv_auth', ), const SizedBox(height: ThemeProvider.margin16), Text( - context.localization.labelCheckEmail, + context.derivAuthLocalization.labelCheckEmail, style: TextStyles.title, ), const SizedBox(height: ThemeProvider.margin08), Text( - context.localization.informVerificationEmailSent(email!), + context.derivAuthLocalization.informVerificationEmailSent(email!), style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), textAlign: TextAlign.center, ), @@ -81,7 +81,7 @@ class DerivVerifyEmailLayout extends StatelessWidget { onPressed: onEmailNotReceivedPressed, child: Center( child: Text( - context.localization.actionEmailNotReceived, + context.derivAuthLocalization.actionEmailNotReceived, style: context.theme.textStyle( textStyle: TextStyles.button, ), diff --git a/packages/deriv_auth_ui/lib/src/features/signup/widgets/country_selection_list_widget.dart b/packages/deriv_auth/lib/features/signup/presentation/widgets/country_selection_list_widget.dart similarity index 93% rename from packages/deriv_auth_ui/lib/src/features/signup/widgets/country_selection_list_widget.dart rename to packages/deriv_auth/lib/features/signup/presentation/widgets/country_selection_list_widget.dart index 0e4fa9133..5b72f3fc0 100644 --- a/packages/deriv_auth_ui/lib/src/features/signup/widgets/country_selection_list_widget.dart +++ b/packages/deriv_auth/lib/features/signup/presentation/widgets/country_selection_list_widget.dart @@ -1,5 +1,5 @@ -import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; -import 'package:deriv_auth_ui/src/features/signup/models/deriv_residence_model.dart'; +import 'package:deriv_auth/core/extensions/context_extension.dart'; +import 'package:deriv_auth/features/signup/models/deriv_residence_model.dart'; import 'package:deriv_theme/deriv_theme.dart'; import 'package:flutter/material.dart'; @@ -102,7 +102,7 @@ class _CountrySelectionListWidgetState children: [ const SizedBox(width: ThemeProvider.margin48), Text( - context.localization.labelChooseCountry, + context.derivAuthLocalization.labelChooseCountry, style: TextStyles.subheading, ), ], @@ -113,7 +113,7 @@ class _CountrySelectionListWidgetState focusNode: _searchFocusNode, style: context.theme.textStyle( textStyle: TextStyles.subheading, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), decoration: InputDecoration( border: InputBorder.none, @@ -128,7 +128,7 @@ class _CountrySelectionListWidgetState onPressed: () => _searchController.clear(), ), contentPadding: const EdgeInsets.only(top: ThemeProvider.margin08), - hintText: context.localization.labelSearchCountry, + hintText: context.derivAuthLocalization.labelSearchCountry, hintStyle: context.theme.textStyle( textStyle: TextStyles.subheading, color: context.theme.colors.active, @@ -145,7 +145,7 @@ class _CountrySelectionListWidgetState _filteredCountries[index].name, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ), diff --git a/packages/deriv_auth_ui/lib/src/features/signup/widgets/password_policy_checker_widget.dart b/packages/deriv_auth/lib/features/signup/presentation/widgets/password_policy_checker_widget.dart similarity index 86% rename from packages/deriv_auth_ui/lib/src/features/signup/widgets/password_policy_checker_widget.dart rename to packages/deriv_auth/lib/features/signup/presentation/widgets/password_policy_checker_widget.dart index 68dc90052..2bb6c1b5b 100644 --- a/packages/deriv_auth_ui/lib/src/features/signup/widgets/password_policy_checker_widget.dart +++ b/packages/deriv_auth/lib/features/signup/presentation/widgets/password_policy_checker_widget.dart @@ -1,5 +1,5 @@ -import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; -import 'package:deriv_auth_ui/src/features/signup/models/deriv_password_policy_model.dart'; +import 'package:deriv_auth/core/extensions/context_extension.dart'; +import 'package:deriv_auth/features/signup/models/deriv_password_policy_model.dart'; import 'package:deriv_theme/deriv_theme.dart'; import 'package:deriv_ui/deriv_ui.dart'; import 'package:flutter/material.dart'; @@ -40,19 +40,20 @@ class PasswordPolicyCheckerWidget extends StatelessWidget { BuildContext context) => [ DerivPasswordPolicyModel( - description: context.localization.informPasswordPolicyLength, + description: context.derivAuthLocalization.informPasswordPolicyLength, regex: validPasswordLengthRegex, ), DerivPasswordPolicyModel( - description: context.localization.informPasswordPolicyLowerAndUpper, + description: + context.derivAuthLocalization.informPasswordPolicyLowerAndUpper, regex: validPasswordWithUppercaseRegex, ), DerivPasswordPolicyModel( - description: context.localization.informPasswordPolicyNumber, + description: context.derivAuthLocalization.informPasswordPolicyNumber, regex: validPasswordWithNumberRegex, ), DerivPasswordPolicyModel( - description: context.localization.warnPasswordContainsSymbol, + description: context.derivAuthLocalization.warnPasswordContainsSymbol, regex: validPasswordWithSymbols, isOptional: true, ), @@ -66,10 +67,10 @@ class PasswordPolicyCheckerWidget extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - context.localization.informPasswordPolicy, + context.derivAuthLocalization.informPasswordPolicy, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), const SizedBox(height: ThemeProvider.margin04), @@ -114,7 +115,7 @@ class PasswordPolicyCheckerWidget extends StatelessWidget { textStyle: TextStyles.body1, color: policy.isMatchWith(password) ? context.theme.colors.hover - : context.theme.colors.lessProminent, + : context.theme.colors.general, ); Widget _buildPolicyIcon({ @@ -129,7 +130,7 @@ class PasswordPolicyCheckerWidget extends StatelessWidget { policy.isMatchWith(password) ? Icons.check : Icons.circle; final Color color = policy.isMatchWith(password) - ? context.theme.colors.lessProminent + ? context.theme.colors.general : context.theme.colors.coral; return Padding( diff --git a/packages/deriv_auth/lib/features/signup/signup.dart b/packages/deriv_auth/lib/features/signup/signup.dart new file mode 100644 index 000000000..5b3b7d992 --- /dev/null +++ b/packages/deriv_auth/lib/features/signup/signup.dart @@ -0,0 +1,15 @@ +export 'cubit/deriv_country_selection_cubit.dart'; +export 'cubit/signup_cubit.dart'; +export 'models/deriv_auth_utm_model.dart'; +export 'models/deriv_password_policy_model.dart'; +export 'models/deriv_residence_model.dart'; +export 'models/new_virtual_account_request_model.dart'; +export 'models/signup_error_type.dart'; +export 'presentation/layouts/deriv_country_selection_layout.dart'; +export 'presentation/layouts/deriv_email_not_received_layout.dart'; +export 'presentation/layouts/deriv_set_password_layout.dart'; +export 'presentation/layouts/deriv_signup_layout.dart'; +export 'presentation/layouts/deriv_verification_done_layout.dart'; +export 'presentation/layouts/deriv_verify_email_layout.dart'; +export 'repository/base_signup_repository.dart'; +export 'services/deriv_signup_service.dart'; diff --git a/packages/deriv_auth/lib/features/single_entry/README.md b/packages/deriv_auth/lib/features/single_entry/README.md new file mode 100644 index 000000000..7068bfb36 --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/README.md @@ -0,0 +1,84 @@ +## Single Entry: + +Single entry will enable you to just call one Widget and get the entire Authentication flow
+ +#### Example: + +```Dart +AuthEntryPage( + AuthEntryModel( + getStartedPage: GetStartedPageModel( + slides: [ + DerivGetStartedSlideModel( + imagePath: 'lib/auth/assets/images/charts.svg', + supportingText: 'Charts', + ), + DerivGetStartedSlideModel( + imagePath: 'lib/auth/assets/images/live_chat.svg', + supportingText: 'Live Chat', + ), + DerivGetStartedSlideModel( + imagePath: 'lib/auth/assets/images/markets.svg', + supportingText: 'Markets', + ), + ], + appLogoIconPath: + 'lib/auth/assets/images/ic_logo_extended.svg', + backgroundImagePath: 'lib/auth/assets/images/triangles.svg', + ), + loginPageModel: LoginPageModel( + welcomeLabel: 'Welcome back testing!', + onLoggedIn: (_) => HomePageRoute().go(context), + authErrorStateHandler: + AuthErrorStateHandler(context: context), + onLoginError: (_) {}, + onSocialAuthButtonPressed: (_) {}, + isSocialAuthEnabled: false, + socialAuthStateHandler: (SocialAuthState) {}, + redirectURL: '', + onWebViewError: (String) {}, + ), + derivAuthCubit: BlocProvider.of(context), + signupPageModel: SignupPageModel( + verificationCode: '', + signupPageLable: 'Start trading with Deriv', + signupPageDescription: + 'Join over 1 million traders worldwide who loves trading at Deriv.', + isSocialAuthEnabled: false, + onSingupError: (DerivSignupErrorState) {}, + socialAuthStateHandler: (SocialAuthState) {}, + redirectURL: '', + onWebViewError: (String) {}, + ), + settingPageModel: SettingPageModel( + appLabel: 'dblanc', + appId: () => PrefService.getString('appId') ?? defaultAppId, + endpoint: () => + PrefService.getString('endpoint') ?? defaultEndpoint, + saveValues: _saveValues, + updateFlavorConfigs: _updateFlavorConfigs, + ), + resetPassPageModel: ResetPassPageModel( + onResetPassSucceed: () {}, + onResetPassError: ({ + required bool isLinkExpired, + String? error, + }) {}, + residences: Future.value([ + const DerivResidenceModel( + isDisabled: false, + name: 'Indonecsia', + code: 'ID', + ), + const DerivResidenceModel( + isDisabled: true, + name: 'England', + code: 'UK', + ), + ]), + userResidence: 'id', + ), + ), + ), + +``` diff --git a/packages/deriv_auth/lib/features/single_entry/core/auth_data.dart b/packages/deriv_auth/lib/features/single_entry/core/auth_data.dart new file mode 100644 index 000000000..1478de792 --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/core/auth_data.dart @@ -0,0 +1,29 @@ +// ignore_for_file: unnecessary_getters_setters + +import 'package:deriv_auth/core/services/jwt/repository/deriv_jwt_repository.dart'; +import 'package:deriv_auth/core/services/jwt/services/deriv_jwt_service.dart'; +import 'package:deriv_auth/core/services/token/services/deriv_token_service.dart'; +import 'package:deriv_auth/features/auth/auth.dart'; +import 'package:deriv_auth/features/social_auth/cubit/social_auth_cubit.dart'; + +import 'models/auth_entry_model.dart'; + +/// Authentication Data Singleton +class AuthData { + /// AuthData instance + factory AuthData() => _instance; + + AuthData._internal(); + + static final AuthData _instance = AuthData._internal(); + + late AuthEntryModel _data; + + /// data getter + AuthEntryModel get data => _data; + + /// data setter + set data(AuthEntryModel data) { + _data = data; + } +} diff --git a/packages/deriv_auth/lib/features/single_entry/core/auth_provider.dart b/packages/deriv_auth/lib/features/single_entry/core/auth_provider.dart new file mode 100644 index 000000000..20d6e6195 --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/core/auth_provider.dart @@ -0,0 +1,59 @@ +import 'package:deriv_auth/features/auth/cubit/deriv_auth_cubit.dart'; +import 'package:deriv_auth/features/reset_password/cubit/reset_password_cubit.dart'; +import 'package:deriv_auth/features/signup/cubit/signup_cubit.dart'; +import 'package:deriv_auth/features/social_auth/cubit/social_auth_cubit.dart'; +import 'package:deriv_language_selector/deriv_language_selector.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +/// A wrapper class for clients apps to provide auth related cubits to the context. +class AuthProvider extends StatelessWidget { + /// Constructor [AuthProvider] + const AuthProvider({ + required this.widget, + required this.derivAuthCubit, + required this.socialAuthCubit, + required this.derivResetPassCubit, + required this.derivSignupCubit, + required this.languageCubit, + }); + + /// Child widget + final Widget widget; + + /// Instance of Auth Cubit + final DerivAuthCubit derivAuthCubit; + + /// Instance of Social Auth Cubit + final SocialAuthCubit socialAuthCubit; + + /// Instance of Reset Pass Cubit + final DerivResetPassCubit derivResetPassCubit; + + /// Instance of Signup Cubit + final DerivSignupCubit derivSignupCubit; + + /// Instance of language Cubit + final LanguageCubit languageCubit; + + Widget build(BuildContext context) => MultiBlocProvider( + providers: [ + BlocProvider.value( + value: derivAuthCubit, + ), + BlocProvider.value( + value: socialAuthCubit, + ), + BlocProvider.value( + value: derivResetPassCubit, + ), + BlocProvider.value( + value: derivSignupCubit, + ), + BlocProvider.value( + value: languageCubit, + ), + ], + child: widget, + ); +} diff --git a/packages/deriv_auth/lib/features/single_entry/core/models/auth_entry_model.dart b/packages/deriv_auth/lib/features/single_entry/core/models/auth_entry_model.dart new file mode 100644 index 000000000..54a93e146 --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/core/models/auth_entry_model.dart @@ -0,0 +1,37 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_passkeys/deriv_passkeys.dart'; + +/// Auth Entry Model class +class AuthEntryModel { + /// Constructor [AuthEntryModel] + const AuthEntryModel({ + required this.getStartedPage, + required this.loginPageModel, + required this.derivAuthCubit, + required this.derivPasskeysBloc, + required this.signupPageModel, + required this.settingPageModel, + required this.resetPassPageModel, + }); + + /// GetStartedPage data model + final GetStartedPageModel getStartedPage; + + /// LoginPage data model + final LoginPageModel loginPageModel; + + /// DerivAuthCubit + final DerivAuthCubit derivAuthCubit; + + /// DerivAuthCubit + final DerivPasskeysBloc derivPasskeysBloc; + + /// SignupPage data model + final SignupPageModel signupPageModel; + + /// SettingPage data model + final SettingPageModel settingPageModel; + + /// ResetPassPage data model + final ResetPassPageModel resetPassPageModel; +} diff --git a/packages/deriv_auth/lib/features/single_entry/core/models/get_started_page_model.dart b/packages/deriv_auth/lib/features/single_entry/core/models/get_started_page_model.dart new file mode 100644 index 000000000..1bf244b2a --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/core/models/get_started_page_model.dart @@ -0,0 +1,20 @@ +import 'package:deriv_auth/deriv_auth.dart'; + +/// Get Started Page Model +class GetStartedPageModel { + /// Constructor [GetStartedPageModel] + const GetStartedPageModel({ + required this.slides, + required this.appLogoIconPath, + required this.backgroundImagePath, + }); + + /// List of Deriv Get Started page Slides + final List slides; + + /// Logo icon path + final String appLogoIconPath; + + /// Background image path + final String backgroundImagePath; +} diff --git a/packages/deriv_auth/lib/features/single_entry/core/models/login_page_model.dart b/packages/deriv_auth/lib/features/single_entry/core/models/login_page_model.dart new file mode 100644 index 000000000..069ccb832 --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/core/models/login_page_model.dart @@ -0,0 +1,67 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:flutter/material.dart'; + +/// LoginPageModel class +class LoginPageModel { + /// Initializes [LoginPageModel]. + const LoginPageModel({ + required this.onLoggedIn, + required this.onSocialAuthButtonPressed, + required this.welcomeLabel, + required this.greetingLabel, + required this.socialAuthStateHandler, + required this.redirectURL, + required this.onWebViewError, + this.isForgotPasswordEnabled = true, + this.isCreateAccountEnabled = true, + this.isSocialAuthEnabled = true, + this.authErrorStateHandler, + this.onLoginError, + this.onLoginTapped, + this.twoFactorAuthNavigation, + }); + + /// Extension of base [AuthErrorStateHandler]. If not provided, base implementation will be used. + final AuthErrorStateHandler? authErrorStateHandler; + + /// Callback to be called when login error occurs. + final Function(DerivAuthErrorState)? onLoginError; + + /// Callback to be called when user is logged in. + final Function(BuildContext, DerivAuthLoggedInState) onLoggedIn; + + /// Callback to be called when social auth button is tapped. + /// Give access to [SocialAuthDto] for 2FA. + final SocialAuthCallback? onSocialAuthButtonPressed; + + /// Welcome text to be displayed on login page. + final String welcomeLabel; + + /// Whether to display social auth buttons. + final bool isSocialAuthEnabled; + + /// Whether to display forgot password section. + final bool isForgotPasswordEnabled; + + /// Whether to display create account section. + final bool isCreateAccountEnabled; + + /// Social auth state handler. + final Function(BuildContext, SocialAuthState) socialAuthStateHandler; + + /// Redirect URL for social auth. + final String redirectURL; + + /// Callback for web view error. + final Function(String) onWebViewError; + + /// Callback to be called when login button is tapped. + /// Give access to email and password. + final Function(String email, String password)? onLoginTapped; + + /// 2FA flow navigation. + final Function(BuildContext context)? twoFactorAuthNavigation; + + /// Greeting label string for login page. + final String greetingLabel; +} diff --git a/packages/deriv_auth/lib/features/single_entry/core/models/models.dart b/packages/deriv_auth/lib/features/single_entry/core/models/models.dart new file mode 100644 index 000000000..4aea9f511 --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/core/models/models.dart @@ -0,0 +1,6 @@ +export 'auth_entry_model.dart'; +export 'get_started_page_model.dart'; +export 'login_page_model.dart'; +export 'signup_page_model.dart'; +export 'setting_page_model.dart'; +export 'reset_pass_page_model.dart'; diff --git a/packages/deriv_auth/lib/features/single_entry/core/models/reset_pass_page_model.dart b/packages/deriv_auth/lib/features/single_entry/core/models/reset_pass_page_model.dart new file mode 100644 index 000000000..b19e0dcc7 --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/core/models/reset_pass_page_model.dart @@ -0,0 +1,25 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:flutter/foundation.dart' show VoidCallback; + +/// Reset password page model +class ResetPassPageModel { + /// Constructor [ResetPassPageModel] + const ResetPassPageModel({ + required this.onResetPassError, + required this.residences, + required this.userResidence, + this.onResetPassSucceed, + }); + + /// Callback to be called when reset pass fails. + final ResetPassErrorCallback onResetPassError; + + /// Callback to be called when reset pass succeeds. + final VoidCallback? onResetPassSucceed; + + /// List of residences to be shown. + final Future> residences; + + /// User residence like 'id' + final String userResidence; +} diff --git a/packages/deriv_auth/lib/features/single_entry/core/models/setting_page_model.dart b/packages/deriv_auth/lib/features/single_entry/core/models/setting_page_model.dart new file mode 100644 index 000000000..3093a9857 --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/core/models/setting_page_model.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +/// Setting page model +class SettingPageModel { + /// Constructor [SettingPageModel] + SettingPageModel({ + required this.appLabel, + required this.appId, + required this.endpoint, + required this.saveValues, + required this.updateFlavorConfigs, + this.settingsPageNavigation, + }); + + /// Setting page App label + final String appLabel; + + /// application id + final String Function() appId; + + /// end point + final String Function() endpoint; + + /// Save values to shared preferences + final Function({required String endpoint, required String appId}) saveValues; + + /// Update flavor configurations + final Function({required String endpoint, required String appId}) + updateFlavorConfigs; + + /// Settings page navigation + final Function(BuildContext context)? settingsPageNavigation; +} diff --git a/packages/deriv_auth/lib/features/single_entry/core/models/signup_page_model.dart b/packages/deriv_auth/lib/features/single_entry/core/models/signup_page_model.dart new file mode 100644 index 000000000..60a8284fa --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/core/models/signup_page_model.dart @@ -0,0 +1,63 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:flutter/foundation.dart' show VoidCallback; +import 'package:flutter/material.dart'; + +/// Sign-up page model +class SignupPageModel { + /// Constructor [SignupPageModel] + SignupPageModel({ + required this.verificationCode, + required this.signupPageLable, + required this.signupPageDescription, + required this.onSingupError, + required this.isSocialAuthEnabled, + required this.socialAuthStateHandler, + required this.redirectURL, + required this.onWebViewError, + required this.handleFlowFromPackage, + required this.residences, + this.onSignupPressed, + this.onSocialAuthButtonPressed, + this.affiliateToken, + }); + + /// verification code + final String verificationCode; + + /// affiliate token + final String? affiliateToken; + + /// Callback to be called when signup button is pressed. + final VoidCallback? onSignupPressed; + + /// Callback to be called when signup error occurs. + final Function(DerivSignupErrorState) onSingupError; + + /// sing-up page label + final String signupPageLable; + + /// sign-up page description + final String signupPageDescription; + + /// is social auth enabled + final bool isSocialAuthEnabled; + + /// Social auth state handler. + final Function(BuildContext, SocialAuthState) socialAuthStateHandler; + + /// Redirect URL for social auth. + final String redirectURL; + + /// Callback for web view error. + final Function(String) onWebViewError; + + /// Callback to be called when social auth button is tapped. + /// Give access to [SocialAuthDto] for 2FA. + final SocialAuthCallback? onSocialAuthButtonPressed; + + /// Handle flow from package. + final bool handleFlowFromPackage; + + /// List of residences to be shown. + final Future> residences; +} diff --git a/packages/deriv_auth/lib/features/single_entry/features/get_started/pages/get_started_page.dart b/packages/deriv_auth/lib/features/single_entry/features/get_started/pages/get_started_page.dart new file mode 100644 index 000000000..c82f1d293 --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/features/get_started/pages/get_started_page.dart @@ -0,0 +1,39 @@ +import 'package:deriv_auth/features/get_started/presentation/layouts/deriv_get_started_layout.dart'; +import 'package:deriv_auth/features/single_entry/core/auth_data.dart'; +import 'package:deriv_auth/features/single_entry/features/login/pages/login_page.dart'; +import 'package:deriv_auth/features/single_entry/features/settings/pages/settings_page.dart'; +import 'package:deriv_auth/features/single_entry/features/signup/pages/signup_page.dart'; +import 'package:flutter/material.dart'; + +/// Get started page +class GetStartedPage extends StatelessWidget { + /// Constructor [GetStartedPage] + const GetStartedPage(); + + @override + Widget build(BuildContext context) => DerivGetStartedLayout( + slides: AuthData().data.getStartedPage.slides, + appLogoIconPath: AuthData().data.getStartedPage.appLogoIconPath, + backgroundImagePath: AuthData().data.getStartedPage.backgroundImagePath, + onLoginTapped: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => const LoginPage(), + ), + ), + onSignupTapped: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => const SignupPage(), + ), + ), + onTapNavigation: + AuthData().data.settingPageModel.settingsPageNavigation ?? + (BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => const SettingsPage(), + ), + ); + }, + ); +} diff --git a/packages/deriv_auth/lib/features/single_entry/features/home/pages/home_page.dart b/packages/deriv_auth/lib/features/single_entry/features/home/pages/home_page.dart new file mode 100644 index 000000000..b1ae6832a --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/features/home/pages/home_page.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Deriv Auth UI'), + ), + body: const Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Deriv Auth UI', + style: TextStyle(fontSize: 24), + ), + SizedBox(height: 16), + Text( + 'Deriv Auth UI is a Flutter package that provides a set of ready-to-use widgets for building authentication flows in your app.', + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + } +} diff --git a/packages/deriv_auth/lib/features/single_entry/features/login/pages/login_page.dart b/packages/deriv_auth/lib/features/single_entry/features/login/pages/login_page.dart new file mode 100644 index 000000000..9b90478a1 --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/features/login/pages/login_page.dart @@ -0,0 +1,54 @@ +import 'package:deriv_auth/features/auth/cubit/deriv_auth_cubit.dart'; +import 'package:deriv_auth/features/login/presentation/layouts/deriv_login_layout.dart'; +import 'package:deriv_auth/features/single_entry/core/auth_data.dart'; +import 'package:deriv_auth/features/single_entry/features/reset_pass/pages/reset_pass_page.dart'; +import 'package:deriv_auth/features/single_entry/features/signup/pages/signup_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +/// Login page +class LoginPage extends StatefulWidget { + /// Constructor [LoginPage] + const LoginPage({super.key}); + + @override + State createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + @override + void initState() { + BlocProvider.of(context).logout(); + super.initState(); + } + + @override + Widget build(BuildContext context) => DerivLoginLayout( + onLoginTapped: AuthData().data.loginPageModel.onLoginTapped, + welcomeLabel: AuthData().data.loginPageModel.welcomeLabel, + onLoggedIn: AuthData().data.loginPageModel.onLoggedIn, + authErrorStateHandler: + AuthData().data.loginPageModel.authErrorStateHandler, + onLoginError: AuthData().data.loginPageModel.onLoginError, + onResetPassTapped: () => Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => const ResetPassPage(), + ), + ), + onSignupTapped: () => Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (BuildContext context) => const SignupPage(), + ), + ), + onSocialAuthButtonPressed: + AuthData().data.loginPageModel.onSocialAuthButtonPressed, + socialAuthStateHandler: + AuthData().data.loginPageModel.socialAuthStateHandler, + redirectURL: AuthData().data.loginPageModel.redirectURL, + onWebViewError: AuthData().data.loginPageModel.onWebViewError, + twoFactorAuthNavigation: + AuthData().data.loginPageModel.twoFactorAuthNavigation, + ); +} diff --git a/packages/deriv_auth/lib/features/single_entry/features/reset_pass/pages/choose_new_password_page.dart b/packages/deriv_auth/lib/features/single_entry/features/reset_pass/pages/choose_new_password_page.dart new file mode 100644 index 000000000..7b1ed804b --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/features/reset_pass/pages/choose_new_password_page.dart @@ -0,0 +1,29 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth/features/single_entry/core/auth_data.dart'; +import 'package:deriv_auth/features/single_entry/features/reset_pass/pages/reset_pass_success_page.dart'; +import 'package:flutter/material.dart'; + +/// Choose new password page +class ChooseNewPasswordPage extends StatelessWidget { + /// Constructor [ChooseNewPasswordPage] + const ChooseNewPasswordPage({required this.token, super.key}); + + /// Token + final String token; + + @override + Widget build(BuildContext context) => DerivChooseNewPassLayout( + token: token, + onResetPassSucceed: + AuthData().data.resetPassPageModel.onResetPassSucceed ?? + () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => + const ResetPassSuccessPage(), + ), + ); + }, + onResetPassError: AuthData().data.resetPassPageModel.onResetPassError, + ); +} diff --git a/packages/deriv_auth/lib/features/single_entry/features/reset_pass/pages/reset_pass_page.dart b/packages/deriv_auth/lib/features/single_entry/features/reset_pass/pages/reset_pass_page.dart new file mode 100644 index 000000000..0c0bcf3c7 --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/features/reset_pass/pages/reset_pass_page.dart @@ -0,0 +1,19 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth/features/single_entry/core/auth_data.dart'; +import 'package:flutter/material.dart'; + +/// Reset password page +class ResetPassPage extends StatefulWidget { + /// Constructor for reset password page + const ResetPassPage({super.key}); + + @override + State createState() => _ResetPasswordPageState(); +} + +class _ResetPasswordPageState extends State { + @override + Widget build(BuildContext context) => DerivResetPassLayout( + onResetPassError: AuthData().data.resetPassPageModel.onResetPassError, + ); +} diff --git a/packages/deriv_auth/lib/features/single_entry/features/reset_pass/pages/reset_pass_success_page.dart b/packages/deriv_auth/lib/features/single_entry/features/reset_pass/pages/reset_pass_success_page.dart new file mode 100644 index 000000000..d2ac07fa9 --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/features/reset_pass/pages/reset_pass_success_page.dart @@ -0,0 +1,43 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth/features/single_entry/core/auth_data.dart'; +import 'package:deriv_auth/features/single_entry/features/login/pages/login_page.dart'; +import 'package:flutter/material.dart'; + +/// Reset Password success page +class ResetPassSuccessPage extends StatefulWidget { + /// Constructor [ResetPassSuccessPage] + const ResetPassSuccessPage({super.key}); + + @override + State createState() => _ResetPassSuccessPageState(); +} + +class _ResetPassSuccessPageState extends State { + static const Duration _successPageHoldDuration = Duration(seconds: 2); + + @override + void initState() { + super.initState(); + + // wait for either [_successPageHoldDuration] or logout to finish + // then navigate to loginPage + Future.wait( + >[ + Future.delayed(_successPageHoldDuration), + AuthData().data.derivAuthCubit.logout(), + ], + ).then( + (_) { + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (context) => LoginPage(), + ), + (Route route) => route is ModalRoute && route.isFirst, + ); + }, + ); + } + + @override + Widget build(BuildContext context) => const DerivSuccessPassChangeLayout(); +} diff --git a/packages/deriv_auth/lib/features/single_entry/features/settings/pages/settings_page.dart b/packages/deriv_auth/lib/features/single_entry/features/settings/pages/settings_page.dart new file mode 100644 index 000000000..149c3a540 --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/features/settings/pages/settings_page.dart @@ -0,0 +1,24 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth/features/single_entry/core/auth_data.dart'; +import 'package:flutter/material.dart'; + +/// Setting page +class SettingsPage extends StatefulWidget { + /// Constructor for setting page + const SettingsPage({super.key}); + + @override + State createState() => _SettingsPageState(); +} + +class _SettingsPageState extends State { + @override + Widget build(BuildContext context) => DerivSettingLayout( + updateFlavorConfigs: + AuthData().data.settingPageModel.updateFlavorConfigs, + appLabel: AuthData().data.settingPageModel.appLabel, + saveValues: AuthData().data.settingPageModel.saveValues, + appId: AuthData().data.settingPageModel.appId.call(), + endpoint: AuthData().data.settingPageModel.endpoint.call(), + ); +} diff --git a/packages/deriv_auth/lib/features/single_entry/features/signup/pages/country_selection_page.dart b/packages/deriv_auth/lib/features/single_entry/features/signup/pages/country_selection_page.dart new file mode 100644 index 000000000..a9b0f03fe --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/features/signup/pages/country_selection_page.dart @@ -0,0 +1,37 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth/features/single_entry/core/auth_data.dart'; +import 'package:deriv_auth/features/single_entry/features/signup/pages/set_password_page.dart'; +import 'package:flutter/material.dart'; + +/// Country selection page +class CountrySelectionPage extends StatelessWidget { + /// + const CountrySelectionPage({ + required this.verificationCode, + this.affiliateToken, + super.key, + }); + + /// Verification code + final String verificationCode; + + /// + final String? affiliateToken; + + @override + Widget build(BuildContext context) => DerivCountrySelectionLayout( + onNextPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => SetPasswordPage( + verificationCode: verificationCode, + affiliateToken: affiliateToken, + ), + ), + ); + }, + residences: AuthData().data.resetPassPageModel.residences, + verificationCode: verificationCode, + affiliateToken: affiliateToken, + ); +} diff --git a/packages/deriv_auth/lib/features/single_entry/features/signup/pages/set_password_page.dart b/packages/deriv_auth/lib/features/single_entry/features/signup/pages/set_password_page.dart new file mode 100644 index 000000000..6950bcdb3 --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/features/signup/pages/set_password_page.dart @@ -0,0 +1,43 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth/features/single_entry/core/auth_data.dart'; +import 'package:deriv_auth/features/single_entry/features/signup/pages/country_selection_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +/// Set password page +class SetPasswordPage extends StatelessWidget { + /// Constructor [SetPasswordPage] + const SetPasswordPage({ + required this.verificationCode, + this.affiliateToken, + super.key, + }); + + /// Verification code + final String verificationCode; + + /// + final String? affiliateToken; + + @override + Widget build(BuildContext context) => DerivSetPasswordLayout( + authErrorStateHandler: AuthErrorStateHandler(context: context), + onDerivSignupState: (BuildContext constext, DerivSignupState state) { + if (state is DerivSignupDoneState) { + context + .read() + .tokenLogin(state.account?.token ?? 'defaultToken'); + } + }, + onPreviousPressed: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => CountrySelectionPage( + verificationCode: verificationCode, + affiliateToken: affiliateToken, + ), + ), + ), + residence: AuthData().data.resetPassPageModel.userResidence, + verificationCode: verificationCode, + ); +} diff --git a/packages/deriv_auth/lib/features/single_entry/features/signup/pages/signup_page.dart b/packages/deriv_auth/lib/features/single_entry/features/signup/pages/signup_page.dart new file mode 100644 index 000000000..0a13d63fd --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/features/signup/pages/signup_page.dart @@ -0,0 +1,46 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth/features/single_entry/core/auth_data.dart'; +import 'package:deriv_auth/features/single_entry/features/login/pages/login_page.dart'; +import 'package:deriv_auth/features/single_entry/features/signup/pages/verify_email_page.dart'; +import 'package:flutter/material.dart'; + +/// Sing up page +class SignupPage extends StatefulWidget { + /// Constructor for sign up page + const SignupPage({super.key}); + + @override + State createState() => _SignupPageState(); +} + +class _SignupPageState extends State { + @override + Widget build(BuildContext context) => DerivSignupLayout( + authErrorStateHandler: AuthErrorStateHandler(context: context), + signupPageLabel: AuthData().data.signupPageModel.signupPageLable, + signupPageDescription: + AuthData().data.signupPageModel.signupPageDescription, + onSocialAuthButtonPressed: + AuthData().data.signupPageModel.onSocialAuthButtonPressed, + onSingupError: AuthData().data.signupPageModel.onSingupError, + onSingupEmailSent: (String email) => Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => VerifyEmailPage( + email: email, + ), + ), + ), + onSignupPressed: AuthData().data.signupPageModel.onSignupPressed, + isSocialAuthEnabled: + AuthData().data.signupPageModel.isSocialAuthEnabled, + onLoginTapped: () => Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (BuildContext context) => const LoginPage(), + ), + ), + socialAuthStateHandler: + AuthData().data.signupPageModel.socialAuthStateHandler, + redirectURL: AuthData().data.signupPageModel.redirectURL, + onWebViewError: AuthData().data.signupPageModel.onWebViewError, + ); +} diff --git a/packages/deriv_auth/lib/features/single_entry/features/signup/pages/verification_done_page.dart b/packages/deriv_auth/lib/features/single_entry/features/signup/pages/verification_done_page.dart new file mode 100644 index 000000000..70e34d723 --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/features/signup/pages/verification_done_page.dart @@ -0,0 +1,35 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth/features/single_entry/features/signup/pages/country_selection_page.dart'; +import 'package:flutter/material.dart'; + +/// Verification done page +class VerificationDonePage extends StatelessWidget { + /// + const VerificationDonePage({ + required this.verificationCode, + super.key, + this.affiliateToken, + }); + + /// verification code + final String verificationCode; + + /// + final String? affiliateToken; + + @override + Widget build(BuildContext context) => DerivVerificationDoneLayout( + onContinuePressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => CountrySelectionPage( + verificationCode: verificationCode, + affiliateToken: affiliateToken, + ), + ), + ); + }, + verificationCode: verificationCode, + affiliateToken: affiliateToken, + ); +} diff --git a/packages/deriv_auth/lib/features/single_entry/features/signup/pages/verify_email_page.dart b/packages/deriv_auth/lib/features/single_entry/features/signup/pages/verify_email_page.dart new file mode 100644 index 000000000..d12203b6f --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/features/signup/pages/verify_email_page.dart @@ -0,0 +1,40 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth/features/single_entry/features/signup/pages/signup_page.dart'; +import 'package:flutter/material.dart'; + +/// Verify email page +class VerifyEmailPage extends StatefulWidget { + /// Constructor for verify email page + const VerifyEmailPage({required this.email, super.key}); + + /// email + final String email; + + @override + State createState() => _VerifyEmailPageState(); +} + +class _VerifyEmailPageState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) => DerivVerifyEmailLayout( + email: widget.email, + onEmailNotReceivedPressed: () => onEmailNotReceivedPressed(context), + ); + + void onEmailNotReceivedPressed(BuildContext context) { + Navigator.of(context).pushReplacement(MaterialPageRoute( + builder: (BuildContext context) => DerivEmailNotReceivedLayout( + onReEnterEmailPressed: () => Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (BuildContext context) => const SignupPage(), + ), + ), + ), + )); + } +} diff --git a/packages/deriv_auth/lib/features/single_entry/features/signup/repositories/referral_repository.dart b/packages/deriv_auth/lib/features/single_entry/features/signup/repositories/referral_repository.dart new file mode 100644 index 000000000..f2e6ec693 --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/features/signup/repositories/referral_repository.dart @@ -0,0 +1,8 @@ +import 'package:deriv_auth/core/services/referral/base_referral_code_service.dart'; + +/// Referral repository +class ReferralRepository implements BaseReferralCodeService { + @override + Future getReferralToken(String referralCode) => + Future.value('${referralCode}Token'); +} diff --git a/packages/deriv_auth/lib/features/single_entry/pages/auth_entry_page.dart b/packages/deriv_auth/lib/features/single_entry/pages/auth_entry_page.dart new file mode 100644 index 000000000..f15fcaa86 --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/pages/auth_entry_page.dart @@ -0,0 +1,15 @@ +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_auth/features/single_entry/core/auth_data.dart'; +import 'package:deriv_auth/features/single_entry/features/get_started/pages/get_started_page.dart'; +import 'package:flutter/material.dart'; + +/// Auth entry page for single entry +class AuthEntryPage extends StatelessWidget { + /// Constructor [AuthEntryPage] + AuthEntryPage(AuthEntryModel entryData) { + AuthData().data = entryData; + } + + @override + Widget build(BuildContext context) => const GetStartedPage(); +} diff --git a/packages/deriv_auth/lib/features/single_entry/single_entry.dart b/packages/deriv_auth/lib/features/single_entry/single_entry.dart new file mode 100644 index 000000000..5dc976d85 --- /dev/null +++ b/packages/deriv_auth/lib/features/single_entry/single_entry.dart @@ -0,0 +1,3 @@ +export 'core/models/models.dart'; +export 'pages/auth_entry_page.dart'; +export 'features/reset_pass/pages/choose_new_password_page.dart'; diff --git a/packages/deriv_auth/lib/features/social_auth/cubit/social_auth_cubit.dart b/packages/deriv_auth/lib/features/social_auth/cubit/social_auth_cubit.dart index 454c34abe..ad1889dad 100644 --- a/packages/deriv_auth/lib/features/social_auth/cubit/social_auth_cubit.dart +++ b/packages/deriv_auth/lib/features/social_auth/cubit/social_auth_cubit.dart @@ -1,34 +1,102 @@ import 'dart:developer'; import 'package:bloc/bloc.dart'; -import 'package:deriv_auth/features/social_auth/cubit/social_auth_state.dart'; -import 'package:deriv_auth/features/social_auth/models/social_auth_provider_model.dart'; -import 'package:deriv_auth/features/social_auth/services/base_social_auth_service.dart'; +import 'package:deriv_auth/deriv_auth.dart'; import 'package:deriv_http_client/deriv_http_client.dart'; /// This Cubit is the single source of truth for social authentication. class SocialAuthCubit extends Cubit { /// Initialize a [SocialAuthCubit]. - SocialAuthCubit({required this.socialAuthService}) - : super(SocialAuthInitialState()); + SocialAuthCubit({ + required this.socialAuthService, + BaseSocialWebViewService? socialAuthWebViewService, + }) : _socialAuthWebViewService = + socialAuthWebViewService ?? SocialAuthWebViewService(), + super(SocialAuthInitialState()); /// [BaseSocialAuthService] handles all social authentication logic of cubit. final BaseSocialAuthService socialAuthService; + final BaseSocialWebViewService _socialAuthWebViewService; + + /// List of social auth providers. + List socialAuthProviders = + []; + /// Get list of social auth providers. - Future getSocialAuthProviders() async { + Future?> getSocialAuthProviders() async { emit(SocialAuthLoadingState()); try { - final List socialAuthProviders = - await socialAuthService.getSocialAuthProviders(); + socialAuthProviders = await socialAuthService.getSocialAuthProviders(); emit(SocialAuthLoadedState(socialAuthProviders: socialAuthProviders)); + + return socialAuthProviders; } on HTTPClientException catch (e) { emit(SocialAuthErrorState(message: e.message)); } on Exception catch (e) { log(e.toString()); emit(SocialAuthErrorState()); } + return null; + } + + /// Handles opening social auth web view based on [SocialAuthProviderModel.authUrl]. + /// to receeiving redirect url. + /// + /// [selectedSocialAuthProvider] is the selected social auth provider. + /// [redirectUrl] is the redirect url for social auth. + /// [onWebViewError] is the callback for web view error. + /// [onRedirectUrlReceived] is the callback on redirect url received from social auth provider. + Future selectSocialLoginProvider({ + required SocialAuthProvider selectedSocialAuthProvider, + required String redirectUrl, + required Function(String) onWebViewError, + required Function(SocialAuthDto) onRedirectUrlReceived, + }) async { + try { + emit(SocialAuthLoadingState()); + + final List socialAuthProviderModel = + socialAuthProviders + .where( + (SocialAuthProviderModel socialAuthProvider) => + socialAuthProvider.name == selectedSocialAuthProvider, + ) + .toList(); + + if (socialAuthProviderModel.isNotEmpty) { + final SocialAuthProviderModel socialAuthProvider = + socialAuthProviderModel.first; + + await _socialAuthWebViewService.handleSocialAuth( + socialAuthProviderModel: socialAuthProviderModel.first, + socialAuthUriHandler: ( + {required String code, required String state}) { + final SocialAuthDto socialAuthDto = SocialAuthDto( + nonce: socialAuthProvider.nonce, + state: socialAuthProvider.state, + codeVerifier: socialAuthProvider.codeVerifier, + code: code, + callbackState: state, + provider: socialAuthProvider.name, + ); + onRedirectUrlReceived(socialAuthDto); + }, + redirectURL: redirectUrl, + onError: onWebViewError, + ); + + emit(SocialAuthLoadedState( + socialAuthProviders: socialAuthProviders, + )); + } else { + emit(SocialAuthErrorState()); + } + } on Exception catch (e) { + log(e.toString()); + emit(SocialAuthErrorState()); + } } } diff --git a/packages/deriv_auth/lib/features/social_auth/cubit/social_auth_state.dart b/packages/deriv_auth/lib/features/social_auth/cubit/social_auth_state.dart index ba921ccda..be7f58cea 100644 --- a/packages/deriv_auth/lib/features/social_auth/cubit/social_auth_state.dart +++ b/packages/deriv_auth/lib/features/social_auth/cubit/social_auth_state.dart @@ -1,7 +1,11 @@ import 'package:deriv_auth/features/social_auth/models/social_auth_provider_model.dart'; +import 'package:equatable/equatable.dart'; /// Abstract class to define all social authentication-related state. -abstract class SocialAuthState {} +abstract class SocialAuthState extends Equatable { + @override + List get props => []; +} /// Initial state of [SocialAuthCubit]. class SocialAuthInitialState extends SocialAuthState {} @@ -18,6 +22,9 @@ class SocialAuthErrorState extends SocialAuthState { /// Error message. final String? message; + + @override + List get props => [message]; } /// Loaded state of [SocialAuthCubit]. @@ -29,4 +36,7 @@ class SocialAuthLoadedState extends SocialAuthState { /// List of social auth providers. final List socialAuthProviders; + + @override + List get props => [socialAuthProviders]; } diff --git a/packages/deriv_auth/lib/features/social_auth/presentation/widgets/deriv_social_auth_divider.dart b/packages/deriv_auth/lib/features/social_auth/presentation/widgets/deriv_social_auth_divider.dart new file mode 100644 index 000000000..1f9411517 --- /dev/null +++ b/packages/deriv_auth/lib/features/social_auth/presentation/widgets/deriv_social_auth_divider.dart @@ -0,0 +1,48 @@ +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; + +/// The divider between in-app authentication and social authentication. +class DerivSocialAuthDivider extends StatelessWidget { + /// Initializes the class. + const DerivSocialAuthDivider({ + required this.label, + this.isVisible = true, + Key? key, + }) : super(key: key); + + /// The label that displayed in the divider. + final String label; + + /// Whether the buttons are visible. + /// Defaults to `true`. Acts as a flag to hide the buttons. + final bool isVisible; + + @override + Widget build(BuildContext context) => Visibility( + visible: isVisible, + child: Row( + children: [ + _buildDivider(context), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: ThemeProvider.margin08), + child: Text( + label, + style: context.theme.textStyle( + textStyle: TextStyles.body1, + color: context.theme.colors.general, + ), + ), + ), + _buildDivider(context), + ], + ), + ); + + Widget _buildDivider(BuildContext context) => Expanded( + child: Divider( + thickness: 1, + color: context.theme.colors.hover, + ), + ); +} diff --git a/packages/deriv_auth/lib/features/social_auth/presentation/widgets/deriv_social_auth_panel.dart b/packages/deriv_auth/lib/features/social_auth/presentation/widgets/deriv_social_auth_panel.dart new file mode 100644 index 000000000..9db617b8c --- /dev/null +++ b/packages/deriv_auth/lib/features/social_auth/presentation/widgets/deriv_social_auth_panel.dart @@ -0,0 +1,166 @@ +import 'package:deriv_auth/core/analytics/service/auth_tracking_mixin.dart'; +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +/// Type definition for social auth callback +/// called when user presses social auth button. +typedef SocialAuthCallback = ValueSetter; + +/// Panel of buttons for social authentication. +class DerivSocialAuthPanel extends StatefulWidget { + /// Initializes [DerivSocialAuthPanel]. + const DerivSocialAuthPanel({ + required this.socialAuthStateHandler, + required this.redirectURL, + required this.onWebViewError, + this.isEnabled = true, + this.isVisible = true, + Key? key, + this.onPressed, + }) : super(key: key); + + /// Whether the buttons are enabled. + /// + /// If the buttons are disabled, they will be greyed out. + /// Defaults to `true`. + final bool isEnabled; + + /// Whether the buttons are visible. + /// Defaults to `true`. Acts as a flag to hide the buttons. + final bool isVisible; + + /// Social auth state handler. + final Function(BuildContext, SocialAuthState) socialAuthStateHandler; + + /// onPressed callback for social auth buttons. + /// Gives access to the social auth token and dto. + final SocialAuthCallback? onPressed; + + /// Redirect URL for social auth. + final String redirectURL; + + /// Callback for web view error. + final Function(String) onWebViewError; + + @override + State createState() => _DerivSocialAuthPanelState(); +} + +class _DerivSocialAuthPanelState extends State + with AuthTrackingMixin { + late SocialAuthCubit _socialAuthCubit; + + @override + void didChangeDependencies() { + _socialAuthCubit = BlocProvider.of(context); + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext context) => Visibility( + visible: widget.isVisible, + child: BlocListener( + listener: (BuildContext context, SocialAuthState state) { + widget.socialAuthStateHandler(context, state); + }, + child: SizedBox( + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: ThemeProvider.margin08), + _buildSocialAuthButton(SocialAuthProvider.google, + key: const Key('social_auth_button_google')), + const SizedBox(height: ThemeProvider.margin08), + _buildSocialAuthButton(SocialAuthProvider.facebook, + key: const Key('social_auth_button_facebook')), + const SizedBox(height: ThemeProvider.margin08), + _buildSocialAuthButton(SocialAuthProvider.apple, + key: const Key('social_auth_button_apple')), + ], + ), + ), + ), + ); + + Widget _buildSocialAuthButton( + SocialAuthProvider socialAuthProvider, { + Key? key, + }) => + InkWell( + key: key, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: context.theme.colors.active, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + _getSocialMediaIcon(socialAuthProvider), + package: 'deriv_auth', + ), + const SizedBox(width: 8), + Text( + socialAuthProvider.name.capitalize, + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent, + ), + ), + ], + ), + ), + onTap: widget.isEnabled + ? () async { + switch (socialAuthProvider) { + case SocialAuthProvider.google: + trackLoginWithGoogle(); + break; + case SocialAuthProvider.facebook: + trackLoginWithFacebook(); + break; + case SocialAuthProvider.apple: + trackLoginWithApple(); + break; + default: + break; + } + final List? socialAuthProviders = + await _socialAuthCubit.getSocialAuthProviders(); + + if (socialAuthProviders != null) { + await _socialAuthCubit.selectSocialLoginProvider( + selectedSocialAuthProvider: socialAuthProvider, + redirectUrl: widget.redirectURL, + onWebViewError: widget.onWebViewError, + onRedirectUrlReceived: (SocialAuthDto socialAuthDto) { + widget.onPressed?.call(socialAuthDto); + + BlocProvider.of(context) + .socialAuth(socialAuthDto: socialAuthDto); + }, + ); + } + } + : null, + ); + + String _getSocialMediaIcon(SocialAuthProvider socialAuthProvider) => + 'assets/icons/ic_${socialAuthProvider.name}.svg'; + + @override + void dispose() { + super.dispose(); + } +} diff --git a/packages/deriv_auth/lib/features/social_auth/services/base_social_web_view_service.dart b/packages/deriv_auth/lib/features/social_auth/services/base_social_web_view_service.dart new file mode 100644 index 000000000..407fa133a --- /dev/null +++ b/packages/deriv_auth/lib/features/social_auth/services/base_social_web_view_service.dart @@ -0,0 +1,16 @@ +import 'package:deriv_auth/deriv_auth.dart'; + +/// Type definition for social auth uri handler. +typedef SocialAuthUriHandler = void Function( + {required String code, required String state}); + +/// Interface to define all social authentication web view-related functionality. +abstract class BaseSocialWebViewService { + /// Handle social authentication web view. + Future handleSocialAuth({ + required SocialAuthProviderModel socialAuthProviderModel, + required SocialAuthUriHandler socialAuthUriHandler, + required String redirectURL, + required Function(String) onError, + }); +} diff --git a/packages/deriv_auth/lib/features/social_auth/services/social_web_view_service.dart b/packages/deriv_auth/lib/features/social_auth/services/social_web_view_service.dart new file mode 100644 index 000000000..98cc5c7f5 --- /dev/null +++ b/packages/deriv_auth/lib/features/social_auth/services/social_web_view_service.dart @@ -0,0 +1,54 @@ +import 'dart:async'; +import 'package:app_links/app_links.dart'; +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_web_view/web_view.dart'; +import 'package:flutter_deriv_api/helpers/miscellaneous_helper.dart'; + +/// Service to handle social auth web view. +final class SocialAuthWebViewService implements BaseSocialWebViewService { + StreamSubscription? _uriLinkStream; + + /// Handle in-house social auth. + @override + Future handleSocialAuth({ + required SocialAuthProviderModel socialAuthProviderModel, + required SocialAuthUriHandler socialAuthUriHandler, + required String redirectURL, + required Function(String) onError, + }) async { + await openInAppWebViewWithUriHandler( + url: socialAuthProviderModel.authUrl, + userAgent: await getUserAgent(), + uriHandler: (Uri uri) { + socialAuthUriHandler( + code: uri.queryParameters['code'].toString(), + state: uri.queryParameters['state'].toString(), + ); + }, + onError: onError, + onClosed: () { + _uriLinkStream?.cancel(); + }, + redirectURLs: [redirectURL]); + + _setupLinkStream(socialAuthUriHandler); + } + + void _setupLinkStream(SocialAuthUriHandler uriHandler) { + _uriLinkStream = AppLinks().uriLinkStream.listen( + (Uri? uri) { + _uriLinkStream?.cancel(); + + if (uri != null) { + closeInAppTabActivityWebView(); + + uriHandler( + code: uri.queryParameters['code'].toString(), + state: uri.queryParameters['state'].toString(), + ); + } + }, + onError: (dynamic error) async => closeInAppTabActivityWebView(), + ); + } +} diff --git a/packages/deriv_auth/lib/features/social_auth/social_auth.dart b/packages/deriv_auth/lib/features/social_auth/social_auth.dart index 6c852a54e..d02101ff8 100644 --- a/packages/deriv_auth/lib/features/social_auth/social_auth.dart +++ b/packages/deriv_auth/lib/features/social_auth/social_auth.dart @@ -2,5 +2,9 @@ export 'cubit/social_auth_cubit.dart'; export 'cubit/social_auth_state.dart'; export 'models/social_auth_provider_model.dart'; export 'models/social_auth_dto.dart'; +export 'presentation/widgets/deriv_social_auth_panel.dart'; +export 'presentation/widgets/deriv_social_auth_divider.dart'; export 'services/base_social_auth_service.dart'; export 'services/deriv_social_auth_service.dart'; +export 'services/base_social_web_view_service.dart'; +export 'services/social_web_view_service.dart'; diff --git a/packages/deriv_auth/pubspec.yaml b/packages/deriv_auth/pubspec.yaml index 764862ae5..f115fbd07 100644 --- a/packages/deriv_auth/pubspec.yaml +++ b/packages/deriv_auth/pubspec.yaml @@ -1,27 +1,95 @@ name: deriv_auth description: Provides deriv authentication functionalities for dart/flutter apps. -version: 1.0.2 - -publish_to: "none" +version: 7.0.7 environment: sdk: ">=3.0.0 <4.0.0" + flutter: "3.10.2" + +publish_to: "none" dependencies: + flutter: + sdk: flutter + + analytics: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/analytics + ref: analytics-v4.1.0 + + deriv_theme: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_theme + ref: deriv_theme-v2.8.0 + + deriv_ui: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_ui + ref: deriv_ui-v0.1.1 + deriv_http_client: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_http_client - ref: dev + ref: deriv_http_client-v2.0.2 - bloc: ^8.1.1 - crypto: ^3.0.2 + flutter_deriv_api: + git: + url: git@github.com:deriv-com/flutter-deriv-api.git + ref: v1.3.0 + + deriv_web_view: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_web_view + ref: deriv_web_view-v0.2.2+5 + + deriv_localizations: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_localizations + ref: deriv_localizations-v1.7.2 + + deriv_passkeys: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_passkeys + ref: deriv_passkeys-v0.0.5+11 + + deriv_language_selector: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_language_selector + ref: deriv_language_selector-v0.0.3+14 + + flutter_bloc: ^8.1.3 + flutter_svg: ^2.0.7 + smooth_page_indicator: ^1.1.0 equatable: ^2.0.5 + package_info_plus: ^8.0.2 + crypto: ^3.0.2 http: ^0.13.5 - intl: ^0.18.0 + app_links: ^3.4.5 + +dependency_overrides: + deriv_ui: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_ui + ref: deriv_ui-v0.1.1 dev_dependencies: - lints: ^2.0.1 - mocktail: ^0.3.0 - test: ^1.24.1 - bloc_test: ^9.1.3 + mocktail: ^1.0.3 + bloc_test: ^9.1.6 + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + patrol_finders: ^1.0.0 + +flutter: + assets: + - assets/icons/ + - assets/images/ diff --git a/packages/deriv_auth_ui/test/base_test_app.dart b/packages/deriv_auth/test/base_test_app.dart similarity index 72% rename from packages/deriv_auth_ui/test/base_test_app.dart rename to packages/deriv_auth/test/base_test_app.dart index 7d334260f..edf6d6abd 100644 --- a/packages/deriv_auth_ui/test/base_test_app.dart +++ b/packages/deriv_auth/test/base_test_app.dart @@ -1,4 +1,4 @@ -import 'package:deriv_auth_ui/generated/l10n.dart'; +import 'package:deriv_localizations/l10n/generated/deriv_auth/deriv_auth_localizations.dart'; import 'package:flutter/material.dart'; class BaseTestApp extends StatelessWidget { @@ -12,7 +12,7 @@ class BaseTestApp extends StatelessWidget { @override Widget build(BuildContext context) => MaterialApp( localizationsDelegates: const >[ - DerivAuthUILocalization.delegate + DerivAuthLocalizations.delegate ], home: child, ); diff --git a/packages/deriv_auth/test/core/extensions/string_extensions_test.dart b/packages/deriv_auth/test/core/extensions/string_extensions_test.dart index 2deafde23..d181bec6f 100644 --- a/packages/deriv_auth/test/core/extensions/string_extensions_test.dart +++ b/packages/deriv_auth/test/core/extensions/string_extensions_test.dart @@ -24,4 +24,37 @@ void main() { expect(testString.toSnakeCase(), testString); }); }); + + group('RegexExtension', () { + test('isValidEmail should return true for valid email', () { + const String validEmail = 'test@example.com'; + expect(validEmail.isValidEmail, true); + }); + + test('isValidEmail should return false for invalid email', () { + const String invalidEmail = 'test'; + expect(invalidEmail.isValidEmail, false); + }); + + test('isValidLoginPasswordLength should return true for valid length', () { + const String validPassword = 'password'; + expect(validPassword.isValidLoginPasswordLength, true); + }); + + test('isValidLoginPasswordLength should return false for invalid length', + () { + const String invalidPassword = 'pass'; + expect(invalidPassword.isValidLoginPasswordLength, false); + }); + + test('isValidSignupPassword should return true for valid password', () { + const String validPassword = 'Abc123456'; + expect(validPassword.isValidSignupPassword, true); + }); + + test('isValidSignupPassword should return false for invalid password', () { + const String invalidPassword = 'password'; + expect(invalidPassword.isValidSignupPassword, false); + }); + }); } diff --git a/packages/deriv_auth/test/core/helpers/account_helper_test.dart b/packages/deriv_auth/test/core/helpers/account_helper_test.dart index 52f01e24c..de0f64887 100644 --- a/packages/deriv_auth/test/core/helpers/account_helper_test.dart +++ b/packages/deriv_auth/test/core/helpers/account_helper_test.dart @@ -1,4 +1,3 @@ -import 'package:deriv_auth/core/helpers/helpers.dart'; import 'package:deriv_auth/deriv_auth.dart'; import 'package:test/test.dart'; diff --git a/packages/deriv_auth_ui/test/core/layouts/deriv_unavailable_country_layout_test.dart b/packages/deriv_auth/test/core/layouts/deriv_unavailable_country_layout_test.dart similarity index 58% rename from packages/deriv_auth_ui/test/core/layouts/deriv_unavailable_country_layout_test.dart rename to packages/deriv_auth/test/core/layouts/deriv_unavailable_country_layout_test.dart index ed7d7feda..81b3fc16d 100644 --- a/packages/deriv_auth_ui/test/core/layouts/deriv_unavailable_country_layout_test.dart +++ b/packages/deriv_auth/test/core/layouts/deriv_unavailable_country_layout_test.dart @@ -1,12 +1,11 @@ -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; -import 'package:deriv_ui/presentation/widgets/fullscreen_message.dart'; +import 'package:deriv_auth/core/layouts/deriv_unavailable_country_layout.dart'; +import 'package:deriv_ui/deriv_ui.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:patrol/patrol.dart'; - +import 'package:patrol_finders/patrol_finders.dart'; import '../../base_test_app.dart'; void main() { - patrolTest('DerivUnavailableCountryLayout', (PatrolTester $) async { + patrolWidgetTest('DerivUnavailableCountryLayout', (PatrolTester $) async { await $.pumpWidgetAndSettle(BaseTestApp( child: DerivUnavailableCountryLayout( userAgent: '', diff --git a/packages/deriv_auth/test/core/services/jwt/repository/deriv_jwt_repository_test.dart b/packages/deriv_auth/test/core/services/jwt/repository/deriv_jwt_repository_test.dart index c1e1e2baa..b804e46c7 100644 --- a/packages/deriv_auth/test/core/services/jwt/repository/deriv_jwt_repository_test.dart +++ b/packages/deriv_auth/test/core/services/jwt/repository/deriv_jwt_repository_test.dart @@ -28,7 +28,7 @@ void main() { _repository = DerivJwtRepository( client: _client, connectionInfo: _connectionInfo, - appToken: 'appToken', + getAppToken: ()=> 'appToken', ); }); diff --git a/packages/deriv_auth/test/core/services/token/services/deriv_token_service_test.dart b/packages/deriv_auth/test/core/services/token/services/deriv_token_service_test.dart index 826f72c9c..10879451c 100644 --- a/packages/deriv_auth/test/core/services/token/services/deriv_token_service_test.dart +++ b/packages/deriv_auth/test/core/services/token/services/deriv_token_service_test.dart @@ -26,7 +26,7 @@ void main() { setUpAll(() { _client = MockHttpClient(); - _tokenService = DerivTokenService(); + _tokenService = DerivTokenService(_client); _jwtToken = 'jwtToken'; _connectionInfo = MockConnectionInfo(); @@ -41,7 +41,7 @@ void main() { test('getUserTokens', () async { final GetTokensResponseModel response = await _tokenService.getUserTokens( request: GetTokensRequestModel(), - client: _client, + httpClient: _client, jwtToken: _jwtToken, connectionInfo: _connectionInfo, ); diff --git a/packages/deriv_auth/test/core/states/auth_error_state_mapper_test.dart b/packages/deriv_auth/test/core/states/auth_error_state_mapper_test.dart new file mode 100644 index 000000000..b0daac89b --- /dev/null +++ b/packages/deriv_auth/test/core/states/auth_error_state_mapper_test.dart @@ -0,0 +1,205 @@ +import 'package:flutter/material.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; +import 'package:deriv_auth/deriv_auth.dart'; + +class MockBuildContext extends Mock implements BuildContext {} + +// Create a mock implementation of AuthErrorStateHandler for testing. +final class MockAuthErrorStateHandler extends AuthErrorStateHandler { + MockAuthErrorStateHandler({required super.context}); + + DerivAuthErrorState? lastHandledError; + + @override + void onMissingOtp(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } + + @override + void onSelfClosed(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } + + @override + void onUnsupportedCountry(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } + + @override + void onAccountUnavailable(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } + + @override + void onInvalidCredentials(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } + + @override + void onFailedAuthorization(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } + + @override + void onInvalidResidence(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } + + @override + void onExpiredAccount(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } + + @override + void invalid2faCode(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } + + @override + void onConnectionError(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } + + @override + void onSwitchAccountError(DerivAuthErrorState errorState) { + lastHandledError = errorState; + } +} + +void main() { + // Create an instance of MockAuthErrorStateHandler for testing. + final MockAuthErrorStateHandler mockHandler = MockAuthErrorStateHandler( + context: MockBuildContext(), + ); + + test('authErrorStateMapper handles missing OTP', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.missingOtp, isSocialLogin: false, message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + + expect(mockHandler.lastHandledError, equals(errorState)); + }); + + test('authErrorStateMapper handles self closed', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.selfClosed, isSocialLogin: false, message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + + expect(mockHandler.lastHandledError, equals(errorState)); + }); + + test('authErrorStateMapper handles unsupported country', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.unsupportedCountry, + isSocialLogin: false, + message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + + expect(mockHandler.lastHandledError, equals(errorState)); + }); + + test('authErrorStateMapper handles account unavailable', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.accountUnavailable, + isSocialLogin: false, + message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + + expect(mockHandler.lastHandledError, equals(errorState)); + }); + + test('authErrorStateMapper handles invalid credentials', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.invalidCredential, + isSocialLogin: false, + message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + + expect(mockHandler.lastHandledError, equals(errorState)); + }); + + test('authErrorStateMapper handles failed authorization', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.failedAuthorization, + isSocialLogin: false, + message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + + expect(mockHandler.lastHandledError, equals(errorState)); + }); + + test('authErrorStateMapper handles invalid residence', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.invalidResidence, + isSocialLogin: false, + message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + + expect(mockHandler.lastHandledError, equals(errorState)); + }); + + test('authErrorStateMapper handles expired account', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.expiredAccount, isSocialLogin: false, message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + + expect(mockHandler.lastHandledError, equals(errorState)); + }); + + test('authErrorStateMapper handles invalid 2FA code', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.invalid2faCode, isSocialLogin: false, message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + expect(mockHandler.lastHandledError, equals(errorState)); + }); + + test('authErrorStateMapper handles connection error', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.connectionError, isSocialLogin: false, message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + expect(mockHandler.lastHandledError, equals(errorState)); + }); + + test('authErrorStateMapper handles switch account error', () { + final DerivAuthErrorState errorState = DerivAuthErrorState( + type: AuthErrorType.switchAccountError, + isSocialLogin: false, + message: ''); + authErrorStateMapper( + authErrorState: errorState, + authErrorStateHandler: mockHandler, + ); + expect(mockHandler.lastHandledError, equals(errorState)); + }); +} diff --git a/packages/deriv_auth/test/core/states/auth_state_listener_test.dart b/packages/deriv_auth/test/core/states/auth_state_listener_test.dart new file mode 100644 index 000000000..b86f1aea7 --- /dev/null +++ b/packages/deriv_auth/test/core/states/auth_state_listener_test.dart @@ -0,0 +1,133 @@ +import 'package:deriv_auth/core/models/landig_comany_model.dart'; +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:patrol_finders/patrol_finders.dart'; + +import '../../pump_app.dart'; + +class MockDerivAuthCubit extends Mock implements DerivAuthCubit {} + +class MockBuildContext extends Mock implements BuildContext {} + +void main() { + late DerivAuthCubit authCubit; + + setUpAll(() { + authCubit = MockDerivAuthCubit(); + + when(() => authCubit.close()).thenAnswer((_) async {}); + }); + + group('AuthStateLister', () { + patrolWidgetTest('onLoggedIn is called based on logged in state', + (PatrolTester $) async { + bool isOnLoggedInCalled = false; + + final DerivAuthLoggedInState mockAuthState = + DerivAuthLoggedInState(const DerivAuthModel( + authorizeEntity: AuthorizeEntity(), + landingCompany: LandingCompanyEntity(), + )); + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream).thenAnswer((_) => + Stream.fromIterable([mockAuthState])); + + await $.pumpApp( + BlocProvider( + create: (_) => authCubit, + child: DerivAuthStateListener( + onLoggedIn: (_) { + isOnLoggedInCalled = true; + }, + child: Container()), + ), + ); + + expect(isOnLoggedInCalled, true); + }); + + patrolWidgetTest('onLoggedOut is called based on logged out state', + (PatrolTester $) async { + bool isOnLoggedOutCalled = false; + + final DerivAuthLoggedOutState mockAuthState = DerivAuthLoggedOutState(); + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream).thenAnswer((_) => + Stream.fromIterable([mockAuthState])); + + await $.pumpApp( + BlocProvider( + create: (_) => authCubit, + child: DerivAuthStateListener( + onLoggedOut: () { + isOnLoggedOutCalled = true; + }, + child: Container()), + ), + ); + + expect(isOnLoggedOutCalled, true); + }); + + patrolWidgetTest('onLoading is called based on loading state', + (PatrolTester $) async { + bool isOnLoadingCalled = false; + + final DerivAuthLoadingState mockAuthState = DerivAuthLoadingState(); + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream).thenAnswer((_) => + Stream.fromIterable([mockAuthState])); + + await $.pumpApp( + BlocProvider( + create: (_) => authCubit, + child: DerivAuthStateListener( + onLoading: () { + isOnLoadingCalled = true; + }, + child: Container()), + ), + ); + + expect(isOnLoadingCalled, true); + }); + + patrolWidgetTest('onError is called based on error state', + (PatrolTester $) async { + bool isOnErrorCalled = false; + + final DerivAuthErrorState mockAuthState = DerivAuthErrorState( + type: AuthErrorType.accountUnavailable, + message: 'error', + isSocialLogin: false, + ); + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream).thenAnswer((_) => + Stream.fromIterable([mockAuthState])); + + await $.pumpApp( + BlocProvider( + create: (_) => authCubit, + child: DerivAuthStateListener( + onError: (_) { + isOnErrorCalled = true; + }, + child: Container()), + ), + ); + + expect(isOnErrorCalled, true); + }); + }); +} diff --git a/packages/deriv_auth/test/features/auth/cubit/deriv_auth_cubit_test.dart b/packages/deriv_auth/test/features/auth/cubit/deriv_auth_cubit_test.dart index 0c3254c1b..90518157b 100644 --- a/packages/deriv_auth/test/features/auth/cubit/deriv_auth_cubit_test.dart +++ b/packages/deriv_auth/test/features/auth/cubit/deriv_auth_cubit_test.dart @@ -1,4 +1,7 @@ +import 'dart:async'; +import 'package:analytics/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart'; import 'package:bloc_test/bloc_test.dart'; +import 'package:deriv_auth/core/analytics/data/auth_tracking_repository.dart'; import 'package:deriv_auth/core/exceptions/deriv_auth_exception.dart'; import 'package:deriv_auth/core/models/account_model.dart'; import 'package:deriv_auth/core/models/auth_error/auth_error.dart'; @@ -16,11 +19,28 @@ import '../mocked_data/mocked_auth_models.dart'; class MockAuthService extends Mock implements BaseAuthService {} +class MockDerivRudderStack extends Mock implements DerivRudderstack {} + void main() { + late final MockDerivRudderStack mockDerivRudderstack; + late final DerivAuthCubit authCubit; late final MockAuthService service; setUpAll(() async { + mockDerivRudderstack = MockDerivRudderStack(); + + AuthTrackingRepository.init( + 'test', + derivRudderstack: mockDerivRudderstack, + ); + + when(() => mockDerivRudderstack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'))).thenAnswer( + (_) => Future.value(true), + ); + service = MockAuthService(); authCubit = DerivAuthCubit(authService: service); }); @@ -32,7 +52,10 @@ void main() { expect(authCubit.output, isA>()); }); - test('should emit [AuthLoggedOutState] for the first app start.', () { + test('should emit [AuthLoggedOutState] for the first app start.', + () async { + when(() => service.getLatestAccounts()).thenAnswer( + (_) => Future>.value([])); when(() => service.getDefaultAccount()) .thenAnswer((_) => Future.value()); @@ -42,20 +65,22 @@ void main() { isA(), ]; - expectLater( + unawaited(expectLater( authCubit.stream, emitsInOrder(expectedResponse), - ); + )); - authCubit.authorizeDefaultAccount(); + await authCubit.authorizeDefaultAccount(); + verify(() => service.getLatestAccounts()).called(1); verify(() => service.getDefaultAccount()).called(1); verifyNever( () => service.login(any(), accounts: any(named: 'accounts')), ); }); - test('should emit [AuthLoggedInState] if there is default account.', () { + test('should emit [AuthLoggedInState] if there is default account.', + () async { when(() => service.getDefaultAccount()) .thenAnswer((_) => Future.value(mockedAccountModel)); @@ -76,12 +101,12 @@ void main() { isA(), ]; - expectLater( + unawaited(expectLater( authCubit.stream, emitsInOrder(expectedResponse), - ); + )); - authCubit.authorizeDefaultAccount(); + await authCubit.authorizeDefaultAccount(); verify(() => service.getDefaultAccount()).called(1); }); @@ -90,8 +115,9 @@ void main() { () { registerFallbackValue(GetTokensRequestModel(type: AuthType.system)); - when(() => service.onLoginRequest(any())).thenAnswer( - (_) => Future.value(mockedValidAuthorizeEntity)); + when(() => service.onLoginRequest(request: any(named: 'request'))) + .thenAnswer((_) => + Future.value(mockedValidAuthorizeEntity)); final List> expectedResponse = >[ @@ -106,7 +132,8 @@ void main() { authCubit.systemLogin(email: 'email', password: 'password'); - verify(() => service.onLoginRequest(any())).called(1); + verify(() => service.onLoginRequest(request: any(named: 'request'))) + .called(1); }); test( @@ -114,8 +141,9 @@ void main() { () { registerFallbackValue(GetTokensRequestModel(type: AuthType.social)); - when(() => service.onLoginRequest(any())).thenAnswer( - (_) => Future.value(mockedValidAuthorizeEntity)); + when(() => service.onLoginRequest(request: any(named: 'request'))) + .thenAnswer((_) => + Future.value(mockedValidAuthorizeEntity)); final List> expectedResponse = >[ @@ -130,7 +158,8 @@ void main() { authCubit.socialLogin(oneAllConnectionToken: 'token'); - verify(() => service.onLoginRequest(any())).called(1); + verify(() => service.onLoginRequest(request: any(named: 'request'))) + .called(1); }); test( @@ -138,8 +167,9 @@ void main() { () { registerFallbackValue(GetTokensRequestModel(type: AuthType.social)); - when(() => service.onLoginRequest(any())).thenAnswer( - (_) => Future.value(mockedValidAuthorizeEntity)); + when(() => service.onLoginRequest(request: any(named: 'request'))) + .thenAnswer((_) => + Future.value(mockedValidAuthorizeEntity)); final List> expectedResponse = >[ @@ -156,12 +186,14 @@ void main() { socialAuthDto: mockSocialAuthDto, ); - verify(() => service.onLoginRequest(any())).called(1); + verify(() => service.onLoginRequest(request: any(named: 'request'))) + .called(1); }); test('should emit [AuthLoggedInState] upon a successful otp login.', () { - when(() => service.onLoginRequest(any())).thenAnswer( - (_) => Future.value(mockedValidAuthorizeEntity)); + when(() => service.onLoginRequest(request: any(named: 'request'))) + .thenAnswer((_) => + Future.value(mockedValidAuthorizeEntity)); final List> expectedResponse = >[ @@ -175,14 +207,16 @@ void main() { ); authCubit.systemLogin(email: 'email', password: 'pass', otp: 'otp'); - verify(() => service.onLoginRequest(any())).called(1); + verify(() => service.onLoginRequest(request: any(named: 'request'))) + .called(1); }); test('should emit [AuthErrorState] when an exception occurs in service.', () { registerFallbackValue(GetTokensRequestModel(type: AuthType.system)); - when(() => service.onLoginRequest(any())).thenThrow(DerivAuthException( + when(() => service.onLoginRequest(request: any(named: 'request'))) + .thenThrow(DerivAuthException( message: 'message', type: AuthErrorType.expiredAccount, )); @@ -200,7 +234,8 @@ void main() { authCubit.systemLogin(email: 'email', password: 'pass'); - verify(() => service.onLoginRequest(any())).called(1); + verify(() => service.onLoginRequest(request: any(named: 'request'))) + .called(1); }); test('should emit [AuthLoggedOutState] upon a successful logout.', () { when(() => service.logout()).thenAnswer((_) => Future.value()); @@ -266,8 +301,10 @@ void main() { Future>.value( [mockedAccountModel])); - when(() => service.login(any(), accounts: any(named: 'accounts'))) - .thenThrow(DerivAuthException( + when(() => service.login( + any(), + accounts: any(named: 'accounts'), + )).thenThrow(DerivAuthException( message: 'message', type: AuthErrorType.invalidToken, )); @@ -286,7 +323,10 @@ void main() { authCubit.tokenLogin(_token); verify( - () => service.login(any(), accounts: any(named: 'accounts')), + () => service.login( + any(), + accounts: any(named: 'accounts'), + ), ).called(1); }); diff --git a/packages/deriv_auth/test/features/auth/service/deriv_auth_service_test.dart b/packages/deriv_auth/test/features/auth/service/deriv_auth_service_test.dart index 3e77e295a..dc1a4890c 100644 --- a/packages/deriv_auth/test/features/auth/service/deriv_auth_service_test.dart +++ b/packages/deriv_auth/test/features/auth/service/deriv_auth_service_test.dart @@ -59,7 +59,6 @@ void main() { when(() => tokenService.getUserTokens( request: any(named: 'request'), - client: any(named: 'client'), jwtToken: any(named: 'jwtToken', that: equals(validJwtToken)), connectionInfo: any(named: 'connectionInfo'), )).thenAnswer( @@ -74,7 +73,6 @@ void main() { when(() => tokenService.getUserTokens( request: any(named: 'request'), - client: any(named: 'client'), jwtToken: any(named: 'jwtToken', that: equals(invalidJwtToken)), connectionInfo: any(named: 'connectionInfo'), )).thenThrow( @@ -127,7 +125,7 @@ void main() { ); final AuthorizeEntity response = await authService.onLoginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.system, email: 'email', password: 'pass', @@ -154,7 +152,7 @@ void main() { ); final AuthorizeEntity response = await authService.onLoginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.social, signupProvider: 'signupProvider', oneAllConnectionToken: 'oneAllConnectionToken', @@ -180,7 +178,7 @@ void main() { ); final AuthorizeEntity response = await authService.onLoginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.socialLogin, signupProvider: 'signupProvider', socialAuthDto: mockSocialAuthDto, @@ -207,16 +205,17 @@ void main() { ); final AuthorizeEntity response = await authService.onLoginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.system, email: 'email', password: 'pass', signupProvider: 'signupProvider', - ), () { - when(() => jwtService.getJwtToken()).thenAnswer( - (_) => Future.value(validJwtToken), - ); - }); + ), + onInvalidJwtToken: () { + when(() => jwtService.getJwtToken()).thenAnswer( + (_) => Future.value(validJwtToken), + ); + }); verify(() => jwtService.getJwtToken()).called(2); @@ -238,7 +237,6 @@ void main() { when(() => tokenService.getUserTokens( request: any(named: 'request'), - client: any(named: 'client'), jwtToken: any(named: 'jwtToken', that: equals(validJwtToken)), connectionInfo: any(named: 'connectionInfo'), )).thenThrow( @@ -251,7 +249,7 @@ void main() { expect( authService.onLoginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.system, email: 'email', password: 'pass', @@ -275,7 +273,6 @@ void main() { when(() => tokenService.getUserTokens( request: any(named: 'request'), - client: any(named: 'client'), jwtToken: any(named: 'jwtToken', that: equals(validJwtToken)), connectionInfo: any(named: 'connectionInfo'), )).thenThrow( @@ -288,7 +285,7 @@ void main() { expect( authService.onLoginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.system, email: 'email', password: 'pass', @@ -312,7 +309,6 @@ void main() { when(() => tokenService.getUserTokens( request: any(named: 'request'), - client: any(named: 'client'), jwtToken: any(named: 'jwtToken', that: equals(validJwtToken)), connectionInfo: any(named: 'connectionInfo'), )).thenThrow( @@ -325,7 +321,7 @@ void main() { expect( authService.onLoginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.system, email: 'email', password: 'pass', @@ -421,7 +417,6 @@ void main() { when(() => tokenService.getUserTokens( request: any(named: 'request'), - client: any(named: 'client'), jwtToken: any(named: 'jwtToken', that: equals(validJwtToken)), connectionInfo: any(named: 'connectionInfo'), )).thenThrow( @@ -434,7 +429,7 @@ void main() { expect( authService.onLoginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.system, email: 'email', password: 'pass', @@ -458,7 +453,6 @@ void main() { when(() => tokenService.getUserTokens( request: any(named: 'request'), - client: any(named: 'client'), jwtToken: any(named: 'jwtToken', that: equals(validJwtToken)), connectionInfo: any(named: 'connectionInfo'), )).thenThrow( @@ -471,7 +465,7 @@ void main() { expect( authService.onLoginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.system, email: 'email', password: 'pass', @@ -495,7 +489,6 @@ void main() { when(() => tokenService.getUserTokens( request: any(named: 'request'), - client: any(named: 'client'), jwtToken: any(named: 'jwtToken', that: equals(validJwtToken)), connectionInfo: any(named: 'connectionInfo'), )).thenThrow( @@ -508,7 +501,7 @@ void main() { expect( authService.onLoginRequest( - GetTokensRequestModel( + request: GetTokensRequestModel( type: AuthType.system, email: 'email', password: 'pass', diff --git a/packages/deriv_auth/test/features/get_started/presentation/layouts/deriv_get_started_layout_test.dart b/packages/deriv_auth/test/features/get_started/presentation/layouts/deriv_get_started_layout_test.dart new file mode 100644 index 000000000..3e9271705 --- /dev/null +++ b/packages/deriv_auth/test/features/get_started/presentation/layouts/deriv_get_started_layout_test.dart @@ -0,0 +1,137 @@ +// ignore_for_file: always_specify_types + +import 'package:analytics/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart'; +import 'package:bloc_test/bloc_test.dart'; +import 'package:deriv_auth/core/analytics/data/auth_tracking_repository.dart'; +import 'package:deriv_auth/features/get_started/models/deriv_get_started_slide_model.dart'; +import 'package:deriv_auth/features/get_started/presentation/layouts/deriv_get_started_layout.dart'; +import 'package:deriv_language_selector/deriv_language_selector.dart'; +import 'package:deriv_localizations/l10n/generated/deriv_auth/deriv_auth_localizations.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:patrol_finders/patrol_finders.dart'; + +import '../../../../pump_app.dart'; + +class MockDerivRudderStack extends Mock implements DerivRudderstack {} + +class MockDerivGetStartedSlideModel extends Mock + implements DerivGetStartedSlideModel {} + +class MockLanguageCubit extends MockCubit + implements LanguageCubit {} + +void main() { + group('DerivGetStartedLayout', () { + late final MockDerivRudderStack mockDerivRudderstack; + + late MockDerivGetStartedSlideModel mockSlideModel; + + const String appLogoIconPath = 'assets/icons/ic_logo_extended.svg'; + const String backgroundImagePath = 'assets/images/triangles.svg'; + + final MockLanguageCubit mockLanguageCubit = MockLanguageCubit(); + + setUpAll(() { + mockSlideModel = MockDerivGetStartedSlideModel(); + + mockDerivRudderstack = MockDerivRudderStack(); + + AuthTrackingRepository.init( + 'test', + derivRudderstack: mockDerivRudderstack, + ); + + when(() => mockDerivRudderstack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'))).thenAnswer( + (_) => Future.value(true), + ); + + when(() => mockSlideModel.imagePath) + .thenReturn('assets/images/charts.svg'); + when(() => mockSlideModel.supportingText).thenReturn('Supporting text'); + when(() => mockSlideModel.imagePath) + .thenReturn('assets/images/charts.svg'); + when(() => mockSlideModel.supportingText).thenReturn('Supporting text'); + + final LanguageModel languageModel = LanguageModel( + code: 'en', + flag: 'assets/icons/flags/ic_flag_en.png', + name: 'English', + ); + + when(() => mockLanguageCubit.state).thenReturn( + LanguageLoadedState( + activeLanguages: [ + languageModel, + ], + language: languageModel, + ), + ); + }); + + patrolWidgetTest('should call onLoginTapped when login button is pressed', + (PatrolTester $) async { + bool loginTapped = false; + + await $.pumpApp(BlocProvider( + create: (_) => mockLanguageCubit, + child: MaterialApp( + localizationsDelegates: const [DerivAuthLocalizations.delegate], + locale: const Locale('en'), + home: Scaffold( + body: DerivGetStartedLayout( + onTapNavigation: (context) {}, + slides: [mockSlideModel], + appLogoIconPath: appLogoIconPath, + backgroundImagePath: backgroundImagePath, + onLoginTapped: () { + loginTapped = true; + }, + onSignupTapped: () {}, + ), + ), + ), + )); + + await $(SecondaryButton).tap(); + + expect(loginTapped, isTrue); + }); + + patrolWidgetTest('should call onSignupTapped when signup button is pressed', + (PatrolTester $) async { + bool signupTapped = false; + + await $.pumpApp( + BlocProvider( + create: (_) => mockLanguageCubit, + child: MaterialApp( + localizationsDelegates: const [DerivAuthLocalizations.delegate], + locale: const Locale('en'), + home: Scaffold( + body: DerivGetStartedLayout( + onTapNavigation: (context) {}, + slides: [mockSlideModel], + appLogoIconPath: appLogoIconPath, + backgroundImagePath: backgroundImagePath, + onLoginTapped: () {}, + onSignupTapped: () { + signupTapped = true; + }, + ), + ), + ), + ), + ); + + await $(PrimaryButton).tap(); + + expect(signupTapped, isTrue); + }); + }); +} diff --git a/packages/deriv_auth_ui/test/features/login/layouts/deriv_2fa_layout_test.dart b/packages/deriv_auth/test/features/login/presentation/layouts/deriv_2fa_layout_test.dart similarity index 74% rename from packages/deriv_auth_ui/test/features/login/layouts/deriv_2fa_layout_test.dart rename to packages/deriv_auth/test/features/login/presentation/layouts/deriv_2fa_layout_test.dart index a835bd124..7762161a6 100644 --- a/packages/deriv_auth_ui/test/features/login/layouts/deriv_2fa_layout_test.dart +++ b/packages/deriv_auth/test/features/login/presentation/layouts/deriv_2fa_layout_test.dart @@ -2,16 +2,15 @@ import 'package:deriv_auth/core/models/landig_comany_model.dart'; import 'package:deriv_auth/deriv_auth.dart'; -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; -import 'package:deriv_ui/presentation/widgets/base_text_field.dart'; +import 'package:deriv_ui/deriv_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:patrol/patrol.dart'; +import 'package:patrol_finders/patrol_finders.dart'; -import '../../../mocks.dart'; -import '../../../pump_app.dart'; +import '../../../../mocks.dart'; +import '../../../../pump_app.dart'; void main() { group('Deriv2FALayout', () { @@ -25,7 +24,7 @@ void main() { mockPassword = 'test1234'; }); - patrolTest('renders correctly', (PatrolTester $) async { + patrolWidgetTest('renders correctly', (PatrolTester $) async { when(() => authCubit.state).thenAnswer((_) => DerivAuthLoggedOutState()); when(() => authCubit.stream) @@ -35,7 +34,8 @@ void main() { settle: false, BlocProvider.value( value: authCubit, - child: Deriv2FALayout(email: mockEmail, password: mockPassword), + child: Deriv2FALayout.systemLogin( + email: mockEmail, password: mockPassword), ), ); @@ -44,7 +44,8 @@ void main() { expect($(ElevatedButton).$('Proceed'), findsOneWidget); }); - patrolTest('proceeds to login on correct code', (PatrolTester $) async { + patrolWidgetTest('proceeds to login on correct code', + (PatrolTester $) async { when(() => authCubit.state).thenAnswer((_) => DerivAuthLoggedOutState()); when(() => authCubit.stream) @@ -54,15 +55,15 @@ void main() { email: any(named: 'email'), password: any(named: 'password'), otp: any(named: 'otp'))) - .thenAnswer((_) async => DerivAuthLoggedInState( - authorizeEntity: const AuthorizeEntity(), - landingCompany: const LandingCompanyEntity(), - )); + .thenAnswer((_) async => DerivAuthLoggedInState(const DerivAuthModel( + authorizeEntity: AuthorizeEntity(), + landingCompany: LandingCompanyEntity(), + ))); await $.pumpApp( BlocProvider.value( value: authCubit, - child: Deriv2FALayout( + child: Deriv2FALayout.systemLogin( email: mockEmail, password: mockPassword, ), diff --git a/packages/deriv_auth/test/features/login/presentation/layouts/deriv_login_layout_test.dart b/packages/deriv_auth/test/features/login/presentation/layouts/deriv_login_layout_test.dart new file mode 100644 index 000000000..6a82e70eb --- /dev/null +++ b/packages/deriv_auth/test/features/login/presentation/layouts/deriv_login_layout_test.dart @@ -0,0 +1,485 @@ +import 'package:analytics/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart'; +import 'package:bloc_test/bloc_test.dart'; +import 'package:deriv_auth/core/analytics/data/auth_tracking_repository.dart'; +import 'package:deriv_auth/core/models/landig_comany_model.dart'; +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_passkeys/deriv_passkeys.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:nested/nested.dart'; +import 'package:patrol_finders/patrol_finders.dart'; + +import '../../../../mocks.dart'; +import '../../../../pump_app.dart'; +import '../../../social_auth/mocks/mock_social_provider_model.dart'; + +class MockDerivPasskeysBloc + extends MockBloc + implements DerivPasskeysBloc {} + +class MockDerivRudderStackService extends Mock implements DerivRudderstack {} + +void main() { + group('DerivLoginLayout', () { + late MockDerivRudderStackService mockDerivRudderstack; + late MockAuthCubit authCubit; + late MockSocialAuthCubit socialAuthCubit; + late MockDerivPasskeysBloc derivPasskeysBloc; + + const String welcomeLabel = 'Welcome Back'; + + setUpAll(() { + mockDerivRudderstack = MockDerivRudderStackService(); + authCubit = MockAuthCubit(); + socialAuthCubit = MockSocialAuthCubit(); + derivPasskeysBloc = MockDerivPasskeysBloc(); + + AuthTrackingRepository.init( + 'test', + derivRudderstack: mockDerivRudderstack, + ); + + when(() => mockDerivRudderstack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'), + )).thenAnswer( + (_) => Future.value(true), + ); + + when(() => derivPasskeysBloc.state).thenReturn( + DerivPasskeysInitializedState(), + ); + + registerFallbackValue(SocialAuthProvider.google); + + when(() => socialAuthCubit.stream).thenAnswer( + (_) => Stream.fromIterable([ + SocialAuthLoadedState( + socialAuthProviders: [ + mockSocialAuthProvider + ]) + ])); + + when(() => socialAuthCubit.state).thenAnswer((_) => SocialAuthLoadedState( + socialAuthProviders: [ + mockSocialAuthProvider + ])); + + when(() => socialAuthCubit.getSocialAuthProviders()).thenAnswer( + (_) async => [mockSocialAuthProvider]); + }); + + patrolWidgetTest( + 'renders email and password field including social auth buttons.', + (PatrolTester $) async { + final DerivAuthLoggedOutState mockAuthState = DerivAuthLoggedOutState(); + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream).thenAnswer((_) => + Stream.fromIterable([mockAuthState])); + + await $.pumpApp( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: socialAuthCubit), + BlocProvider.value(value: derivPasskeysBloc), + ], + child: DerivLoginLayout( + socialAuthStateHandler: + (BuildContext context, SocialAuthState state) {}, + redirectURL: 'deriv://example', + onWebViewError: (String error) {}, + welcomeLabel: welcomeLabel, + onResetPassTapped: () {}, + onSignupTapped: () {}, + onLoggedIn: (context, _) {}, + onSocialAuthButtonPressed: (SocialAuthDto p0) {}, + onLoginError: (_) {}, + ), + ), + ); + + expect($(DerivLoginLayout), findsOneWidget); + expect($(BaseTextField).$('Email'), findsOneWidget); + expect($(BaseTextField).$('Password'), findsOneWidget); + expect($(DerivSocialAuthPanel), findsOneWidget); + }); + + patrolWidgetTest('displays invalid email error on invalid email typed.', + (PatrolTester $) async { + final DerivAuthLoggedOutState mockAuthState = DerivAuthLoggedOutState(); + const String invalidEmail = 'invalid-email'; + const String password = '123456'; + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream).thenAnswer((_) => + Stream.fromIterable([mockAuthState])); + + await $.pumpApp( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: socialAuthCubit), + BlocProvider.value(value: derivPasskeysBloc), + ], + child: DerivLoginLayout( + socialAuthStateHandler: + (BuildContext context, SocialAuthState state) {}, + redirectURL: 'deriv://example', + onWebViewError: (String error) {}, + welcomeLabel: welcomeLabel, + onResetPassTapped: () {}, + onSignupTapped: () {}, + onLoggedIn: (context, _) {}, + onSocialAuthButtonPressed: (_) {}, + onLoginError: (_) {}, + ), + ), + ); + + final PatrolFinder emailField = $(BaseTextField).first; + // final emailField = $(BaseTextField).$('Email'); --> this doesn't work + + final PatrolFinder passwordField = $(BaseTextField).last; + + await $.enterText(emailField, invalidEmail); + + await $.pumpAndSettle(); + + await $.enterText(passwordField, password); + + // Allow time for validation to trigger + await $.pumpAndSettle(duration: const Duration(seconds: 1)); + + expect($(Text).$('Enter a valid email address'), findsOneWidget); + }); + + patrolWidgetTest('displays loading error on AuthLoadingState', + (PatrolTester $) async { + final DerivAuthLoadingState mockAuthState = DerivAuthLoadingState(); + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream).thenAnswer((_) => + Stream.fromIterable([mockAuthState])); + + await $.pumpApp( + settle: false, + MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: socialAuthCubit), + BlocProvider.value(value: derivPasskeysBloc), + ], + child: DerivLoginLayout( + socialAuthStateHandler: + (BuildContext context, SocialAuthState state) {}, + redirectURL: 'deriv://example', + onWebViewError: (String error) {}, + welcomeLabel: welcomeLabel, + onResetPassTapped: () {}, + onSignupTapped: () {}, + onLoggedIn: (context, _) {}, + onSocialAuthButtonPressed: (_) {}, + onLoginError: (_) {}, + ), + )); + + expect($(LoadingIndicator), findsOneWidget); + }); + + patrolWidgetTest('calls signupTapped when signup button is pressed.', + (PatrolTester $) async { + final DerivAuthLoggedOutState mockAuthState = DerivAuthLoggedOutState(); + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream).thenAnswer((_) => + Stream.fromIterable([mockAuthState])); + + bool onSignupTappedCalled = false; + + await $.pumpApp(MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: socialAuthCubit), + BlocProvider.value(value: derivPasskeysBloc), + ], + child: DerivLoginLayout( + socialAuthStateHandler: + (BuildContext context, SocialAuthState state) {}, + redirectURL: 'deriv://example', + onWebViewError: (String error) {}, + welcomeLabel: welcomeLabel, + onResetPassTapped: () {}, + onSignupTapped: () { + onSignupTappedCalled = true; + }, + onLoggedIn: (context, _) {}, + onSocialAuthButtonPressed: (_) {}, + onLoginError: (_) {}, + ), + )); + + final PatrolFinder signupButton = $(InkWell).$('Create a new account'); + + await $.scrollUntilVisible(finder: signupButton); + + await signupButton.tap(); + + expect(onSignupTappedCalled, isTrue); + }); + + patrolWidgetTest('calls onLoggedIn on successful login.', + (PatrolTester $) async { + final DerivAuthLoggedInState mockAuthState = DerivAuthLoggedInState( + const DerivAuthModel( + authorizeEntity: AuthorizeEntity(), + landingCompany: LandingCompanyEntity(), + ), + ); + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream).thenAnswer((_) => + Stream.fromIterable([mockAuthState])); + + bool onLoggedInCalled = false; + + await $.pumpApp(MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: socialAuthCubit), + BlocProvider.value(value: derivPasskeysBloc), + ], + child: DerivLoginLayout( + socialAuthStateHandler: + (BuildContext context, SocialAuthState state) {}, + redirectURL: 'deriv://example', + onWebViewError: (String error) {}, + welcomeLabel: welcomeLabel, + onResetPassTapped: () {}, + onSignupTapped: () {}, + onLoggedIn: (context, _) { + onLoggedInCalled = true; + }, + onSocialAuthButtonPressed: (_) {}, + onLoginError: (_) {}, + ), + )); + + expect(onLoggedInCalled, isTrue); + }); + + patrolWidgetTest('calls onLoginError on login error.', + (PatrolTester $) async { + final DerivAuthErrorState mockAuthState = DerivAuthErrorState( + isSocialLogin: false, + message: 'error', + type: AuthErrorType.failedAuthorization); + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream).thenAnswer((_) => + Stream.fromIterable([mockAuthState])); + + bool onLoginErrorCalled = false; + + await $.pumpApp(MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: socialAuthCubit), + BlocProvider.value(value: derivPasskeysBloc), + ], + child: DerivLoginLayout( + socialAuthStateHandler: + (BuildContext context, SocialAuthState state) {}, + redirectURL: 'deriv://example', + onWebViewError: (String error) {}, + welcomeLabel: welcomeLabel, + onResetPassTapped: () {}, + onSignupTapped: () {}, + onLoginError: (_) { + onLoginErrorCalled = true; + }, + onLoggedIn: (context, _) {}, + onSocialAuthButtonPressed: (_) {}, + ), + )); + + expect(onLoginErrorCalled, isTrue); + }); + + patrolWidgetTest('calls [AuthErrorStateHandler] on auth error state.', + (PatrolTester $) async { + final DerivAuthErrorState mockAuthState = DerivAuthErrorState( + isSocialLogin: false, + message: 'Authorization failed.', + type: AuthErrorType.failedAuthorization, + ); + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream).thenAnswer((_) => + Stream.fromIterable([mockAuthState])); + + await $.pumpApp(MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: socialAuthCubit), + BlocProvider.value(value: derivPasskeysBloc), + ], + child: DerivLoginLayout( + socialAuthStateHandler: + (BuildContext context, SocialAuthState state) {}, + redirectURL: 'deriv://example', + onWebViewError: (String error) {}, + welcomeLabel: welcomeLabel, + onResetPassTapped: () {}, + onSignupTapped: () {}, + onLoginError: (_) {}, + onLoggedIn: (context, _) {}, + onSocialAuthButtonPressed: (_) {}, + ), + )); + + expect($(PopupAlertDialog).$('Authorization failed.'), findsOneWidget); + }); + + patrolWidgetTest('calls resetPassTapped when reset button is pressed.', + (PatrolTester $) async { + final DerivAuthLoggedOutState mockAuthState = DerivAuthLoggedOutState(); + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream).thenAnswer((_) => + Stream.fromIterable([mockAuthState])); + + bool onResetPassTappedCalled = false; + + await $.pumpApp(MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: socialAuthCubit), + BlocProvider.value(value: derivPasskeysBloc), + ], + child: DerivLoginLayout( + socialAuthStateHandler: + (BuildContext context, SocialAuthState state) {}, + redirectURL: 'deriv://example', + onWebViewError: (String error) {}, + welcomeLabel: welcomeLabel, + onResetPassTapped: () { + onResetPassTappedCalled = true; + }, + onSignupTapped: () {}, + onLoggedIn: (context, _) {}, + onSocialAuthButtonPressed: (_) {}, + onLoginError: (_) {}, + ), + )); + + await $(InkWell).$('Forgot password?').tap(); + + expect(onResetPassTappedCalled, isTrue); + }); + + patrolWidgetTest( + 'calls onSocialAuthButtonPressed when social auth button is pressed.', + (PatrolTester $) async { + final DerivAuthLoggedOutState mockAuthState = DerivAuthLoggedOutState(); + + bool onSocialAuthButtonPressedCalled = false; + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream).thenAnswer((_) => + Stream.fromIterable([mockAuthState])); + + /// Should be called when social auth button is pressed. + when(() => socialAuthCubit.selectSocialLoginProvider( + selectedSocialAuthProvider: any(named: 'selectedSocialAuthProvider'), + redirectUrl: 'deriv://example', + onWebViewError: any(named: 'onWebViewError'), + onRedirectUrlReceived: + any(named: 'onRedirectUrlReceived'))).thenAnswer((_) async { + onSocialAuthButtonPressedCalled = true; + }); + + await $.pumpApp(MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: socialAuthCubit), + BlocProvider.value(value: derivPasskeysBloc), + ], + child: DerivLoginLayout( + socialAuthStateHandler: + (BuildContext context, SocialAuthState state) {}, + redirectURL: 'deriv://example', + onWebViewError: (String error) {}, + welcomeLabel: welcomeLabel, + onResetPassTapped: () {}, + onSignupTapped: () {}, + onLoggedIn: (context, _) {}, + onSocialAuthButtonPressed: (_) { + onSocialAuthButtonPressedCalled = true; + }, + onLoginError: (_) {}, + ), + )); + + await $(InkWell).$(SocialAuthProvider.google.name.capitalize).tap(); + + expect(onSocialAuthButtonPressedCalled, isTrue); + }); + + patrolWidgetTest('calls socialAuthHandler on [SocialAuthState]', + (PatrolTester $) async { + final DerivAuthLoggedOutState mockAuthState = DerivAuthLoggedOutState(); + + bool onSocialAuthHandlerCalled = false; + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream).thenAnswer((_) => + Stream.fromIterable([mockAuthState])); + + when(() => socialAuthCubit.stream).thenAnswer((_) => + Stream.fromIterable( + [SocialAuthErrorState()])); + + when(() => socialAuthCubit.state) + .thenAnswer((_) => SocialAuthErrorState()); + + await $.pumpApp(MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: socialAuthCubit), + BlocProvider.value(value: derivPasskeysBloc), + ], + child: DerivLoginLayout( + socialAuthStateHandler: + (BuildContext context, SocialAuthState state) { + onSocialAuthHandlerCalled = true; + }, + redirectURL: 'deriv://example', + onWebViewError: (String error) {}, + welcomeLabel: welcomeLabel, + onResetPassTapped: () {}, + onSignupTapped: () {}, + onLoggedIn: (context, _) {}, + onSocialAuthButtonPressed: (_) {}, + onLoginError: (_) {}, + ), + )); + + expect(onSocialAuthHandlerCalled, isTrue); + }); + }); +} diff --git a/packages/deriv_auth/test/features/reset_password/cubit/deiv_reset_password_cubit_test.dart b/packages/deriv_auth/test/features/reset_password/cubit/deiv_reset_password_cubit_test.dart index 8988a15c4..f19b9033c 100644 --- a/packages/deriv_auth/test/features/reset_password/cubit/deiv_reset_password_cubit_test.dart +++ b/packages/deriv_auth/test/features/reset_password/cubit/deiv_reset_password_cubit_test.dart @@ -1,5 +1,7 @@ import 'package:deriv_auth/deriv_auth.dart'; import 'package:deriv_auth/features/reset_password/services/base_reset_password_service.dart'; +import 'package:flutter_deriv_api/api/exceptions/base_api_exception.dart'; +import 'package:flutter_deriv_api/api/models/base_exception_model.dart'; import 'package:mocktail/mocktail.dart'; import 'package:test/expect.dart'; import 'package:test/scaffolding.dart'; @@ -77,6 +79,25 @@ void main() { }); }); + test( + 'Should emit [DerivResetPassErrorState] with isLinkExpired true if chagePassword is unsuccessful.', + () async { + when(() => service.resetPassword( + verificationCode: any(named: 'verificationCode'), + newPassword: any(named: 'newPassword'))) + .thenAnswer((_) async => Future.value(false)); + + await resetPassCubit.changePassword( + token: '123', newPassword: 'newpassword'); + expect( + resetPassCubit.state, + isA().having( + (DerivResetPassErrorState state) => state.isLinkExpired, + 'reset password link expired', + true, + )); + }); + test( 'Should emit [DerivResetPassErrorState] if chagePassword throws an exception.', () async { @@ -88,4 +109,27 @@ void main() { token: '123', newPassword: 'newpassword'); expect(resetPassCubit.state, isA()); }); + + test('''Should emit [DerivResetPassErrorState] with isLinkExpired set to true + if chagePassword throws an [BaseAPIException] with error code InvalidToken.''', + () async { + when(() => + service.resetPassword( + verificationCode: any(named: 'verificationCode'), + newPassword: any(named: 'newPassword'))).thenThrow(BaseAPIException( + baseExceptionModel: BaseExceptionModel( + code: 'InvalidToken', + ))); + + await resetPassCubit.changePassword( + token: '123', newPassword: 'newpassword'); + + expect( + resetPassCubit.state, + isA().having( + (DerivResetPassErrorState state) => state.isLinkExpired, + 'reset password link expired', + true, + )); + }); } diff --git a/packages/deriv_auth_ui/test/features/reset_pass/layouts/deriv_choose_new_pass_layout_test.dart b/packages/deriv_auth/test/features/reset_password/presentation/layouts/deriv_choose_new_pass_layout_test.dart similarity index 86% rename from packages/deriv_auth_ui/test/features/reset_pass/layouts/deriv_choose_new_pass_layout_test.dart rename to packages/deriv_auth/test/features/reset_password/presentation/layouts/deriv_choose_new_pass_layout_test.dart index b351286c8..90cbc2566 100644 --- a/packages/deriv_auth_ui/test/features/reset_pass/layouts/deriv_choose_new_pass_layout_test.dart +++ b/packages/deriv_auth/test/features/reset_password/presentation/layouts/deriv_choose_new_pass_layout_test.dart @@ -1,16 +1,15 @@ // ignore_for_file: always_specify_types import 'package:deriv_auth/deriv_auth.dart'; -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; import 'package:deriv_ui/deriv_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:patrol/patrol.dart'; +import 'package:patrol_finders/patrol_finders.dart'; -import '../../../mocks.dart'; -import '../../../pump_app.dart'; +import '../../../../mocks.dart'; +import '../../../../pump_app.dart'; void main() { group('DerivChooseNewPassLayout', () { @@ -21,7 +20,7 @@ void main() { mockResetPassCubit = MockDerivResetPassCubit(); }); - patrolTest('should render DerivChooseNewPassLayout', + patrolWidgetTest('should render DerivChooseNewPassLayout', (PatrolTester $) async { const resetPassState = DerivResetPassInitialState(); @@ -33,7 +32,7 @@ void main() { BlocProvider.value( value: mockResetPassCubit, child: DerivChooseNewPassLayout( - onResetPassError: (String? error) {}, + onResetPassError: ({required bool isLinkExpired, String? error}) {}, onResetPassSucceed: () {}, token: token, ), @@ -43,7 +42,7 @@ void main() { expect($(DerivChooseNewPassLayout), findsOneWidget); }); - patrolTest( + patrolWidgetTest( 'should call onResetPassError when state is DerivResetPassErrorState', (PatrolTester $) async { const errorState = DerivResetPassErrorState(errorMessage: 'test error'); @@ -58,7 +57,7 @@ void main() { BlocProvider.value( value: mockResetPassCubit, child: DerivChooseNewPassLayout( - onResetPassError: (String? error) { + onResetPassError: ({required bool isLinkExpired, String? error}) { onResetPassErrorCalled = true; }, onResetPassSucceed: () {}, @@ -70,7 +69,7 @@ void main() { expect(onResetPassErrorCalled, isTrue); }); - patrolTest( + patrolWidgetTest( 'should call onResetPassSucceed after 2 seconds when state is DerivResetPassPasswordChangedState', (PatrolTester $) async { const passwordChangedState = DerivResetPassPasswordChangedState(); @@ -87,7 +86,7 @@ void main() { BlocProvider.value( value: mockResetPassCubit, child: DerivChooseNewPassLayout( - onResetPassError: (String? error) {}, + onResetPassError: ({required bool isLinkExpired, String? error}) {}, onResetPassSucceed: () { onResetPassSucceedCalled = true; }, @@ -101,7 +100,7 @@ void main() { expect(onResetPassSucceedCalled, isTrue); }); - patrolTest('should call changePassword when input is valid', + patrolWidgetTest('should call changePassword when input is valid', (PatrolTester $) async { const resetPassState = DerivResetPassInitialState(); const newPassTest = 'newPassWordTest123@'; @@ -117,7 +116,7 @@ void main() { BlocProvider.value( value: mockResetPassCubit, child: DerivChooseNewPassLayout( - onResetPassError: (String? error) {}, + onResetPassError: ({required bool isLinkExpired, String? error}) {}, onResetPassSucceed: () {}, token: token, ), @@ -131,7 +130,7 @@ void main() { token: token, newPassword: newPassTest)).called(1); }); - patrolTest('should not call changePassword when input is invalid', + patrolWidgetTest('should not call changePassword when input is invalid', (PatrolTester $) async { const resetPassState = DerivResetPassInitialState(); const newPassTest = 'pass'; @@ -147,7 +146,7 @@ void main() { BlocProvider.value( value: mockResetPassCubit, child: DerivChooseNewPassLayout( - onResetPassError: (String? error) {}, + onResetPassError: ({required bool isLinkExpired, String? error}) {}, onResetPassSucceed: () {}, token: token, ), diff --git a/packages/deriv_auth_ui/test/features/reset_pass/layouts/deriv_reset_pass_layout_test.dart b/packages/deriv_auth/test/features/reset_password/presentation/layouts/deriv_reset_pass_layout_test.dart similarity index 80% rename from packages/deriv_auth_ui/test/features/reset_pass/layouts/deriv_reset_pass_layout_test.dart rename to packages/deriv_auth/test/features/reset_password/presentation/layouts/deriv_reset_pass_layout_test.dart index 5f1df8405..b2e69bca5 100644 --- a/packages/deriv_auth_ui/test/features/reset_pass/layouts/deriv_reset_pass_layout_test.dart +++ b/packages/deriv_auth/test/features/reset_password/presentation/layouts/deriv_reset_pass_layout_test.dart @@ -1,16 +1,15 @@ // ignore_for_file: always_specify_types import 'package:deriv_auth/deriv_auth.dart'; -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; import 'package:deriv_ui/deriv_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:patrol/patrol.dart'; +import 'package:patrol_finders/patrol_finders.dart'; -import '../../../mocks.dart'; -import '../../../pump_app.dart'; +import '../../../../mocks.dart'; +import '../../../../pump_app.dart'; void main() { group('DerivResetPassLayout', () { @@ -20,7 +19,8 @@ void main() { mockResetPassCubit = MockDerivResetPassCubit(); }); - patrolTest('should render DerivResetPassLayout', (PatrolTester $) async { + patrolWidgetTest('should render DerivResetPassLayout', + (PatrolTester $) async { const resetPassState = DerivResetPassInitialState(); when(() => mockResetPassCubit.state).thenAnswer((_) => resetPassState); @@ -31,7 +31,10 @@ void main() { BlocProvider.value( value: mockResetPassCubit, child: DerivResetPassLayout( - onResetPassError: (String? error) {}, + onResetPassError: ({ + required bool isLinkExpired, + String? error, + }) {}, ), ), ); @@ -39,7 +42,7 @@ void main() { expect($(DerivResetPassLayout), findsOneWidget); }); - patrolTest( + patrolWidgetTest( 'should show emailSentPage when state is DerivResetPassEmailSentState', (PatrolTester $) async { const resetPassState = DerivResetPassEmailSentState(); @@ -52,7 +55,10 @@ void main() { BlocProvider.value( value: mockResetPassCubit, child: DerivResetPassLayout( - onResetPassError: (String? error) {}, + onResetPassError: ({ + required bool isLinkExpired, + String? error, + }) {}, ), ), ); @@ -63,7 +69,7 @@ void main() { findsOneWidget); }); - patrolTest( + patrolWidgetTest( 'should show submitEmailForm when state is DerivResetPassInitialState', (PatrolTester $) async { const resetPassState = DerivResetPassInitialState(); @@ -76,7 +82,10 @@ void main() { BlocProvider.value( value: mockResetPassCubit, child: DerivResetPassLayout( - onResetPassError: (String? error) {}, + onResetPassError: ({ + required bool isLinkExpired, + String? error, + }) {}, ), ), ); @@ -87,7 +96,8 @@ void main() { findsOneWidget); }); - patrolTest('should not call sendVerificationEmail when email is invalid', + patrolWidgetTest( + 'should not call sendVerificationEmail when email is invalid', (PatrolTester $) async { const resetPassState = DerivResetPassInitialState(); const email = 'wrongEmail@format'; @@ -100,7 +110,10 @@ void main() { BlocProvider.value( value: mockResetPassCubit, child: DerivResetPassLayout( - onResetPassError: (String? error) {}, + onResetPassError: ({ + required bool isLinkExpired, + String? error, + }) {}, ), ), ); @@ -115,7 +128,7 @@ void main() { verifyNever(() => mockResetPassCubit.sendVerificationEmail(email)); }); - patrolTest('should call sendVerificationEmail when email is valid', + patrolWidgetTest('should call sendVerificationEmail when email is valid', (PatrolTester $) async { const resetPassState = DerivResetPassInitialState(); const email = 'correctEmail@format.com'; @@ -130,7 +143,10 @@ void main() { BlocProvider.value( value: mockResetPassCubit, child: DerivResetPassLayout( - onResetPassError: (String? error) {}, + onResetPassError: ({ + required bool isLinkExpired, + String? error, + }) {}, ), ), ); @@ -145,7 +161,7 @@ void main() { verify(() => mockResetPassCubit.sendVerificationEmail(email)).called(1); }); - patrolTest( + patrolWidgetTest( 'should call OnResetPassError when state is DerivResetPassErrorState', (PatrolTester $) async { const errorState = DerivResetPassErrorState(errorMessage: 'test error'); @@ -160,7 +176,10 @@ void main() { BlocProvider.value( value: mockResetPassCubit, child: DerivResetPassLayout( - onResetPassError: (String? error) { + onResetPassError: ({ + required bool isLinkExpired, + String? error, + }) { onResetPassErrorCalled = true; }, ), diff --git a/packages/deriv_auth/test/features/reset_password/presentation/layouts/deriv_success_pass_change_layout_test.dart b/packages/deriv_auth/test/features/reset_password/presentation/layouts/deriv_success_pass_change_layout_test.dart new file mode 100644 index 000000000..1c6951ab9 --- /dev/null +++ b/packages/deriv_auth/test/features/reset_password/presentation/layouts/deriv_success_pass_change_layout_test.dart @@ -0,0 +1,17 @@ +import 'package:deriv_auth/features/reset_password/presentation/layouts/deriv_success_pass_change_layout.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:patrol_finders/patrol_finders.dart'; + +import '../../../../pump_app.dart'; + +void main() { + group('DerivSuccessPassChangeLayout', () { + patrolWidgetTest('renders correctly', (PatrolTester $) async { + await $.pumpApp(settle: false, const DerivSuccessPassChangeLayout()); + + expect($(Text).$('Reset password'), findsOneWidget); + expect($(Text).$('Your password has been reset'), findsOneWidget); + }); + }); +} diff --git a/packages/deriv_auth/test/features/signup/cubit/deriv_country_selection_cubit_test.dart b/packages/deriv_auth/test/features/signup/cubit/deriv_country_selection_cubit_test.dart new file mode 100644 index 000000000..a916d5f00 --- /dev/null +++ b/packages/deriv_auth/test/features/signup/cubit/deriv_country_selection_cubit_test.dart @@ -0,0 +1,101 @@ +// ignore_for_file: always_specify_types + +import 'package:bloc_test/bloc_test.dart'; +import 'package:deriv_auth/features/signup/cubit/deriv_country_selection_cubit.dart'; +import 'package:deriv_auth/features/signup/models/deriv_residence_model.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('DerivCountrySelectionCubit', () { + late DerivCountrySelectionCubit countrySelectionCubit; + late List countriesList; + + setUp(() { + countriesList = [ + const DerivResidenceModel( + code: 'us', name: 'United States', isDisabled: false), + const DerivResidenceModel( + code: 'br', name: 'Brazil', isDisabled: false), + const DerivResidenceModel( + code: 'fr', name: 'France', isDisabled: false), + const DerivResidenceModel( + code: 'de', name: 'Germany', isDisabled: false), + ]; + + countrySelectionCubit = + DerivCountrySelectionCubit(Future.value(countriesList)); + }); + + blocTest( + 'Initial states is [DerivCountrySelectionInitialState]', + build: () => countrySelectionCubit, + verify: (DerivCountrySelectionCubit cubit) { + expect( + cubit.state, + isA(), + ); + }); + + blocTest( + 'Verify cubit emits the right states by loading country list', + build: () => countrySelectionCubit, + act: (DerivCountrySelectionCubit cubit) => + countrySelectionCubit.fetchResidenceCountries(), + verify: (DerivCountrySelectionCubit cubit) { + expect( + cubit.state, + isA(), + ); + }); + + blocTest( + 'Verify cubit emits the right states by changing the country selection.', + build: () => countrySelectionCubit, + seed: () => DerivCountrySelectionLoadedState(countriesList), + act: (DerivCountrySelectionCubit cubit) => cubit.changeSelectedCountry( + selectedCountry: countriesList[0], + ), + verify: (DerivCountrySelectionCubit cubit) { + expect( + countrySelectionCubit.state, + isA(), + ); + + expect( + countrySelectionCubit.state.selectedCountry?.code, + countriesList[0].code, + ); + }); + + blocTest( + 'Verify cubit emits the right states when it changing the consent status(Checkbox).', + build: () => countrySelectionCubit, + seed: () => DerivCountrySelectionLoadedState(countriesList), + act: (DerivCountrySelectionCubit cubit) { + cubit + ..changeSelectedCountry( + selectedCountry: const DerivResidenceModel( + code: 'br', name: 'Brazil', isDisabled: false), + ) + ..updateCountryConsentStatus( + agreedToTerms: !cubit.state.agreedToTerms, + ); + }, + verify: (DerivCountrySelectionCubit cubit) { + expect( + cubit.state, + isA(), + ); + + expect( + cubit.state.selectedCountryRequiresConsent, + true, + ); + + expect( + cubit.state.agreedToTerms, + true, + ); + }); + }); +} diff --git a/packages/deriv_auth_ui/test/features/signup/models/deriv_auth_utm_model_test.dart b/packages/deriv_auth/test/features/signup/models/deriv_auth_utm_model_test.dart similarity index 76% rename from packages/deriv_auth_ui/test/features/signup/models/deriv_auth_utm_model_test.dart rename to packages/deriv_auth/test/features/signup/models/deriv_auth_utm_model_test.dart index daa546800..f562eb08a 100644 --- a/packages/deriv_auth_ui/test/features/signup/models/deriv_auth_utm_model_test.dart +++ b/packages/deriv_auth/test/features/signup/models/deriv_auth_utm_model_test.dart @@ -1,12 +1,12 @@ // ignore_for_file: always_specify_types -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; +import 'package:deriv_auth/features/signup/models/deriv_auth_utm_model.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:patrol/patrol.dart'; +import 'package:patrol_finders/patrol_finders.dart'; void main() { group('DerivAuthUtmModel', () { - patrolTest('Constructor initializes all properties correctly', + patrolWidgetTest('Constructor initializes all properties correctly', (PatrolTester $) async { final model = DerivAuthUtmModel( affiliateToken: 'affiliateToken', @@ -31,9 +31,10 @@ void main() { expect(model.affiliateToken, 'affiliateToken'); }); - patrolTest('Constructor initializes optional properties as null', + patrolWidgetTest('Constructor initializes optional properties as null', (PatrolTester $) async { - final model = DerivAuthUtmModel(utmSource: 'source', utmCampaign: 'campaign'); + final model = + DerivAuthUtmModel(utmSource: 'source', utmCampaign: 'campaign'); expect(model.utmCampaignId, isNull); expect(model.utmAdGroupId, isNull); diff --git a/packages/deriv_auth_ui/test/features/signup/models/deriv_password_policy_model_test.dart b/packages/deriv_auth/test/features/signup/models/deriv_password_policy_model_test.dart similarity index 60% rename from packages/deriv_auth_ui/test/features/signup/models/deriv_password_policy_model_test.dart rename to packages/deriv_auth/test/features/signup/models/deriv_password_policy_model_test.dart index 6a23e8b08..cf504388b 100644 --- a/packages/deriv_auth_ui/test/features/signup/models/deriv_password_policy_model_test.dart +++ b/packages/deriv_auth/test/features/signup/models/deriv_password_policy_model_test.dart @@ -1,13 +1,13 @@ - // ignore_for_file: always_specify_types -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; +import 'package:deriv_auth/features/signup/models/deriv_password_policy_model.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:patrol/patrol.dart'; +import 'package:patrol_finders/patrol_finders.dart'; void main() { group('DerivPasswordPolicyModel', () { - patrolTest('isMatchWith() returns true for matching password', (PatrolTester $) async { + patrolWidgetTest('isMatchWith() returns true for matching password', + (PatrolTester $) async { final policy = DerivPasswordPolicyModel( description: 'At least 8 characters', regex: RegExp(r'.{8,}'), @@ -16,7 +16,8 @@ void main() { expect(policy.isMatchWith('password123'), isTrue); }); - patrolTest('isMatchWith() returns false for non-matching password', (PatrolTester $) async { + patrolWidgetTest('isMatchWith() returns false for non-matching password', + (PatrolTester $) async { final policy = DerivPasswordPolicyModel( description: 'At least 8 characters', regex: RegExp(r'.{8,}'), @@ -25,7 +26,9 @@ void main() { expect(policy.isMatchWith('pass'), isFalse); }); - patrolTest('isMatchWith() returns true for optional policy with empty password', (PatrolTester $) async { + patrolWidgetTest( + 'isMatchWith() returns true for optional policy with empty password', + (PatrolTester $) async { final policy = DerivPasswordPolicyModel( description: 'Optional policy', regex: RegExp(r'^$'), diff --git a/packages/deriv_auth_ui/test/features/signup/models/deriv_residence_model_test.dart b/packages/deriv_auth/test/features/signup/models/deriv_residence_model_test.dart similarity index 67% rename from packages/deriv_auth_ui/test/features/signup/models/deriv_residence_model_test.dart rename to packages/deriv_auth/test/features/signup/models/deriv_residence_model_test.dart index 9f1ca143a..bc6eab451 100644 --- a/packages/deriv_auth_ui/test/features/signup/models/deriv_residence_model_test.dart +++ b/packages/deriv_auth/test/features/signup/models/deriv_residence_model_test.dart @@ -1,12 +1,12 @@ // ignore_for_file: always_specify_types -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; +import 'package:deriv_auth/features/signup/models/deriv_residence_model.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:patrol/patrol.dart'; +import 'package:patrol_finders/patrol_finders.dart'; void main() { group('DerivResidenceModel', () { - patrolTest('Constructor initializes all properties correctly', + patrolWidgetTest('Constructor initializes all properties correctly', (PatrolTester $) async { const model = DerivResidenceModel( isDisabled: true, diff --git a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_country_selection_layout_test.dart b/packages/deriv_auth/test/features/signup/presentation/layouts/deriv_country_selection_layout_test.dart similarity index 67% rename from packages/deriv_auth_ui/test/features/signup/layouts/deriv_country_selection_layout_test.dart rename to packages/deriv_auth/test/features/signup/presentation/layouts/deriv_country_selection_layout_test.dart index 6efff970b..cc8423c12 100644 --- a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_country_selection_layout_test.dart +++ b/packages/deriv_auth/test/features/signup/presentation/layouts/deriv_country_selection_layout_test.dart @@ -1,10 +1,11 @@ -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; +import 'package:deriv_auth/features/signup/models/deriv_residence_model.dart'; +import 'package:deriv_auth/features/signup/presentation/layouts/deriv_country_selection_layout.dart'; import 'package:deriv_ui/deriv_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:patrol/patrol.dart'; +import 'package:patrol_finders/patrol_finders.dart'; -import '../../../pump_app.dart'; +import '../../../../pump_app.dart'; void main() { group('DerivCountrySelectionLayout', () { @@ -13,13 +14,14 @@ void main() { setUp(() { residences = Future>.value([ - const DerivResidenceModel(code: 'ID', name: 'Indonesia', isDisabled: false), + const DerivResidenceModel( + code: 'ID', name: 'Indonesia', isDisabled: false), const DerivResidenceModel( code: 'UK', name: 'England', isDisabled: true), ]); }); - patrolTest('renders correctly', (PatrolTester $) async { + patrolWidgetTest('renders correctly', (PatrolTester $) async { await $.pumpApp(DerivCountrySelectionLayout( onNextPressed: () {}, verificationCode: '123456', diff --git a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_email_not_received_layout_test.dart b/packages/deriv_auth/test/features/signup/presentation/layouts/deriv_email_not_received_layout_test.dart similarity index 68% rename from packages/deriv_auth_ui/test/features/signup/layouts/deriv_email_not_received_layout_test.dart rename to packages/deriv_auth/test/features/signup/presentation/layouts/deriv_email_not_received_layout_test.dart index 910b3b3fb..2e4a58cba 100644 --- a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_email_not_received_layout_test.dart +++ b/packages/deriv_auth/test/features/signup/presentation/layouts/deriv_email_not_received_layout_test.dart @@ -1,13 +1,13 @@ -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; +import 'package:deriv_auth/features/signup/presentation/layouts/deriv_email_not_received_layout.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:patrol/patrol.dart'; +import 'package:patrol_finders/patrol_finders.dart'; -import '../../../pump_app.dart'; +import '../../../../pump_app.dart'; void main() { group('DerivEmailNotReceivedLayout', () { - patrolTest('renders correctly', (PatrolTester $) async { + patrolWidgetTest('renders correctly', (PatrolTester $) async { await $.pumpApp(DerivEmailNotReceivedLayout( onReEnterEmailPressed: () {}, )); @@ -17,7 +17,8 @@ void main() { findsOneWidget); }); - patrolTest('onReEnterEmailPressed is called when tapped on the button', + patrolWidgetTest( + 'onReEnterEmailPressed is called when tapped on the button', (PatrolTester $) async { bool isReEnterEmailPressed = false; diff --git a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_set_password_layout_test.dart b/packages/deriv_auth/test/features/signup/presentation/layouts/deriv_set_password_layout_test.dart similarity index 72% rename from packages/deriv_auth_ui/test/features/signup/layouts/deriv_set_password_layout_test.dart rename to packages/deriv_auth/test/features/signup/presentation/layouts/deriv_set_password_layout_test.dart index 4e6ede2a4..5696b9358 100644 --- a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_set_password_layout_test.dart +++ b/packages/deriv_auth/test/features/signup/presentation/layouts/deriv_set_password_layout_test.dart @@ -1,16 +1,15 @@ // ignore_for_file: always_specify_types import 'package:deriv_auth/deriv_auth.dart'; -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; import 'package:deriv_ui/deriv_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:patrol/patrol.dart'; +import 'package:patrol_finders/patrol_finders.dart'; -import '../../../mocks.dart'; -import '../../../pump_app.dart'; +import '../../../../mocks.dart'; +import '../../../../pump_app.dart'; void main() { late MockAuthCubit authCubit; @@ -31,7 +30,7 @@ void main() { }); group('DerivSetPasswordLayout', () { - patrolTest('renders correctly', (PatrolTester $) async { + patrolWidgetTest('renders correctly', (PatrolTester $) async { await $.pumpApp( settle: false, MultiBlocProvider( @@ -40,7 +39,6 @@ void main() { BlocProvider.value(value: signupCubit), ], child: DerivSetPasswordLayout( - onDerivAuthState: (_, __) {}, onDerivSignupState: (_, __) {}, onPreviousPressed: () {}, verificationCode: '123456', @@ -53,32 +51,8 @@ void main() { expect($(ElevatedButton), findsNWidgets(2)); }); - patrolTest('onDerivAuthState is called on DerivAuth state changes', - (PatrolTester $) async { - bool isOnDerivAuthStateCalled = false; - - when(() => authCubit.stream).thenAnswer((_) => Stream.fromIterable([ - DerivAuthLoadingState(), - DerivAuthLoggedOutState(), - ])); - - await $.pumpApp(MultiBlocProvider( - providers: [ - BlocProvider.value(value: authCubit), - BlocProvider.value(value: signupCubit), - ], - child: DerivSetPasswordLayout( - onDerivAuthState: (_, __) => isOnDerivAuthStateCalled = true, - onDerivSignupState: (_, __) {}, - onPreviousPressed: () {}, - verificationCode: 'verificationCode', - residence: 'residence'), - )); - - expect(isOnDerivAuthStateCalled, true); - }); - - patrolTest('onDerivSignupState is called on DerivSignup state changes', + patrolWidgetTest( + 'onDerivSignupState is called on DerivSignup state changes', (PatrolTester $) async { bool isOnDerivSignupStateCalled = false; @@ -95,7 +69,6 @@ void main() { BlocProvider.value(value: signupCubit), ], child: DerivSetPasswordLayout( - onDerivAuthState: (_, __) {}, onDerivSignupState: (_, __) => isOnDerivSignupStateCalled = true, onPreviousPressed: () {}, @@ -106,7 +79,66 @@ void main() { expect(isOnDerivSignupStateCalled, true); }); - patrolTest('onPreviousPressed is called upon tapping previous button', + patrolWidgetTest('calls [AuthErrorStateHandler] on auth error state.', + (PatrolTester $) async { + final mockAuthState = DerivAuthErrorState( + isSocialLogin: false, + message: 'Authorization failed.', + type: AuthErrorType.failedAuthorization, + ); + + when(() => authCubit.state).thenReturn(mockAuthState); + when(() => authCubit.stream) + .thenAnswer((_) => Stream.fromIterable([mockAuthState])); + + await $.pumpApp(MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: signupCubit), + ], + child: DerivSetPasswordLayout( + onDerivSignupState: (_, __) {}, + onPreviousPressed: () {}, + verificationCode: 'verificationCode', + residence: 'residence'), + )); + + expect($(PopupAlertDialog).$('Authorization failed.'), findsOneWidget); + }); + + patrolWidgetTest('onAuthError is called on auth error state.', + (PatrolTester $) async { + final mockAuthState = DerivAuthErrorState( + isSocialLogin: false, + message: 'error', + type: AuthErrorType.failedAuthorization, + ); + + when(() => authCubit.state).thenReturn(mockAuthState); + when(() => authCubit.stream) + .thenAnswer((_) => Stream.fromIterable([mockAuthState])); + + bool isOnAuthErrorCalled = false; + + await $.pumpApp(MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: signupCubit), + ], + child: DerivSetPasswordLayout( + onDerivSignupState: (_, __) {}, + onPreviousPressed: () {}, + onAuthError: (_) { + isOnAuthErrorCalled = true; + }, + verificationCode: 'verificationCode', + residence: 'residence'), + )); + + expect(isOnAuthErrorCalled, true); + }); + + patrolWidgetTest('onPreviousPressed is called upon tapping previous button', (PatrolTester $) async { bool isOnPreviousPressedCalled = false; @@ -118,7 +150,6 @@ void main() { BlocProvider.value(value: signupCubit), ], child: DerivSetPasswordLayout( - onDerivAuthState: (_, __) {}, onDerivSignupState: (_, __) {}, onPreviousPressed: () => isOnPreviousPressedCalled = true, verificationCode: 'verificationCode', @@ -130,7 +161,8 @@ void main() { expect(isOnPreviousPressedCalled, true); }); - patrolTest('password is no longer obscure after visibility icon pressed', + patrolWidgetTest( + 'password is no longer obscure after visibility icon pressed', (PatrolTester $) async { await $.pumpApp( settle: false, @@ -140,7 +172,6 @@ void main() { BlocProvider.value(value: signupCubit), ], child: DerivSetPasswordLayout( - onDerivAuthState: (_, __) {}, onDerivSignupState: (_, __) {}, onPreviousPressed: () {}, verificationCode: 'verificationCode', @@ -158,7 +189,7 @@ void main() { findsOneWidget); }); - patrolTest('start trading button is disabled until password is valid', + patrolWidgetTest('start trading button is disabled until password is valid', (PatrolTester $) async { const validPassword = 'Abcdefg123'; @@ -170,7 +201,6 @@ void main() { BlocProvider.value(value: signupCubit), ], child: DerivSetPasswordLayout( - onDerivAuthState: (_, __) {}, onDerivSignupState: (_, __) {}, onPreviousPressed: () {}, verificationCode: 'verificationCode', diff --git a/packages/deriv_auth/test/features/signup/presentation/layouts/deriv_signup_layout_test.dart b/packages/deriv_auth/test/features/signup/presentation/layouts/deriv_signup_layout_test.dart new file mode 100644 index 000000000..0eddaba9d --- /dev/null +++ b/packages/deriv_auth/test/features/signup/presentation/layouts/deriv_signup_layout_test.dart @@ -0,0 +1,417 @@ +import 'package:analytics/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart'; +import 'package:deriv_auth/core/analytics/data/auth_tracking_repository.dart'; +import 'package:deriv_auth/deriv_auth.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:nested/nested.dart'; +import 'package:patrol_finders/patrol_finders.dart'; + +import '../../../../mocks.dart'; +import '../../../../pump_app.dart'; +import '../../../social_auth/mocks/mock_social_provider_model.dart'; + +class MockDerivRudderStack extends Mock implements DerivRudderstack {} + +void main() { + group('DerivSignupLayout', () { + late final MockDerivRudderStack mockDerivRudderstack; + late MockSignupCubit signupCubit; + late MockAuthCubit authCubit; + late MockSocialAuthCubit socialAuthCubit; + + const String signupPageLabel = 'Create a free account'; + const String signupPageDescription = 'Start trading within minutes.'; + + setUpAll(() { + signupCubit = MockSignupCubit(); + authCubit = MockAuthCubit(); + socialAuthCubit = MockSocialAuthCubit(); + + mockDerivRudderstack = MockDerivRudderStack(); + + AuthTrackingRepository.init( + 'test', + derivRudderstack: mockDerivRudderstack, + ); + + when(() => mockDerivRudderstack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'))).thenAnswer( + (_) => Future.value(true), + ); + + registerFallbackValue(SocialAuthProvider.google); + + when(() => signupCubit.state) + .thenAnswer((_) => const DerivSignupInitialState()); + + when(() => signupCubit.stream).thenAnswer((_) => + Stream.fromIterable( + [const DerivSignupInitialState()])); + + when(() => authCubit.state).thenAnswer((_) => DerivAuthLoadingState()); + + when(() => authCubit.stream).thenAnswer((_) => + Stream.fromIterable( + [DerivAuthLoadingState()])); + + when(() => socialAuthCubit.stream).thenAnswer( + (_) => Stream.fromIterable([ + SocialAuthLoadedState( + socialAuthProviders: [ + mockSocialAuthProvider + ]) + ])); + + when(() => socialAuthCubit.state).thenAnswer((_) => SocialAuthLoadedState( + socialAuthProviders: [ + mockSocialAuthProvider + ])); + + when(() => socialAuthCubit.getSocialAuthProviders()).thenAnswer( + (_) async => [mockSocialAuthProvider]); + }); + + patrolWidgetTest('renders correctly', (PatrolTester $) async { + await $.pumpApp( + settle: false, + MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: signupCubit), + BlocProvider.value(value: socialAuthCubit), + ], + child: DerivSignupLayout( + socialAuthStateHandler: + (BuildContext context, SocialAuthState state) {}, + redirectURL: 'deriv://example', + onWebViewError: (String error) {}, + signupPageLabel: signupPageLabel, + signupPageDescription: signupPageDescription, + onSocialAuthButtonPressed: (_) {}, + onSingupError: (_) {}, + onSingupEmailSent: (_) {}, + onSignupPressed: () {}, + onLoginTapped: () {}, + ), + )); + + expect($(DerivSignupLayout), findsOneWidget); + expect($(BaseTextField).$('Email'), findsOneWidget); + expect($(ElevatedButton).$('Create free demo account'), findsOneWidget); + expect($(DerivSocialAuthPanel), findsOneWidget); + }); + + patrolWidgetTest( + 'onSocialAuthButtonPressed is called upon tapping social auth option', + (PatrolTester $) async { + bool isOnSocialAuthButtonPressedCalled = false; + + /// Should be called when social auth button is pressed. + when(() => socialAuthCubit.selectSocialLoginProvider( + selectedSocialAuthProvider: any(named: 'selectedSocialAuthProvider'), + redirectUrl: 'deriv://example', + onWebViewError: any(named: 'onWebViewError'), + onRedirectUrlReceived: + any(named: 'onRedirectUrlReceived'))).thenAnswer((_) async { + isOnSocialAuthButtonPressedCalled = true; + }); + + await $.pumpApp( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: signupCubit), + BlocProvider.value(value: socialAuthCubit), + ], + child: DerivSignupLayout( + socialAuthStateHandler: + (BuildContext context, SocialAuthState state) {}, + redirectURL: 'deriv://example', + onWebViewError: (String error) {}, + signupPageLabel: signupPageLabel, + signupPageDescription: signupPageDescription, + onSocialAuthButtonPressed: (_) { + isOnSocialAuthButtonPressedCalled = true; + }, + onSingupError: (_) {}, + onSingupEmailSent: (_) {}, + onSignupPressed: () {}, + onLoginTapped: () {}, + ), + ), + ); + + await $.tap($(DerivSocialAuthPanel)); + + expect(isOnSocialAuthButtonPressedCalled, true); + }); + + patrolWidgetTest('onSignupEmailSent is called upon sign up email sent', + (PatrolTester $) async { + bool isOnSignupEmailSentCalled = false; + + when(() => signupCubit.state) + .thenAnswer((_) => const DerivSignupEmailSentState()); + + when(() => signupCubit.stream).thenAnswer((_) => + Stream.fromIterable( + [const DerivSignupEmailSentState()])); + + await $.pumpApp( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: signupCubit), + BlocProvider.value(value: socialAuthCubit), + ], + child: DerivSignupLayout( + socialAuthStateHandler: + (BuildContext context, SocialAuthState state) {}, + redirectURL: 'deriv://example', + onWebViewError: (String error) {}, + signupPageLabel: signupPageLabel, + signupPageDescription: signupPageDescription, + onSocialAuthButtonPressed: (_) {}, + onSingupError: (_) {}, + onSingupEmailSent: (_) => isOnSignupEmailSentCalled = true, + onSignupPressed: () {}, + onLoginTapped: () {}), + ), + ); + + expect(isOnSignupEmailSentCalled, true); + }); + + patrolWidgetTest('onSignupPressed is called upon tapping signup button', + (PatrolTester $) async { + bool isOnSignupPressedCalled = false; + + when(() => signupCubit.sendVerificationEmail('test@gmail.com')) + .thenAnswer((_) async => const DerivSignupEmailSentState()); + + await $.pumpApp( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: signupCubit), + BlocProvider.value(value: socialAuthCubit), + ], + child: DerivSignupLayout( + socialAuthStateHandler: + (BuildContext context, SocialAuthState state) {}, + redirectURL: 'deriv://example', + onWebViewError: (String error) {}, + signupPageLabel: signupPageLabel, + signupPageDescription: signupPageDescription, + onSocialAuthButtonPressed: (_) {}, + onSingupError: (_) {}, + onSingupEmailSent: (_) {}, + onSignupPressed: () => isOnSignupPressedCalled = true, + onLoginTapped: () {}), + ), + ); + + final PatrolFinder signUpButton = + $(ElevatedButton).$('Create free demo account'); + + await $.enterText($(BaseTextField), 'test@gmail.com'); + await $.tap(signUpButton); + + expect(isOnSignupPressedCalled, true); + + verify(() => signupCubit.sendVerificationEmail('test@gmail.com')) + .called(1); + }); + + patrolWidgetTest('onLoginTapped is called upon tapping login button', + (PatrolTester $) async { + bool isOnLoginTappedCalled = false; + + await $.pumpApp( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: signupCubit), + BlocProvider.value(value: socialAuthCubit), + ], + child: DerivSignupLayout( + socialAuthStateHandler: + (BuildContext context, SocialAuthState state) {}, + redirectURL: 'deriv://example', + onWebViewError: (String error) {}, + signupPageLabel: signupPageLabel, + signupPageDescription: signupPageDescription, + onSocialAuthButtonPressed: (_) {}, + onSingupError: (_) {}, + onSingupEmailSent: (_) {}, + onSignupPressed: () {}, + onLoginTapped: () { + isOnLoginTappedCalled = true; + }), + ), + ); + + final PatrolFinder loginButton = $(InkWell).$('Log in'); + + await $.scrollUntilVisible(finder: loginButton); + await $.tap(loginButton); + + expect(isOnLoginTappedCalled, true); + }); + + patrolWidgetTest('onSignupError is called upon signup error state', + (PatrolTester $) async { + bool isOnSignupErrorCalled = false; + + when(() => signupCubit.state).thenAnswer( + (_) => const DerivSignupErrorState('This is a test error message')); + + when(() => signupCubit.stream).thenAnswer((_) => + Stream.fromIterable( + [const DerivSignupErrorState('')])); + + await $.pumpApp( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: signupCubit), + BlocProvider.value(value: socialAuthCubit), + ], + child: DerivSignupLayout( + socialAuthStateHandler: + (BuildContext context, SocialAuthState state) {}, + redirectURL: 'deriv://example', + onWebViewError: (String error) {}, + signupPageLabel: signupPageLabel, + signupPageDescription: signupPageDescription, + onSocialAuthButtonPressed: (_) {}, + onSingupError: (_) => isOnSignupErrorCalled = true, + onSingupEmailSent: (_) {}, + onSignupPressed: () {}, + onLoginTapped: () {}), + ), + ); + + expect(isOnSignupErrorCalled, true); + }); + patrolWidgetTest('onAuthError is called upon auth error state', + (PatrolTester $) async { + bool isOnAuthErrorCalled = false; + + final DerivAuthErrorState mockAuthState = DerivAuthErrorState( + isSocialLogin: false, + message: 'error', + type: AuthErrorType.failedAuthorization, + ); + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream).thenAnswer((_) => + Stream.fromIterable([mockAuthState])); + + await $.pumpApp( + MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: signupCubit), + BlocProvider.value(value: socialAuthCubit), + ], + child: DerivSignupLayout( + socialAuthStateHandler: + (BuildContext context, SocialAuthState state) {}, + redirectURL: 'deriv://example', + onWebViewError: (String error) {}, + signupPageLabel: signupPageLabel, + signupPageDescription: signupPageDescription, + onSocialAuthButtonPressed: (_) {}, + onSingupError: (_) {}, + onSingupEmailSent: (_) {}, + onSignupPressed: () {}, + onAuthError: (_) => isOnAuthErrorCalled = true, + onLoginTapped: () {}), + ), + ); + + expect(isOnAuthErrorCalled, true); + }); + + patrolWidgetTest('calls [AuthErrorStateHandler] on auth error state.', + (PatrolTester $) async { + final DerivAuthErrorState mockAuthState = DerivAuthErrorState( + isSocialLogin: false, + message: 'Authorization failed.', + type: AuthErrorType.failedAuthorization, + ); + + when(() => authCubit.state).thenAnswer((_) => mockAuthState); + + when(() => authCubit.stream).thenAnswer((_) => + Stream.fromIterable([mockAuthState])); + + await $.pumpApp(MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: signupCubit), + BlocProvider.value(value: socialAuthCubit), + ], + child: DerivSignupLayout( + socialAuthStateHandler: + (BuildContext context, SocialAuthState state) {}, + redirectURL: 'deriv://example', + onWebViewError: (String error) {}, + signupPageLabel: signupPageLabel, + signupPageDescription: signupPageDescription, + onSocialAuthButtonPressed: (_) {}, + onSingupError: (_) {}, + onSingupEmailSent: (_) {}, + onSignupPressed: () {}, + onLoginTapped: () {}), + )); + + expect($(PopupAlertDialog).$('Authorization failed.'), findsOneWidget); + }); + + patrolWidgetTest('calls [socialAuthHandler] on [SocialAuthState]', + (PatrolTester $) async { + bool isSocialAuthHandlerCalled = false; + + final SocialAuthLoadingState socialAuthLoadingState = + SocialAuthLoadingState(); + + when(() => socialAuthCubit.state) + .thenAnswer((_) => socialAuthLoadingState); + + when(() => socialAuthCubit.stream).thenAnswer((_) => + Stream.fromIterable( + [socialAuthLoadingState])); + + await $.pumpApp(MultiBlocProvider( + providers: [ + BlocProvider.value(value: authCubit), + BlocProvider.value(value: signupCubit), + BlocProvider.value(value: socialAuthCubit), + ], + child: DerivSignupLayout( + socialAuthStateHandler: + (BuildContext context, SocialAuthState state) { + isSocialAuthHandlerCalled = true; + }, + redirectURL: 'deriv://example', + onWebViewError: (String error) {}, + signupPageLabel: signupPageLabel, + signupPageDescription: signupPageDescription, + onSocialAuthButtonPressed: (_) {}, + onSingupError: (_) {}, + onSingupEmailSent: (_) {}, + onSignupPressed: () {}, + onLoginTapped: () {}), + )); + + expect(isSocialAuthHandlerCalled, true); + }); + }); +} diff --git a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_verification_done_layout_test.dart b/packages/deriv_auth/test/features/signup/presentation/layouts/deriv_verification_done_layout_test.dart similarity index 70% rename from packages/deriv_auth_ui/test/features/signup/layouts/deriv_verification_done_layout_test.dart rename to packages/deriv_auth/test/features/signup/presentation/layouts/deriv_verification_done_layout_test.dart index 36bcb5202..d4c355afb 100644 --- a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_verification_done_layout_test.dart +++ b/packages/deriv_auth/test/features/signup/presentation/layouts/deriv_verification_done_layout_test.dart @@ -1,13 +1,13 @@ -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; +import 'package:deriv_auth/features/signup/presentation/layouts/deriv_verification_done_layout.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:patrol/patrol.dart'; +import 'package:patrol_finders/patrol_finders.dart'; -import '../../../pump_app.dart'; +import '../../../../pump_app.dart'; void main() { group('DerivVerificationDoneLayout', () { - patrolTest('renders correctly', (PatrolTester $) async { + patrolWidgetTest('renders correctly', (PatrolTester $) async { await $.pumpApp(DerivVerificationDoneLayout( verificationCode: '123456', onContinuePressed: () {}, @@ -18,7 +18,7 @@ void main() { expect($(ElevatedButton).$('Continue'), findsOneWidget); }); - patrolTest('onContinuePressed is called', (PatrolTester $) async { + patrolWidgetTest('onContinuePressed is called', (PatrolTester $) async { bool isContinuePressed = false; await $.pumpApp(DerivVerificationDoneLayout( diff --git a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_verify_email_layout_test.dart b/packages/deriv_auth/test/features/signup/presentation/layouts/deriv_verify_email_layout_test.dart similarity index 73% rename from packages/deriv_auth_ui/test/features/signup/layouts/deriv_verify_email_layout_test.dart rename to packages/deriv_auth/test/features/signup/presentation/layouts/deriv_verify_email_layout_test.dart index da35b53cc..fea5eb6b0 100644 --- a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_verify_email_layout_test.dart +++ b/packages/deriv_auth/test/features/signup/presentation/layouts/deriv_verify_email_layout_test.dart @@ -1,9 +1,9 @@ -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; +import 'package:deriv_auth/features/signup/presentation/layouts/deriv_verify_email_layout.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:patrol/patrol.dart'; +import 'package:patrol_finders/patrol_finders.dart'; -import '../../../pump_app.dart'; +import '../../../../pump_app.dart'; void main() { group('DerivVerifyEmailLayout', () { @@ -13,7 +13,7 @@ void main() { testEmail = 'test@gmail.com'; }); - patrolTest('renders correctly', (PatrolTester $) async { + patrolWidgetTest('renders correctly', (PatrolTester $) async { await $.pumpApp(DerivVerifyEmailLayout( email: testEmail, onEmailNotReceivedPressed: () {}, @@ -25,7 +25,8 @@ void main() { expect($(Text).$('Check your email'), findsOneWidget); }); - patrolTest('onEmailNotReceivedPressed is called when tapped on the button', + patrolWidgetTest( + 'onEmailNotReceivedPressed is called when tapped on the button', (PatrolTester $) async { bool isEmailNotReceivedPressed = false; diff --git a/packages/deriv_auth_ui/test/features/signup/widgets/country_selection_list_widget_test.dart b/packages/deriv_auth/test/features/signup/presentation/widgets/country_selection_list_widget_test.dart similarity index 68% rename from packages/deriv_auth_ui/test/features/signup/widgets/country_selection_list_widget_test.dart rename to packages/deriv_auth/test/features/signup/presentation/widgets/country_selection_list_widget_test.dart index 54ac56f6f..4c3675fd4 100644 --- a/packages/deriv_auth_ui/test/features/signup/widgets/country_selection_list_widget_test.dart +++ b/packages/deriv_auth/test/features/signup/presentation/widgets/country_selection_list_widget_test.dart @@ -1,10 +1,10 @@ -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; -import 'package:deriv_auth_ui/src/features/signup/widgets/country_selection_list_widget.dart'; +import 'package:deriv_auth/features/signup/models/deriv_residence_model.dart'; +import 'package:deriv_auth/features/signup/presentation/widgets/country_selection_list_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:patrol/patrol.dart'; +import 'package:patrol_finders/patrol_finders.dart'; -import '../../../pump_app.dart'; +import '../../../../pump_app.dart'; void main() { group('CountrySelectionListWidget', () { @@ -12,13 +12,14 @@ void main() { setUpAll(() { residences = [ - const DerivResidenceModel(code: 'ID', name: 'Indonesia', isDisabled: false), + const DerivResidenceModel( + code: 'ID', name: 'Indonesia', isDisabled: false), const DerivResidenceModel( code: 'UK', name: 'England', isDisabled: true), ]; }); - patrolTest('renders correctly', (PatrolTester $) async { + patrolWidgetTest('renders correctly', (PatrolTester $) async { await $.pumpApp( CountrySelectionListWidget(countries: residences, onChanged: (_) {})); @@ -28,7 +29,7 @@ void main() { expect($(Text).$('England'), findsOneWidget); }); - patrolTest('onChanged is called', (PatrolTester $) async { + patrolWidgetTest('onChanged is called', (PatrolTester $) async { bool isOnChangedCalled = false; await $.pumpApp(CountrySelectionListWidget( @@ -40,7 +41,7 @@ void main() { expect(isOnChangedCalled, true); }); - patrolTest('search field appears on tapping search', + patrolWidgetTest('search field appears on tapping search', (PatrolTester $) async { await $.pumpApp(CountrySelectionListWidget( countries: residences, onChanged: (int country) => null)); diff --git a/packages/deriv_auth/test/features/social_auth/cubit/social_auth_cubit_test.dart b/packages/deriv_auth/test/features/social_auth/cubit/social_auth_cubit_test.dart index f78d69954..39efc4363 100644 --- a/packages/deriv_auth/test/features/social_auth/cubit/social_auth_cubit_test.dart +++ b/packages/deriv_auth/test/features/social_auth/cubit/social_auth_cubit_test.dart @@ -1,68 +1,85 @@ +import 'package:bloc_test/bloc_test.dart'; import 'package:deriv_auth/deriv_auth.dart'; import 'package:deriv_http_client/deriv_http_client.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:test/test.dart'; import '../mocks/mock_social_provider_model.dart'; class MockSocialAuthService extends Mock implements BaseSocialAuthService {} +class MockSocialWebViewService extends Mock + implements BaseSocialWebViewService {} + void main() { late final BaseSocialAuthService _socialAuthService; - late final SocialAuthCubit _socialAuthCubit; + late final BaseSocialWebViewService _socialAuthWebViewService; + SocialAuthCubit _socialAuthCubit; setUpAll(() { _socialAuthService = MockSocialAuthService(); - _socialAuthCubit = SocialAuthCubit(socialAuthService: _socialAuthService); + _socialAuthWebViewService = MockSocialWebViewService(); + + registerFallbackValue(({required String code, required String state}) {}); }); group('SocialAuthCubit', () { - test('emits [SocialAuthLoadedState] with social auth providers.', () { - when(() => _socialAuthService.getSocialAuthProviders()) - .thenAnswer((_) async => mockSocialAuthProviders); - - final List> expectedResponse = - >[ - isA(), - isA().having( - (SocialAuthLoadedState state) => state.socialAuthProviders, - 'list of social auth providers', - mockSocialAuthProviders, - ), - ]; - - expectLater( - _socialAuthCubit.stream, - emitsInOrder(expectedResponse), - ); - - _socialAuthCubit.getSocialAuthProviders(); - - verify(() => _socialAuthService.getSocialAuthProviders()).called(1); - }); - - test('emits [SocialAuthErrorState] when an error occurs.', () { - when(() => _socialAuthService.getSocialAuthProviders()) - .thenThrow(HTTPClientException( - statusCode: 500, - message: 'message', - errorCode: 'TEST_CODE', - )); + blocTest( + 'emits [SocialAuthErrorState] when an error occurs.', + build: () { + _socialAuthCubit = SocialAuthCubit( + socialAuthService: _socialAuthService, + socialAuthWebViewService: _socialAuthWebViewService, + ); + when(() => _socialAuthService.getSocialAuthProviders()) + .thenThrow(HTTPClientException( + statusCode: 500, + message: 'message', + errorCode: 'TEST_CODE', + )); - final List> expectedResponse = - >[ + return _socialAuthCubit; + }, + act: (SocialAuthCubit cubit) => cubit.getSocialAuthProviders(), + expect: () => >[ isA(), isA(), - ]; + ], + ); - expectLater( - _socialAuthCubit.stream, - emitsInOrder(expectedResponse), - ); + blocTest( + 'emits [SocialAuthLoadedState] with social auth providers on initialization.', + build: () { + _socialAuthCubit = SocialAuthCubit( + socialAuthService: _socialAuthService, + socialAuthWebViewService: _socialAuthWebViewService, + ); + when(() => _socialAuthService.getSocialAuthProviders()).thenAnswer( + (_) async => [mockSocialAuthProvider]); - _socialAuthCubit.getSocialAuthProviders(); + when( + () => _socialAuthWebViewService.handleSocialAuth( + socialAuthProviderModel: mockSocialAuthProvider, + socialAuthUriHandler: any(named: 'socialAuthUriHandler'), + redirectURL: 'deriv://example', + onError: any(named: 'onError')), + ).thenAnswer((_) async => Future.value()); + return _socialAuthCubit; + }, + act: (SocialAuthCubit cubit) async { + await cubit.getSocialAuthProviders(); - verify(() => _socialAuthService.getSocialAuthProviders()).called(1); - }); + return cubit.selectSocialLoginProvider( + selectedSocialAuthProvider: mockSocialAuthProvider.name, + redirectUrl: 'deriv://example', + onWebViewError: (String error) {}, + onRedirectUrlReceived: (SocialAuthDto socialAuthDto) {}); + }, + expect: () => >[ + isA(), + isA(), + isA(), + isA(), + ]); }); } diff --git a/packages/deriv_auth_ui/test/mocks.dart b/packages/deriv_auth/test/mocks.dart similarity index 80% rename from packages/deriv_auth_ui/test/mocks.dart rename to packages/deriv_auth/test/mocks.dart index ca42e2da0..1f2eb30b6 100644 --- a/packages/deriv_auth_ui/test/mocks.dart +++ b/packages/deriv_auth/test/mocks.dart @@ -6,3 +6,5 @@ class MockAuthCubit extends Mock implements DerivAuthCubit {} class MockDerivResetPassCubit extends Mock implements DerivResetPassCubit {} class MockSignupCubit extends Mock implements DerivSignupCubit {} + +class MockSocialAuthCubit extends Mock implements SocialAuthCubit {} diff --git a/packages/deriv_auth_ui/test/pump_app.dart b/packages/deriv_auth/test/pump_app.dart similarity index 87% rename from packages/deriv_auth_ui/test/pump_app.dart rename to packages/deriv_auth/test/pump_app.dart index 3f74dd32a..a290d6116 100644 --- a/packages/deriv_auth_ui/test/pump_app.dart +++ b/packages/deriv_auth/test/pump_app.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:patrol/patrol.dart'; +import 'package:patrol_finders/patrol_finders.dart'; import 'base_test_app.dart'; diff --git a/packages/deriv_auth_ui/CHANGELOG.md b/packages/deriv_auth_ui/CHANGELOG.md deleted file mode 100644 index 5a8e15d2f..000000000 --- a/packages/deriv_auth_ui/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -## 0.0.1 - -* Init project diff --git a/packages/deriv_auth_ui/assets/icons/ic_apple.svg b/packages/deriv_auth_ui/assets/icons/ic_apple.svg deleted file mode 100644 index 34181e5d0..000000000 --- a/packages/deriv_auth_ui/assets/icons/ic_apple.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/deriv_auth_ui/assets/icons/ic_facebook.svg b/packages/deriv_auth_ui/assets/icons/ic_facebook.svg deleted file mode 100644 index 65b214b79..000000000 --- a/packages/deriv_auth_ui/assets/icons/ic_facebook.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/packages/deriv_auth_ui/assets/icons/ic_google.svg b/packages/deriv_auth_ui/assets/icons/ic_google.svg deleted file mode 100644 index 4759de29f..000000000 --- a/packages/deriv_auth_ui/assets/icons/ic_google.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/deriv_auth_ui/example/lib/main.dart b/packages/deriv_auth_ui/example/lib/main.dart deleted file mode 100644 index 8fe410f0b..000000000 --- a/packages/deriv_auth_ui/example/lib/main.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:deriv_auth/deriv_auth.dart'; -import 'package:deriv_auth_ui/generated/l10n.dart'; -import 'package:deriv_theme/deriv_theme.dart'; -import 'package:example/features/get_started/pages/get_started_page.dart'; -import 'package:example/features/login/repositories/example_login_repository.dart'; -import 'package:example/features/signup/repositories/example_referral_repository.dart'; -import 'package:example/features/signup/repositories/example_signup_repository.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -import 'features/login/services/example_login_service.dart'; - -void main() { - runApp(const MyApp()); -} - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - @override - Widget build(BuildContext context) { - return MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => DerivAuthCubit( - authService: ExampleLoginService( - authRepository: ExampleLoginRepository(), - ), - ), - ), - BlocProvider( - create: (context) => DerivSignupCubit( - service: DerivSignupService(repository: ExampleSignupRepository()), - referralService: ExampleReferralRepository(), - ), - ), - ], - child: DerivThemeProvider.builder( - initialTheme: ThemeMode.dark, - builder: (context) => MaterialApp( - theme: context.themeData, - localizationsDelegates: const [DerivAuthUILocalization.delegate], - home: const GetStartedPage(), - ), - ), - ); - } -} diff --git a/packages/deriv_auth_ui/lib/deriv_auth_ui.dart b/packages/deriv_auth_ui/lib/deriv_auth_ui.dart deleted file mode 100644 index c03e33998..000000000 --- a/packages/deriv_auth_ui/lib/deriv_auth_ui.dart +++ /dev/null @@ -1,17 +0,0 @@ -export 'src/core/layouts/deriv_unavailable_country_layout.dart'; -export 'src/features/get_started/layouts/deriv_get_started_layout.dart'; -export 'src/features/get_started/models/deriv_get_started_slide_model.dart'; -export 'src/features/login/layouts/deriv_2fa_layout.dart'; -export 'src/features/login/layouts/deriv_login_layout.dart'; -export 'src/features/reset_pass/layouts/deriv_choose_new_pass_layout.dart'; -export 'src/features/reset_pass/layouts/deriv_reset_pass_layout.dart'; -export 'src/features/signup/cubits/deriv_country_selection_cubit.dart'; -export 'src/features/signup/layouts/deriv_country_selection_layout.dart'; -export 'src/features/signup/layouts/deriv_email_not_received_layout.dart'; -export 'src/features/signup/layouts/deriv_set_password_layout.dart'; -export 'src/features/signup/layouts/deriv_signup_layout.dart'; -export 'src/features/signup/layouts/deriv_verification_done_layout.dart'; -export 'src/features/signup/layouts/deriv_verify_email_layout.dart'; -export 'src/features/signup/models/deriv_auth_utm_model.dart'; -export 'src/features/signup/models/deriv_password_policy_model.dart'; -export 'src/features/signup/models/deriv_residence_model.dart'; diff --git a/packages/deriv_auth_ui/lib/generated/intl/messages_all.dart b/packages/deriv_auth_ui/lib/generated/intl/messages_all.dart deleted file mode 100644 index 203415ccf..000000000 --- a/packages/deriv_auth_ui/lib/generated/intl/messages_all.dart +++ /dev/null @@ -1,63 +0,0 @@ -// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart -// This is a library that looks up messages for specific locales by -// delegating to the appropriate library. - -// Ignore issues from commonly used lints in this file. -// ignore_for_file:implementation_imports, file_names, unnecessary_new -// ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering -// ignore_for_file:argument_type_not_assignable, invalid_assignment -// ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases -// ignore_for_file:comment_references - -import 'dart:async'; - -import 'package:flutter/foundation.dart'; -import 'package:intl/intl.dart'; -import 'package:intl/message_lookup_by_library.dart'; -import 'package:intl/src/intl_helpers.dart'; - -import 'messages_en.dart' as messages_en; - -typedef Future LibraryLoader(); -Map _deferredLibraries = { - 'en': () => new SynchronousFuture(null), -}; - -MessageLookupByLibrary? _findExact(String localeName) { - switch (localeName) { - case 'en': - return messages_en.messages; - default: - return null; - } -} - -/// User programs should call this before using [localeName] for messages. -Future initializeMessages(String localeName) { - var availableLocale = Intl.verifiedLocale( - localeName, (locale) => _deferredLibraries[locale] != null, - onFailure: (_) => null); - if (availableLocale == null) { - return new SynchronousFuture(false); - } - var lib = _deferredLibraries[availableLocale]; - lib == null ? new SynchronousFuture(false) : lib(); - initializeInternalMessageLookup(() => new CompositeMessageLookup()); - messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); - return new SynchronousFuture(true); -} - -bool _messagesExistFor(String locale) { - try { - return _findExact(locale) != null; - } catch (e) { - return false; - } -} - -MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) { - var actualLocale = - Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null); - if (actualLocale == null) return null; - return _findExact(actualLocale); -} diff --git a/packages/deriv_auth_ui/lib/generated/intl/messages_en.dart b/packages/deriv_auth_ui/lib/generated/intl/messages_en.dart deleted file mode 100644 index 927d0cfc3..000000000 --- a/packages/deriv_auth_ui/lib/generated/intl/messages_en.dart +++ /dev/null @@ -1,150 +0,0 @@ -// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart -// This is a library that provides messages for a en locale. All the -// messages from the main program should be duplicated here with the same -// function name. - -// Ignore issues from commonly used lints in this file. -// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new -// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering -// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases -// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes -// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes - -import 'package:intl/intl.dart'; -import 'package:intl/message_lookup_by_library.dart'; - -final messages = new MessageLookup(); - -typedef String MessageIfAbsent(String messageStr, List args); - -class MessageLookup extends MessageLookupByLibrary { - String get localeName => 'en'; - - static String m0(email) => - "We’ve sent a message to ${email} with a link to reset your password."; - - static String m1(email) => - "We\'ve sent a message to ${email} with a link to activate your account."; - - static String m2(app) => "${app} isn\'t available in your country"; - - final messages = _notInlinedMessages(_notInlinedMessages); - static Map _notInlinedMessages(_) => { - "actionContinue": MessageLookupByLibrary.simpleMessage("Continue"), - "actionCreateANewAccount": - MessageLookupByLibrary.simpleMessage("Create a new account"), - "actionCreateAccount": - MessageLookupByLibrary.simpleMessage("Create free demo account"), - "actionEmailNotReceived": - MessageLookupByLibrary.simpleMessage("Didn\'t receive your email?"), - "actionForgotPassword": - MessageLookupByLibrary.simpleMessage("Forgot password?"), - "actionGetAFreeAccount": - MessageLookupByLibrary.simpleMessage("Get a free account"), - "actionLogin": MessageLookupByLibrary.simpleMessage("Log in"), - "actionNext": MessageLookupByLibrary.simpleMessage("Next"), - "actionPrevious": MessageLookupByLibrary.simpleMessage("Previous"), - "actionProceed": MessageLookupByLibrary.simpleMessage("Proceed"), - "actionReenterEmail": MessageLookupByLibrary.simpleMessage( - "Re-enter your email and try again"), - "actionResetPass": - MessageLookupByLibrary.simpleMessage("Reset my password"), - "actionStartTrading": - MessageLookupByLibrary.simpleMessage("Start trading"), - "infoReferralInfoDescription": MessageLookupByLibrary.simpleMessage( - "An alphanumeric code provided by a Deriv affiliate, applicable for email sign-ups only."), - "informEnterTwoFactorAuthCode": MessageLookupByLibrary.simpleMessage( - "Enter the 6-digit code from the authenticator app on your phone."), - "informInvalidEmailFormat": - MessageLookupByLibrary.simpleMessage("Enter a valid email address"), - "informInvalidPasswordFormat": MessageLookupByLibrary.simpleMessage( - "Please enter a valid password format"), - "informInvalidReferralCode": MessageLookupByLibrary.simpleMessage( - "The referral code you entered is invalid. Check and try again."), - "informLetsContinue": - MessageLookupByLibrary.simpleMessage("Let\'s continue."), - "informLoginOptions": - MessageLookupByLibrary.simpleMessage("Or log in with"), - "informPasswordPolicy": - MessageLookupByLibrary.simpleMessage("Your password must have:"), - "informPasswordPolicyLength": - MessageLookupByLibrary.simpleMessage("8-25 characters"), - "informPasswordPolicyLowerAndUpper": - MessageLookupByLibrary.simpleMessage( - "Upper and lower case letters"), - "informPasswordPolicyNumber": - MessageLookupByLibrary.simpleMessage("At least one number"), - "informRedirectLogin": MessageLookupByLibrary.simpleMessage( - "You’ll need to log in with your new password. Hang on, we’re redirecting you."), - "informResetPassByEmail": MessageLookupByLibrary.simpleMessage( - "We\'ll email you instructions to reset your password."), - "informSendResetPasswordEmail": m0, - "informVerificationEmailSent": m1, - "informYourPassHasBeenReset": MessageLookupByLibrary.simpleMessage( - "Your password has been reset"), - "labelCheckEmail": - MessageLookupByLibrary.simpleMessage("Check your email"), - "labelChooseCountry": - MessageLookupByLibrary.simpleMessage("Choose country"), - "labelChooseNewPass": - MessageLookupByLibrary.simpleMessage("Choose a new password"), - "labelCreatePass": - MessageLookupByLibrary.simpleMessage("Create a password"), - "labelCreatePassword": - MessageLookupByLibrary.simpleMessage("Create a password"), - "labelDontHaveAnAccountYet": - MessageLookupByLibrary.simpleMessage("Don’t have an account yet?"), - "labelEmail": MessageLookupByLibrary.simpleMessage("Email"), - "labelEmailIssueFirewall": MessageLookupByLibrary.simpleMessage( - "We can\'t deliver the email to this address (Usually because of firewalls or filtering)."), - "labelEmailIssueHeader": MessageLookupByLibrary.simpleMessage( - "If you don\'t see an email from us within a few minutes, a few things could have happened:"), - "labelEmailIssueSpam": MessageLookupByLibrary.simpleMessage( - "The email is in your spam folder (Sometimes things get lost there)."), - "labelEmailIssueTypo": MessageLookupByLibrary.simpleMessage( - "The email address you entered had a mistake or typo (happens to the best of us)."), - "labelEmailIssueWrongEmail": MessageLookupByLibrary.simpleMessage( - "You accidentally gave us another email address (Usually a work or a personal one instead of the one you meant)."), - "labelGotReferralCode": - MessageLookupByLibrary.simpleMessage("Got a referral code?"), - "labelHaveAccount": - MessageLookupByLibrary.simpleMessage("Already have an account?"), - "labelKeepPassword": MessageLookupByLibrary.simpleMessage( - "Keep your account secure with a password"), - "labelLiveChat": MessageLookupByLibrary.simpleMessage("Live chat"), - "labelLogIn": MessageLookupByLibrary.simpleMessage("Log in"), - "labelNotAvailable": - MessageLookupByLibrary.simpleMessage("Not available"), - "labelOrSignUpWith": - MessageLookupByLibrary.simpleMessage("Or sign up with"), - "labelPassword": MessageLookupByLibrary.simpleMessage("Password"), - "labelReferralCode": - MessageLookupByLibrary.simpleMessage("Referral Code"), - "labelReferralInfoTitle": - MessageLookupByLibrary.simpleMessage("Affiliate referral code"), - "labelResetPassword": - MessageLookupByLibrary.simpleMessage("Reset Password"), - "labelSearchCountry": - MessageLookupByLibrary.simpleMessage("Search country"), - "labelSelectCountry": - MessageLookupByLibrary.simpleMessage("Where do you live?"), - "labelSignUp": MessageLookupByLibrary.simpleMessage("Sign up"), - "labelThanksEmail": MessageLookupByLibrary.simpleMessage( - "Thanks for verifying your email"), - "labelTwoFactorAuth": - MessageLookupByLibrary.simpleMessage("Two-factor authentication"), - "labelTwoFactorAuthenticationCode": - MessageLookupByLibrary.simpleMessage("2FA code"), - "labelVerifyYourEmail": - MessageLookupByLibrary.simpleMessage("Verify your email"), - "warnCountryNotAvailable": MessageLookupByLibrary.simpleMessage( - "Unfortunately, Deriv is not available in your country."), - "warnNotAvailableCountries": MessageLookupByLibrary.simpleMessage( - "If you have any questions, contact us via "), - "warnNotAvailableCountriesTitle": m2, - "warnPasswordContainsSymbol": MessageLookupByLibrary.simpleMessage( - "Use symbols for strong password."), - "warnPasswordLength": MessageLookupByLibrary.simpleMessage( - "You should enter 8-25 characters.") - }; -} diff --git a/packages/deriv_auth_ui/lib/generated/l10n.dart b/packages/deriv_auth_ui/lib/generated/l10n.dart deleted file mode 100644 index 7d4ef2308..000000000 --- a/packages/deriv_auth_ui/lib/generated/l10n.dart +++ /dev/null @@ -1,721 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'intl/messages_all.dart'; - -// ************************************************************************** -// Generator: Flutter Intl IDE plugin -// Made by Localizely -// ************************************************************************** - -// ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars -// ignore_for_file: join_return_with_assignment, prefer_final_in_for_each -// ignore_for_file: avoid_redundant_argument_values, avoid_escaping_inner_quotes - -class DerivAuthUILocalization { - DerivAuthUILocalization(); - - static DerivAuthUILocalization? _current; - - static DerivAuthUILocalization get current { - assert(_current != null, - 'No instance of DerivAuthUILocalization was loaded. Try to initialize the DerivAuthUILocalization delegate before accessing DerivAuthUILocalization.current.'); - return _current!; - } - - static const AppLocalizationDelegate delegate = AppLocalizationDelegate(); - - static Future load(Locale locale) { - final name = (locale.countryCode?.isEmpty ?? false) - ? locale.languageCode - : locale.toString(); - final localeName = Intl.canonicalizedLocale(name); - return initializeMessages(localeName).then((_) { - Intl.defaultLocale = localeName; - final instance = DerivAuthUILocalization(); - DerivAuthUILocalization._current = instance; - - return instance; - }); - } - - static DerivAuthUILocalization of(BuildContext context) { - final instance = DerivAuthUILocalization.maybeOf(context); - assert(instance != null, - 'No instance of DerivAuthUILocalization present in the widget tree. Did you add DerivAuthUILocalization.delegate in localizationsDelegates?'); - return instance!; - } - - static DerivAuthUILocalization? maybeOf(BuildContext context) { - return Localizations.of( - context, DerivAuthUILocalization); - } - - /// `Not available` - String get labelNotAvailable { - return Intl.message( - 'Not available', - name: 'labelNotAvailable', - desc: '', - args: [], - ); - } - - /// `{app} isn't available in your country` - String warnNotAvailableCountriesTitle(String app) { - return Intl.message( - '$app isn\'t available in your country', - name: 'warnNotAvailableCountriesTitle', - desc: 'A message with a single parameter', - args: [app], - ); - } - - /// `If you have any questions, contact us via ` - String get warnNotAvailableCountries { - return Intl.message( - 'If you have any questions, contact us via ', - name: 'warnNotAvailableCountries', - desc: '', - args: [], - ); - } - - /// `Live chat` - String get labelLiveChat { - return Intl.message( - 'Live chat', - name: 'labelLiveChat', - desc: '', - args: [], - ); - } - - /// `Get a free account` - String get actionGetAFreeAccount { - return Intl.message( - 'Get a free account', - name: 'actionGetAFreeAccount', - desc: '', - args: [], - ); - } - - /// `Log in` - String get actionLogin { - return Intl.message( - 'Log in', - name: 'actionLogin', - desc: '', - args: [], - ); - } - - /// `Two-factor authentication` - String get labelTwoFactorAuth { - return Intl.message( - 'Two-factor authentication', - name: 'labelTwoFactorAuth', - desc: '', - args: [], - ); - } - - /// `Enter the 6-digit code from the authenticator app on your phone.` - String get informEnterTwoFactorAuthCode { - return Intl.message( - 'Enter the 6-digit code from the authenticator app on your phone.', - name: 'informEnterTwoFactorAuthCode', - desc: '', - args: [], - ); - } - - /// `2FA code` - String get labelTwoFactorAuthenticationCode { - return Intl.message( - '2FA code', - name: 'labelTwoFactorAuthenticationCode', - desc: '', - args: [], - ); - } - - /// `Proceed` - String get actionProceed { - return Intl.message( - 'Proceed', - name: 'actionProceed', - desc: '', - args: [], - ); - } - - /// `Log in` - String get labelLogIn { - return Intl.message( - 'Log in', - name: 'labelLogIn', - desc: '', - args: [], - ); - } - - /// `Or log in with` - String get informLoginOptions { - return Intl.message( - 'Or log in with', - name: 'informLoginOptions', - desc: '', - args: [], - ); - } - - /// `Email` - String get labelEmail { - return Intl.message( - 'Email', - name: 'labelEmail', - desc: '', - args: [], - ); - } - - /// `Password` - String get labelPassword { - return Intl.message( - 'Password', - name: 'labelPassword', - desc: '', - args: [], - ); - } - - /// `Forgot password?` - String get actionForgotPassword { - return Intl.message( - 'Forgot password?', - name: 'actionForgotPassword', - desc: '', - args: [], - ); - } - - /// `Don’t have an account yet?` - String get labelDontHaveAnAccountYet { - return Intl.message( - 'Don’t have an account yet?', - name: 'labelDontHaveAnAccountYet', - desc: '', - args: [], - ); - } - - /// `Create a new account` - String get actionCreateANewAccount { - return Intl.message( - 'Create a new account', - name: 'actionCreateANewAccount', - desc: '', - args: [], - ); - } - - /// `Enter a valid email address` - String get informInvalidEmailFormat { - return Intl.message( - 'Enter a valid email address', - name: 'informInvalidEmailFormat', - desc: '', - args: [], - ); - } - - /// `You should enter 8-25 characters.` - String get warnPasswordLength { - return Intl.message( - 'You should enter 8-25 characters.', - name: 'warnPasswordLength', - desc: '', - args: [], - ); - } - - /// `Reset Password` - String get labelResetPassword { - return Intl.message( - 'Reset Password', - name: 'labelResetPassword', - desc: '', - args: [], - ); - } - - /// `Choose a new password` - String get labelChooseNewPass { - return Intl.message( - 'Choose a new password', - name: 'labelChooseNewPass', - desc: '', - args: [], - ); - } - - /// `Create a password` - String get labelCreatePass { - return Intl.message( - 'Create a password', - name: 'labelCreatePass', - desc: '', - args: [], - ); - } - - /// `Your password has been reset` - String get informYourPassHasBeenReset { - return Intl.message( - 'Your password has been reset', - name: 'informYourPassHasBeenReset', - desc: '', - args: [], - ); - } - - /// `You’ll need to log in with your new password. Hang on, we’re redirecting you.` - String get informRedirectLogin { - return Intl.message( - 'You’ll need to log in with your new password. Hang on, we’re redirecting you.', - name: 'informRedirectLogin', - desc: '', - args: [], - ); - } - - /// `Reset my password` - String get actionResetPass { - return Intl.message( - 'Reset my password', - name: 'actionResetPass', - desc: '', - args: [], - ); - } - - /// `Please enter a valid password format` - String get informInvalidPasswordFormat { - return Intl.message( - 'Please enter a valid password format', - name: 'informInvalidPasswordFormat', - desc: '', - args: [], - ); - } - - /// `Check your email` - String get labelCheckEmail { - return Intl.message( - 'Check your email', - name: 'labelCheckEmail', - desc: '', - args: [], - ); - } - - /// `We’ve sent a message to {email} with a link to reset your password.` - String informSendResetPasswordEmail(String email) { - return Intl.message( - 'We’ve sent a message to $email with a link to reset your password.', - name: 'informSendResetPasswordEmail', - desc: 'A message with a single parameter', - args: [email], - ); - } - - /// `We'll email you instructions to reset your password.` - String get informResetPassByEmail { - return Intl.message( - 'We\'ll email you instructions to reset your password.', - name: 'informResetPassByEmail', - desc: '', - args: [], - ); - } - - /// `Where do you live?` - String get labelSelectCountry { - return Intl.message( - 'Where do you live?', - name: 'labelSelectCountry', - desc: '', - args: [], - ); - } - - /// `Choose country` - String get labelChooseCountry { - return Intl.message( - 'Choose country', - name: 'labelChooseCountry', - desc: '', - args: [], - ); - } - - /// `Unfortunately, Deriv is not available in your country.` - String get warnCountryNotAvailable { - return Intl.message( - 'Unfortunately, Deriv is not available in your country.', - name: 'warnCountryNotAvailable', - desc: '', - args: [], - ); - } - - /// `Next` - String get actionNext { - return Intl.message( - 'Next', - name: 'actionNext', - desc: '', - args: [], - ); - } - - /// `If you don't see an email from us within a few minutes, a few things could have happened:` - String get labelEmailIssueHeader { - return Intl.message( - 'If you don\'t see an email from us within a few minutes, a few things could have happened:', - name: 'labelEmailIssueHeader', - desc: '', - args: [], - ); - } - - /// `The email is in your spam folder (Sometimes things get lost there).` - String get labelEmailIssueSpam { - return Intl.message( - 'The email is in your spam folder (Sometimes things get lost there).', - name: 'labelEmailIssueSpam', - desc: '', - args: [], - ); - } - - /// `You accidentally gave us another email address (Usually a work or a personal one instead of the one you meant).` - String get labelEmailIssueWrongEmail { - return Intl.message( - 'You accidentally gave us another email address (Usually a work or a personal one instead of the one you meant).', - name: 'labelEmailIssueWrongEmail', - desc: '', - args: [], - ); - } - - /// `The email address you entered had a mistake or typo (happens to the best of us).` - String get labelEmailIssueTypo { - return Intl.message( - 'The email address you entered had a mistake or typo (happens to the best of us).', - name: 'labelEmailIssueTypo', - desc: '', - args: [], - ); - } - - /// `We can't deliver the email to this address (Usually because of firewalls or filtering).` - String get labelEmailIssueFirewall { - return Intl.message( - 'We can\'t deliver the email to this address (Usually because of firewalls or filtering).', - name: 'labelEmailIssueFirewall', - desc: '', - args: [], - ); - } - - /// `Re-enter your email and try again` - String get actionReenterEmail { - return Intl.message( - 'Re-enter your email and try again', - name: 'actionReenterEmail', - desc: '', - args: [], - ); - } - - /// `Keep your account secure with a password` - String get labelKeepPassword { - return Intl.message( - 'Keep your account secure with a password', - name: 'labelKeepPassword', - desc: '', - args: [], - ); - } - - /// `Create a password` - String get labelCreatePassword { - return Intl.message( - 'Create a password', - name: 'labelCreatePassword', - desc: '', - args: [], - ); - } - - /// `Start trading` - String get actionStartTrading { - return Intl.message( - 'Start trading', - name: 'actionStartTrading', - desc: '', - args: [], - ); - } - - /// `Previous` - String get actionPrevious { - return Intl.message( - 'Previous', - name: 'actionPrevious', - desc: '', - args: [], - ); - } - - /// `Sign up` - String get labelSignUp { - return Intl.message( - 'Sign up', - name: 'labelSignUp', - desc: '', - args: [], - ); - } - - /// `Or sign up with` - String get labelOrSignUpWith { - return Intl.message( - 'Or sign up with', - name: 'labelOrSignUpWith', - desc: '', - args: [], - ); - } - - /// `Affiliate referral code` - String get labelReferralInfoTitle { - return Intl.message( - 'Affiliate referral code', - name: 'labelReferralInfoTitle', - desc: '', - args: [], - ); - } - - /// `An alphanumeric code provided by a Deriv affiliate, applicable for email sign-ups only.` - String get infoReferralInfoDescription { - return Intl.message( - 'An alphanumeric code provided by a Deriv affiliate, applicable for email sign-ups only.', - name: 'infoReferralInfoDescription', - desc: '', - args: [], - ); - } - - /// `Got a referral code?` - String get labelGotReferralCode { - return Intl.message( - 'Got a referral code?', - name: 'labelGotReferralCode', - desc: '', - args: [], - ); - } - - /// `Already have an account?` - String get labelHaveAccount { - return Intl.message( - 'Already have an account?', - name: 'labelHaveAccount', - desc: '', - args: [], - ); - } - - /// `Create free demo account` - String get actionCreateAccount { - return Intl.message( - 'Create free demo account', - name: 'actionCreateAccount', - desc: '', - args: [], - ); - } - - /// `The referral code you entered is invalid. Check and try again.` - String get informInvalidReferralCode { - return Intl.message( - 'The referral code you entered is invalid. Check and try again.', - name: 'informInvalidReferralCode', - desc: '', - args: [], - ); - } - - /// `Verify your email` - String get labelVerifyYourEmail { - return Intl.message( - 'Verify your email', - name: 'labelVerifyYourEmail', - desc: '', - args: [], - ); - } - - /// `Thanks for verifying your email` - String get labelThanksEmail { - return Intl.message( - 'Thanks for verifying your email', - name: 'labelThanksEmail', - desc: '', - args: [], - ); - } - - /// `Let's continue.` - String get informLetsContinue { - return Intl.message( - 'Let\'s continue.', - name: 'informLetsContinue', - desc: '', - args: [], - ); - } - - /// `Continue` - String get actionContinue { - return Intl.message( - 'Continue', - name: 'actionContinue', - desc: '', - args: [], - ); - } - - /// `Search country` - String get labelSearchCountry { - return Intl.message( - 'Search country', - name: 'labelSearchCountry', - desc: '', - args: [], - ); - } - - /// `We've sent a message to {email} with a link to activate your account.` - String informVerificationEmailSent(String email) { - return Intl.message( - 'We\'ve sent a message to $email with a link to activate your account.', - name: 'informVerificationEmailSent', - desc: 'A message with a single parameter', - args: [email], - ); - } - - /// `Didn't receive your email?` - String get actionEmailNotReceived { - return Intl.message( - 'Didn\'t receive your email?', - name: 'actionEmailNotReceived', - desc: '', - args: [], - ); - } - - /// `Your password must have:` - String get informPasswordPolicy { - return Intl.message( - 'Your password must have:', - name: 'informPasswordPolicy', - desc: '', - args: [], - ); - } - - /// `8-25 characters` - String get informPasswordPolicyLength { - return Intl.message( - '8-25 characters', - name: 'informPasswordPolicyLength', - desc: '', - args: [], - ); - } - - /// `Upper and lower case letters` - String get informPasswordPolicyLowerAndUpper { - return Intl.message( - 'Upper and lower case letters', - name: 'informPasswordPolicyLowerAndUpper', - desc: '', - args: [], - ); - } - - /// `At least one number` - String get informPasswordPolicyNumber { - return Intl.message( - 'At least one number', - name: 'informPasswordPolicyNumber', - desc: '', - args: [], - ); - } - - /// `Use symbols for strong password.` - String get warnPasswordContainsSymbol { - return Intl.message( - 'Use symbols for strong password.', - name: 'warnPasswordContainsSymbol', - desc: '', - args: [], - ); - } - - /// `Referral Code` - String get labelReferralCode { - return Intl.message( - 'Referral Code', - name: 'labelReferralCode', - desc: '', - args: [], - ); - } -} - -class AppLocalizationDelegate - extends LocalizationsDelegate { - const AppLocalizationDelegate(); - - List get supportedLocales { - return const [ - Locale.fromSubtags(languageCode: 'en'), - ]; - } - - @override - bool isSupported(Locale locale) => _isSupported(locale); - @override - Future load(Locale locale) => - DerivAuthUILocalization.load(locale); - @override - bool shouldReload(AppLocalizationDelegate old) => false; - - bool _isSupported(Locale locale) { - for (var supportedLocale in supportedLocales) { - if (supportedLocale.languageCode == locale.languageCode) { - return true; - } - } - return false; - } -} diff --git a/packages/deriv_auth_ui/lib/src/core/extensions/context_extension.dart b/packages/deriv_auth_ui/lib/src/core/extensions/context_extension.dart deleted file mode 100644 index 1d96e8ab2..000000000 --- a/packages/deriv_auth_ui/lib/src/core/extensions/context_extension.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:deriv_auth_ui/generated/l10n.dart'; -import 'package:flutter/material.dart'; - -/// Extension methods for [BuildContext]. -extension ContextExtension on BuildContext { - /// Gets [DerivAuthUILocalization]. - DerivAuthUILocalization get localization => DerivAuthUILocalization.of(this); -} diff --git a/packages/deriv_auth_ui/lib/src/core/extensions/string_extension.dart b/packages/deriv_auth_ui/lib/src/core/extensions/string_extension.dart deleted file mode 100644 index 34eb12e54..000000000 --- a/packages/deriv_auth_ui/lib/src/core/extensions/string_extension.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:deriv_ui/deriv_ui.dart'; - -/// Extension methods for input validation on [String] using regex. -extension RegexExtension on String { - /// Indicates whether the input is a valid email. - bool get isValidEmail => validEmailRegex.hasMatch(this); - - /// Apart from signup (8-char password), login should support 6-char password as there will be legacy accounts having 6 chars. - bool get isValidLoginPasswordLength => - validLoginPasswordLengthRegex.hasMatch(this); - - /// Signup valid Password Regex. - bool get isValidSignupPassword => validPasswordRegex.hasMatch(this); -} diff --git a/packages/deriv_auth_ui/lib/src/features/login/widgets/deriv_social_auth_divider.dart b/packages/deriv_auth_ui/lib/src/features/login/widgets/deriv_social_auth_divider.dart deleted file mode 100644 index 64d14f557..000000000 --- a/packages/deriv_auth_ui/lib/src/features/login/widgets/deriv_social_auth_divider.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:deriv_theme/deriv_theme.dart'; -import 'package:flutter/material.dart'; - -/// The divider between in-app authentication and social authentication. -class DerivSocialAuthDivider extends StatelessWidget { - /// Initializes the class. - const DerivSocialAuthDivider({ - required this.label, - Key? key, - }) : super(key: key); - - /// The label that displayed in the divider. - final String label; - - @override - Widget build(BuildContext context) => Row( - children: [ - _buildDivider(context), - Padding( - padding: - const EdgeInsets.symmetric(horizontal: ThemeProvider.margin08), - child: Text( - label, - style: context.theme.textStyle( - textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, - ), - ), - ), - _buildDivider(context), - ], - ); - - Widget _buildDivider(BuildContext context) => Expanded( - child: Divider( - thickness: 1, - color: context.theme.colors.hover, - ), - ); -} diff --git a/packages/deriv_auth_ui/lib/src/features/login/widgets/deriv_social_auth_panel.dart b/packages/deriv_auth_ui/lib/src/features/login/widgets/deriv_social_auth_panel.dart deleted file mode 100644 index 91298a3d4..000000000 --- a/packages/deriv_auth_ui/lib/src/features/login/widgets/deriv_social_auth_panel.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:deriv_auth/deriv_auth.dart'; -import 'package:deriv_theme/deriv_theme.dart'; -import 'package:deriv_ui/deriv_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; - -/// Panel of buttons for social authentication. -class DerivSocialAuthPanel extends StatelessWidget { - /// Initializes [DerivSocialAuthPanel]. - const DerivSocialAuthPanel({ - required this.onSocialAuthButtonPressed, - this.isEnabled = true, - Key? key, - }) : super(key: key); - - /// Whether the buttons are enabled. - /// - /// If the buttons are disabled, they will be greyed out. - /// Defaults to `true`. - final bool isEnabled; - - /// onPressed callback for social auth buttons. - final void Function(SocialAuthProvider) onSocialAuthButtonPressed; - - @override - Widget build(BuildContext context) => Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _buildSocialAuthButton(SocialAuthProvider.apple), - const SizedBox(width: ThemeProvider.margin24), - _buildSocialAuthButton(SocialAuthProvider.google), - const SizedBox(width: ThemeProvider.margin24), - _buildSocialAuthButton(SocialAuthProvider.facebook), - ], - ); - - Widget _buildSocialAuthButton(SocialAuthProvider socialAuthProvider) => - IconButton( - padding: EdgeInsets.zero, - iconSize: ThemeProvider.iconSize40, - icon: Opacity( - opacity: getOpacity(isEnabled: isEnabled), - child: SvgPicture.asset( - _getSocialMediaIcon(socialAuthProvider), - package: 'deriv_auth_ui', - ), - ), - onPressed: isEnabled - ? () => onSocialAuthButtonPressed(socialAuthProvider) - : null, - ); - - String _getSocialMediaIcon(SocialAuthProvider socialAuthProvider) => - 'assets/icons/ic_${socialAuthProvider.name}.svg'; -} diff --git a/packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_choose_new_pass_layout.dart b/packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_choose_new_pass_layout.dart deleted file mode 100644 index 787a508d9..000000000 --- a/packages/deriv_auth_ui/lib/src/features/reset_pass/layouts/deriv_choose_new_pass_layout.dart +++ /dev/null @@ -1,261 +0,0 @@ -import 'dart:async'; - -import 'package:deriv_auth/deriv_auth.dart'; -import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; -import 'package:deriv_auth_ui/src/core/extensions/string_extension.dart'; -import 'package:deriv_auth_ui/src/core/helpers/assets.dart'; -import 'package:deriv_auth_ui/src/features/signup/widgets/password_policy_checker_widget.dart'; -import 'package:deriv_theme/deriv_theme.dart'; -import 'package:deriv_ui/deriv_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_svg/flutter_svg.dart'; - -/// Choose new Pass page. -class DerivChooseNewPassLayout extends StatefulWidget { - /// Initializes choose new pass page. - const DerivChooseNewPassLayout({ - required this.token, - required this.onResetPassSucceed, - required this.onResetPassError, - Key? key, - }) : super(key: key); - - /// Access token for changing password. can be received from email verification step. - final String token; - - /// Callback to be called when reset pass fails. - final Function(String?) onResetPassError; - - /// Callback to be called when reset pass succeeds. - final VoidCallback onResetPassSucceed; - - @override - State createState() => - _DerivChooseNewPassLayoutState(); -} - -class _DerivChooseNewPassLayoutState extends State { - static const Duration _successPageHoldDuration = Duration(seconds: 2); - - final GlobalKey _formKey = GlobalKey(); - final TextEditingController _passController = TextEditingController(); - final FocusNode _passFocusNode = FocusNode(); - final PageController _pageController = PageController(); - - bool _isBusy = false; - bool _isPasswordVisible = false; - - @override - Widget build(BuildContext context) => Scaffold( - backgroundColor: context.theme.colors.primary, - appBar: AppBar( - elevation: ThemeProvider.zeroMargin, - title: Text( - context.localization.labelResetPassword, - style: TextStyles.title, - ), - ), - body: BlocListener( - listener: (BuildContext context, DerivResetPassState state) { - if (state is DerivResetPassPasswordChangedState) { - _pageController.animateToPage( - 1, - duration: slidingPageChangeDuration, - curve: Curves.easeInOut, - ); - - Timer(_successPageHoldDuration, widget.onResetPassSucceed); - } else if (state is DerivResetPassErrorState) { - widget.onResetPassError(state.errorMessage); - } - }, - child: PageView( - controller: _pageController, - physics: const NeverScrollableScrollPhysics(), - children: [ - _buildChooseNewPassSection(context), - _buildSuccessPassChangeSection(context) - ], - ), - ), - ); - - Widget _buildChooseNewPassSection(BuildContext context) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: ThemeProvider.margin16, - ), - child: Center( - child: Form( - key: _formKey, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const SizedBox(height: ThemeProvider.margin72), - Expanded(child: _buildContent()), - const SizedBox(height: ThemeProvider.margin24), - _buildSubmitPassButton() - ], - ), - ), - ), - ); - - Widget _buildContent() => - NotificationListener( - onNotification: (OverscrollIndicatorNotification overscroll) { - overscroll.disallowIndicator(); - - return true; - }, - child: SingleChildScrollView( - child: Column( - children: [ - Center( - child: SvgPicture.asset( - Assets.chooseNewPassIcon, - package: 'deriv_auth_ui', - width: ThemeProvider.iconSize96, - height: ThemeProvider.iconSize32, - ), - ), - const SizedBox(height: ThemeProvider.margin48), - Text( - context.localization.labelChooseNewPass, - style: TextStyles.title, - ), - const SizedBox(height: ThemeProvider.margin24), - BaseTextField( - controller: _passController, - focusNode: _passFocusNode, - labelText: context.localization.labelCreatePass, - obscureText: !_isPasswordVisible, - suffixIcon: IconButton( - icon: Icon( - _isPasswordVisible - ? Icons.visibility - : Icons.visibility_off, - color: context.theme.colors.disabled, - ), - splashColor: Colors.transparent, - highlightColor: Colors.transparent, - onPressed: () => - setState(() => _isPasswordVisible = !_isPasswordVisible), - ), - validator: _passwordValidator, - onChanged: (_) => setState(() {}), - onEditingComplete: () => _formKey.currentState?.validate(), - ), - const SizedBox(height: ThemeProvider.margin40), - PasswordPolicyCheckerWidget( - passwordController: _passController, - policies: PasswordPolicyCheckerWidget.getDerivPasswordPolicies( - context, - ), - ), - ], - ), - ), - ); - - Widget _buildSuccessPassChangeSection(BuildContext context) => Center( - child: Padding( - padding: - const EdgeInsets.symmetric(horizontal: ThemeProvider.margin16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox( - height: ThemeProvider.margin16, - width: ThemeProvider.margin16, - child: LoadingIndicator( - valueColor: Colors.white, - strokeWidth: 2.5, - ), - ), - const SizedBox( - height: ThemeProvider.margin16, - ), - Text( - context.localization.informYourPassHasBeenReset, - style: TextStyles.title, - ), - const SizedBox( - height: ThemeProvider.margin08, - ), - Text( - context.localization.informRedirectLogin, - textAlign: TextAlign.center, - style: context.theme.textStyle( - textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, - ), - ), - ], - ), - ), - ); - - Widget _buildSubmitPassButton() => ElevatedButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - context.theme.colors.coral.withOpacity( - getOpacity(isEnabled: isFormValid()), - ), - ), - ), - onPressed: isFormValid() ? _onSubmitEmailTapped : null, - child: Center( - child: _isBusy - ? const LoadingIndicator( - valueColor: Colors.white, - strokeWidth: ThemeProvider.margin02, - height: ThemeProvider.iconSize16, - width: ThemeProvider.iconSize16, - ) - : Text( - context.localization.actionResetPass, - style: context.theme.textStyle( - textStyle: TextStyles.body2, - color: context.theme.colors.prominent.withOpacity( - getOpacity(isEnabled: isFormValid()), - ), - ), - ), - ), - ); - - Future _onSubmitEmailTapped() async { - _passFocusNode.unfocus(); - - if ((_formKey.currentState?.validate() ?? false) && !_isBusy) { - setState(() => _isBusy = true); - - await context.read().changePassword( - token: widget.token, - newPassword: _passController.text, - ); - - setState(() => _isBusy = false); - } - } - - String? _passwordValidator(String? input) { - if (input?.isValidSignupPassword ?? false) { - return null; - } - - return context.localization.informInvalidPasswordFormat; - } - - bool isFormValid() => _passwordValidator(_passController.text) == null; - - @override - void dispose() { - _pageController.dispose(); - _passController.dispose(); - _passFocusNode.dispose(); - - super.dispose(); - } -} diff --git a/packages/deriv_auth_ui/lib/src/features/signup/cubits/deriv_country_selection_cubit.dart b/packages/deriv_auth_ui/lib/src/features/signup/cubits/deriv_country_selection_cubit.dart deleted file mode 100644 index 4ce13603a..000000000 --- a/packages/deriv_auth_ui/lib/src/features/signup/cubits/deriv_country_selection_cubit.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:deriv_auth_ui/src/features/signup/models/deriv_residence_model.dart'; - -part 'deriv_country_selection_state.dart'; - -/// Cubit to handle country selection screen. -class DerivCountrySelectionCubit extends Cubit { - /// Constructor for CountrySelectionCubit. - DerivCountrySelectionCubit(this.residences) - : super(const DerivCountrySelectionInitialState()); - - /// List of residence countries. - final Future> residences; - - /// Fetches residence countries. - Future fetchResidenceCounties() async { - final List countries = await residences; - - final List filteredCountries = countries - .where( - (DerivResidenceModel country) => _isAllowedCountry(country), - ) - .toList(); - - emit(DerivCountrySelectionLoadedState(filteredCountries)); - } - - bool _isAllowedCountry(DerivResidenceModel country) => _notAllowedCountryCodes - .every((String countryCode) => country.code != countryCode); - - static final List _notAllowedCountryCodes = [ - // MF country codes. - 'de', - 'es', - 'fr', - 'gr', - 'it', - 'lu', - 'mf', - // MLT country codes. - 'at', - 'be', - 'bg', - 'cy', - 'cz', - 'dk', - 'ee', - 'fi', - 'hr', - 'hu', - 'ie', - 'lt', - 'lv', - 'nl', - 'pl', - 'pt', - 'ro', - 'se', - 'si', - 'sk', - // MX country codes. - 'gb', - 'im' - ]; -} diff --git a/packages/deriv_auth_ui/lib/src/features/signup/cubits/deriv_country_selection_state.dart b/packages/deriv_auth_ui/lib/src/features/signup/cubits/deriv_country_selection_state.dart deleted file mode 100644 index 6f67179df..000000000 --- a/packages/deriv_auth_ui/lib/src/features/signup/cubits/deriv_country_selection_state.dart +++ /dev/null @@ -1,30 +0,0 @@ -part of 'deriv_country_selection_cubit.dart'; - -/// Country selection state -abstract class DerivCountrySelectionState extends Equatable { - /// Initialize country selection state. - const DerivCountrySelectionState(this.countries); - - /// List of countries. - final List countries; -} - -/// Initial state. -class DerivCountrySelectionInitialState extends DerivCountrySelectionState { - /// Initializes initial state. - const DerivCountrySelectionInitialState() - : super(const []); - - @override - List get props => []; -} - -/// Country list loaded state. -class DerivCountrySelectionLoadedState extends DerivCountrySelectionState { - /// Initialize country list loaded state - const DerivCountrySelectionLoadedState(List countries) - : super(countries); - - @override - List get props => [countries]; -} diff --git a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_country_selection_layout.dart b/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_country_selection_layout.dart deleted file mode 100644 index 01ccde127..000000000 --- a/packages/deriv_auth_ui/lib/src/features/signup/layouts/deriv_country_selection_layout.dart +++ /dev/null @@ -1,172 +0,0 @@ -import 'package:deriv_auth_ui/src/core/extensions/context_extension.dart'; -import 'package:deriv_auth_ui/src/core/helpers/assets.dart'; -import 'package:deriv_auth_ui/src/features/signup/cubits/deriv_country_selection_cubit.dart'; -import 'package:deriv_auth_ui/src/features/signup/models/deriv_residence_model.dart'; -import 'package:deriv_auth_ui/src/features/signup/widgets/country_selection_list_widget.dart'; -import 'package:deriv_theme/deriv_theme.dart'; -import 'package:deriv_ui/deriv_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_svg/svg.dart'; - -/// Allows user to select a country from list -class DerivCountrySelectionLayout extends StatefulWidget { - /// constructor of country selection page - const DerivCountrySelectionLayout({ - required this.verificationCode, - required this.residences, - required this.onNextPressed, - this.affiliateToken, - Key? key, - }) : super(key: key); - - /// List of residences to be shown. - final Future> residences; - - /// Callback to be called when next button is tapped. - final VoidCallback onNextPressed; - - /// Verification Code from Previous Step. Saved for next step. - final String verificationCode; - - /// Affiliate token. - final String? affiliateToken; - - @override - State createState() => - _DerivCountrySelectionLayoutState(); -} - -class _DerivCountrySelectionLayoutState - extends State { - final GlobalKey _formKey = GlobalKey(); - final FocusNode _focusNode = FocusNode(); - - late TextEditingController _textController; - - late final DerivCountrySelectionCubit _countrySelectionCubit; - DerivResidenceModel? _selectedResidence; - - @override - void initState() { - super.initState(); - - _countrySelectionCubit = DerivCountrySelectionCubit(widget.residences) - ..fetchResidenceCounties(); - - _textController = TextEditingController(); - } - - @override - Widget build(BuildContext context) => Scaffold( - backgroundColor: context.theme.colors.primary, - body: SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Expanded(child: _buildUpperPage()), - Padding( - padding: const EdgeInsets.all(ThemeProvider.margin16), - child: _buildLowerPage(), - ), - ], - ), - ), - ); - - Widget _buildUpperPage() => Padding( - padding: const EdgeInsets.all(ThemeProvider.margin16), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset(Assets.locationIcon, package: 'deriv_auth_ui'), - const SizedBox(height: ThemeProvider.margin16), - Text( - context.localization.labelSelectCountry, - style: TextStyles.title, - ), - const SizedBox(height: ThemeProvider.margin24), - _buildSelectionInput() - ], - ), - ); - - Widget _buildLowerPage() => Row( - children: [ - Expanded(child: _buildNextButton()), - ], - ); - - Widget _buildSelectionInput() => - BlocBuilder( - bloc: _countrySelectionCubit, - builder: (BuildContext context, DerivCountrySelectionState state) => - Form( - key: _formKey, - child: BaseTextField( - controller: _textController, - labelText: context.localization.labelChooseCountry, - labelColor: context.theme.colors.disabled, - focusNode: _focusNode, - focusedLabelColor: context.theme.colors.blue, - suffixIcon: Icon( - Icons.keyboard_arrow_down, - color: context.theme.colors.prominent, - ), - readOnly: true, - enabled: state is DerivCountrySelectionLoadedState, - validator: (String? value) => _selectedResidence!.isDisabled - ? context.localization.warnCountryNotAvailable - : null, - onTap: () => _onSelectCountryTap(state.countries), - ), - ), - ); - - Widget _buildNextButton() => PrimaryButton( - isEnabled: - _selectedResidence != null && !_selectedResidence!.isDisabled, - onPressed: widget.onNextPressed, - child: Center( - child: Text( - context.localization.actionNext, - style: TextStyles.button - .copyWith(color: context.theme.colors.prominent), - ), - ), - ); - - void _onSelectCountryTap(List countries) { - final MediaQueryData mediaQuery = MediaQuery.of(context); - final double screenHeight = mediaQuery.size.height; - final double notchHeight = mediaQuery.padding.top; - - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (BuildContext context) => SizedBox( - height: screenHeight - notchHeight, - child: CountrySelectionListWidget( - countries: countries, - onChanged: (int index) => setState( - () { - _textController.text = countries[index].name; - _selectedResidence = countries[index]; - - _formKey.currentState!.validate(); - }, - ), - ), - ), - ); - } - - @override - void dispose() { - _focusNode.dispose(); - - _countrySelectionCubit.close(); - - super.dispose(); - } -} diff --git a/packages/deriv_auth_ui/pubspec.yaml b/packages/deriv_auth_ui/pubspec.yaml deleted file mode 100644 index 0b7c361f6..000000000 --- a/packages/deriv_auth_ui/pubspec.yaml +++ /dev/null @@ -1,53 +0,0 @@ -name: deriv_auth_ui -description: A flutter package for deriv auth UI flows. -version: 0.0.1 - -environment: - sdk: ">=3.0.0 <4.0.0" - flutter: "3.10.2" - -publish_to: "none" - -dependencies: - flutter: - sdk: flutter - - flutter_bloc: ^8.1.3 - - deriv_auth: - git: - url: git@github.com:regentmarkets/flutter-deriv-packages.git - path: packages/deriv_auth - ref: d5bef178341a318f911b42b8105d8c3d50c6b63e - deriv_theme: - path: ../deriv_theme - # git: - # url: git@github.com:regentmarkets/flutter-deriv-packages.git - # path: packages/deriv_theme - # ref: dev - deriv_ui: - path: ../deriv_ui - flutter_svg: ^1.1.6 - smooth_page_indicator: ^1.1.0 - intl_utils: ^2.8.3 - intl: ^0.18.0 - equatable: ^2.0.5 - -dev_dependencies: - flutter_test: - sdk: flutter - integration_test: - sdk: flutter - flutter_lints: ^2.0.0 - mocktail: ^0.3.0 - patrol: ^1.1.4 - bloc_test: ^9.1.3 - -flutter_intl: - enabled: true - class_name: DerivAuthUILocalization - -flutter: - assets: - - assets/icons/ - - assets/images/ diff --git a/packages/deriv_auth_ui/test/core/extensions/string_extensions_test.dart b/packages/deriv_auth_ui/test/core/extensions/string_extensions_test.dart deleted file mode 100644 index d9aea422a..000000000 --- a/packages/deriv_auth_ui/test/core/extensions/string_extensions_test.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:deriv_auth_ui/src/core/extensions/string_extension.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - group('RegexExtension', () { - test('isValidEmail should return true for valid email', () { - const String validEmail = 'test@example.com'; - expect(validEmail.isValidEmail, true); - }); - - test('isValidEmail should return false for invalid email', () { - const String invalidEmail = 'test'; - expect(invalidEmail.isValidEmail, false); - }); - - test('isValidLoginPasswordLength should return true for valid length', () { - const String validPassword = 'password'; - expect(validPassword.isValidLoginPasswordLength, true); - }); - - test('isValidLoginPasswordLength should return false for invalid length', - () { - const String invalidPassword = 'pass'; - expect(invalidPassword.isValidLoginPasswordLength, false); - }); - - test('isValidSignupPassword should return true for valid password', () { - const String validPassword = 'Abc123456'; - expect(validPassword.isValidSignupPassword, true); - }); - - test('isValidSignupPassword should return false for invalid password', () { - const String invalidPassword = 'password'; - expect(invalidPassword.isValidSignupPassword, false); - }); - }); -} diff --git a/packages/deriv_auth_ui/test/features/get_started/layouts/deriv_get_started_layout_test.dart b/packages/deriv_auth_ui/test/features/get_started/layouts/deriv_get_started_layout_test.dart deleted file mode 100644 index 82100761b..000000000 --- a/packages/deriv_auth_ui/test/features/get_started/layouts/deriv_get_started_layout_test.dart +++ /dev/null @@ -1,82 +0,0 @@ -// ignore_for_file: always_specify_types - -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; -import 'package:deriv_ui/deriv_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:patrol/patrol.dart'; - -import '../../../pump_app.dart'; - -class MockDerivGetStartedSlideModel extends Mock - implements DerivGetStartedSlideModel {} - -void main() { - group('DerivGetStartedLayout', () { - late MockDerivGetStartedSlideModel mockSlideModel; - - const String appLogoIconPath = 'assets/icons/ic_logo_extended.svg'; - const String backgroundImagePath = 'assets/images/triangles.svg'; - - setUpAll(() { - mockSlideModel = MockDerivGetStartedSlideModel(); - when(() => mockSlideModel.imagePath) - .thenReturn('assets/images/charts.svg'); - when(() => mockSlideModel.supportingText).thenReturn('Supporting text'); - }); - - patrolTest('should render DerivGetStartedLayout', (PatrolTester $) async { - await $.pumpApp(DerivGetStartedLayout( - slides: [mockSlideModel], - appLogoIconPath: appLogoIconPath, - backgroundImagePath: backgroundImagePath, - onLoginTapped: () {}, - onSignupTapped: () {}, - )); - - expect($(DerivGetStartedLayout), findsOneWidget); - expect($(AppBar), findsOneWidget); - expect($(PrimaryButton), findsOneWidget); - expect($(SecondaryButton), findsOneWidget); - }); - - patrolTest('should call onLoginTapped when login button is pressed', - (PatrolTester $) async { - bool loginTapped = false; - - await $.pumpApp(DerivGetStartedLayout( - slides: [mockSlideModel], - appLogoIconPath: appLogoIconPath, - backgroundImagePath: backgroundImagePath, - onLoginTapped: () { - loginTapped = true; - }, - onSignupTapped: () {}, - )); - - await $.tap($(SecondaryButton)); - - expect(loginTapped, isTrue); - }); - - patrolTest('should call onSignupTapped when signup button is pressed', - (PatrolTester $) async { - bool signupTapped = false; - - await $.pumpApp(DerivGetStartedLayout( - slides: [mockSlideModel], - appLogoIconPath: appLogoIconPath, - backgroundImagePath: backgroundImagePath, - onLoginTapped: () {}, - onSignupTapped: () { - signupTapped = true; - }, - )); - - await $.tap($(PrimaryButton)); - - expect(signupTapped, isTrue); - }); - }); -} diff --git a/packages/deriv_auth_ui/test/features/login/layouts/deriv_login_layout_test.dart b/packages/deriv_auth_ui/test/features/login/layouts/deriv_login_layout_test.dart deleted file mode 100644 index 101e4be1e..000000000 --- a/packages/deriv_auth_ui/test/features/login/layouts/deriv_login_layout_test.dart +++ /dev/null @@ -1,275 +0,0 @@ -// ignore_for_file: always_specify_types - -import 'package:deriv_auth/core/models/landig_comany_model.dart'; -import 'package:deriv_auth/deriv_auth.dart'; -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; -import 'package:deriv_auth_ui/src/features/login/widgets/deriv_social_auth_panel.dart'; -import 'package:deriv_ui/deriv_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:patrol/patrol.dart'; - -import '../../../mocks.dart'; -import '../../../pump_app.dart'; - -void main() { - group('DerivLoginLayout', () { - late MockAuthCubit authCubit; - - const String welcomeLabel = 'Welcome Back'; - const String greetingLabel = 'Let\'s start trading.'; - - setUpAll(() => authCubit = MockAuthCubit()); - - patrolTest( - 'renders email and password field including social auth buttons.', - (PatrolTester $) async { - final mockAuthState = DerivAuthLoggedOutState(); - - when(() => authCubit.state).thenAnswer((_) => mockAuthState); - - when(() => authCubit.stream) - .thenAnswer((_) => Stream.fromIterable([mockAuthState])); - - await $.pumpApp( - BlocProvider.value( - value: authCubit, - child: DerivLoginLayout( - welcomeLabel: welcomeLabel, - greetingLabel: greetingLabel, - onResetPassTapped: () {}, - onSignupTapped: () {}, - onLoginError: (_) {}, - onLoggedIn: (_) {}, - onSocialAuthButtonPressed: (p0) {}, - ), - ), - ); - - expect($(DerivLoginLayout), findsOneWidget); - expect($(BaseTextField).$('Email'), findsOneWidget); - expect($(BaseTextField).$('Password'), findsOneWidget); - expect($(DerivSocialAuthPanel), findsOneWidget); - }); - - patrolTest('displays invalid email error on invalid email typed.', - (PatrolTester $) async { - final mockAuthState = DerivAuthLoggedOutState(); - const invalidEmail = 'invalid-email'; - - when(() => authCubit.state).thenAnswer((_) => mockAuthState); - - when(() => authCubit.stream) - .thenAnswer((_) => Stream.fromIterable([mockAuthState])); - - await $.pumpApp( - BlocProvider.value( - value: authCubit, - child: DerivLoginLayout( - welcomeLabel: welcomeLabel, - greetingLabel: greetingLabel, - onResetPassTapped: () {}, - onSignupTapped: () {}, - onLoginError: (_) {}, - onLoggedIn: (_) {}, - onSocialAuthButtonPressed: (_) {}, - ), - ), - ); - - final emailField = $(BaseTextField).first; - // final emailField = $(BaseTextField).$('Email'); --> this doesn't work - - await $.enterText(emailField, invalidEmail); - - expect($(Text).$('Enter a valid email address'), findsOneWidget); - }); - - patrolTest('displays loading error on AuthLoadingState', - (PatrolTester $) async { - final mockAuthState = DerivAuthLoadingState(); - - when(() => authCubit.state).thenAnswer((_) => mockAuthState); - - when(() => authCubit.stream) - .thenAnswer((_) => Stream.fromIterable([mockAuthState])); - - await $.pumpApp( - settle: false, - BlocProvider.value( - value: authCubit, - child: DerivLoginLayout( - welcomeLabel: welcomeLabel, - greetingLabel: greetingLabel, - onResetPassTapped: () {}, - onSignupTapped: () {}, - onLoginError: (_) {}, - onLoggedIn: (_) {}, - onSocialAuthButtonPressed: (_) {}, - ), - )); - - expect($(LoadingIndicator), findsOneWidget); - }); - - patrolTest('calls signupTapped when signup button is pressed.', - (PatrolTester $) async { - final mockAuthState = DerivAuthLoggedOutState(); - - when(() => authCubit.state).thenAnswer((_) => mockAuthState); - - when(() => authCubit.stream) - .thenAnswer((_) => Stream.fromIterable([mockAuthState])); - - bool onSignupTappedCalled = false; - - await $.pumpApp(BlocProvider.value( - value: authCubit, - child: DerivLoginLayout( - welcomeLabel: welcomeLabel, - greetingLabel: greetingLabel, - onResetPassTapped: () {}, - onSignupTapped: () { - onSignupTappedCalled = true; - }, - onLoginError: (_) {}, - onLoggedIn: (_) {}, - onSocialAuthButtonPressed: (_) {}, - ), - )); - - final signupButton = $(InkWell).$('Create a new account'); - - await $.scrollUntilVisible(finder: signupButton); - - await signupButton.tap(); - - expect(onSignupTappedCalled, isTrue); - }); - - patrolTest('calls onLoggedIn on successful login.', (PatrolTester $) async { - final mockAuthState = DerivAuthLoggedInState( - authorizeEntity: const AuthorizeEntity(), - landingCompany: const LandingCompanyEntity(), - ); - - when(() => authCubit.state).thenAnswer((_) => mockAuthState); - - when(() => authCubit.stream) - .thenAnswer((_) => Stream.fromIterable([mockAuthState])); - - bool onLoggedInCalled = false; - - await $.pumpApp(BlocProvider.value( - value: authCubit, - child: DerivLoginLayout( - welcomeLabel: welcomeLabel, - greetingLabel: greetingLabel, - onResetPassTapped: () {}, - onSignupTapped: () {}, - onLoginError: (_) {}, - onLoggedIn: (_) { - onLoggedInCalled = true; - }, - onSocialAuthButtonPressed: (_) {}, - ), - )); - - expect(onLoggedInCalled, isTrue); - }); - - patrolTest('calls onLoginError on login error.', (PatrolTester $) async { - final mockAuthState = DerivAuthErrorState( - message: 'error', type: AuthErrorType.failedAuthorization); - - when(() => authCubit.state).thenAnswer((_) => mockAuthState); - - when(() => authCubit.stream) - .thenAnswer((_) => Stream.fromIterable([mockAuthState])); - - bool onLoginErrorCalled = false; - - await $.pumpApp(BlocProvider.value( - value: authCubit, - child: DerivLoginLayout( - welcomeLabel: welcomeLabel, - greetingLabel: greetingLabel, - onResetPassTapped: () {}, - onSignupTapped: () {}, - onLoginError: (_) { - onLoginErrorCalled = true; - }, - onLoggedIn: (_) {}, - onSocialAuthButtonPressed: (_) {}, - ), - )); - - expect(onLoginErrorCalled, isTrue); - }); - - patrolTest('calls resetPassTapped when reset button is pressed.', - (PatrolTester $) async { - final mockAuthState = DerivAuthLoggedOutState(); - - when(() => authCubit.state).thenAnswer((_) => mockAuthState); - - when(() => authCubit.stream) - .thenAnswer((_) => Stream.fromIterable([mockAuthState])); - - bool onResetPassTappedCalled = false; - - await $.pumpApp(BlocProvider.value( - value: authCubit, - child: DerivLoginLayout( - welcomeLabel: welcomeLabel, - greetingLabel: greetingLabel, - onResetPassTapped: () { - onResetPassTappedCalled = true; - }, - onSignupTapped: () {}, - onLoginError: (_) {}, - onLoggedIn: (_) {}, - onSocialAuthButtonPressed: (_) {}, - ), - )); - - await $(InkWell).$('Forgot password?').tap(); - - expect(onResetPassTappedCalled, isTrue); - }); - - patrolTest( - 'calls onSocialAuthButtonPressed when social auth button is pressed.', - (PatrolTester $) async { - final mockAuthState = DerivAuthLoggedOutState(); - - when(() => authCubit.state).thenAnswer((_) => mockAuthState); - - when(() => authCubit.stream) - .thenAnswer((_) => Stream.fromIterable([mockAuthState])); - - bool onSocialAuthButtonPressedCalled = false; - - await $.pumpApp(BlocProvider.value( - value: authCubit, - child: DerivLoginLayout( - welcomeLabel: welcomeLabel, - greetingLabel: greetingLabel, - onResetPassTapped: () {}, - onSignupTapped: () {}, - onLoginError: (_) {}, - onLoggedIn: (_) {}, - onSocialAuthButtonPressed: (_) { - onSocialAuthButtonPressedCalled = true; - }, - ), - )); - - await $(IconButton).at(1).tap(); - - expect(onSocialAuthButtonPressedCalled, isTrue); - }); - }); -} diff --git a/packages/deriv_auth_ui/test/features/signup/cubits/deriv_country_selection_cubit_test.dart b/packages/deriv_auth_ui/test/features/signup/cubits/deriv_country_selection_cubit_test.dart deleted file mode 100644 index 7d854c8bc..000000000 --- a/packages/deriv_auth_ui/test/features/signup/cubits/deriv_country_selection_cubit_test.dart +++ /dev/null @@ -1,34 +0,0 @@ -// ignore_for_file: always_specify_types - -import 'package:bloc_test/bloc_test.dart'; -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - group('DerivCountrySelectionCubit', () { - late DerivCountrySelectionCubit cubit; - - setUp(() { - final countries = [ - const DerivResidenceModel( - code: 'us', name: 'United States', isDisabled: false), - const DerivResidenceModel( - code: 'fr', name: 'France', isDisabled: false), - const DerivResidenceModel( - code: 'de', name: 'Germany', isDisabled: false), - ]; - cubit = DerivCountrySelectionCubit(Future.value(countries)); - }); - - test('initial state is DerivCountrySelectionInitialState', () { - expect(cubit.state, const DerivCountrySelectionInitialState()); - }); - - blocTest( - 'emits DerivCountrySelectionLoadedState with filtered countries when fetchResidenceCounties is called', - build: () => cubit, - act: (cubit) => cubit.fetchResidenceCounties(), - expect: () => [isA()], - ); - }); -} diff --git a/packages/deriv_auth_ui/test/features/signup/cubits/deriv_country_selection_state_test.dart b/packages/deriv_auth_ui/test/features/signup/cubits/deriv_country_selection_state_test.dart deleted file mode 100644 index 146c66014..000000000 --- a/packages/deriv_auth_ui/test/features/signup/cubits/deriv_country_selection_state_test.dart +++ /dev/null @@ -1,39 +0,0 @@ -// ignore_for_file: always_specify_types - -import 'package:bloc_test/bloc_test.dart'; -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - group('DerivCountrySelectionCubit', () { - late DerivCountrySelectionCubit cubit; - - setUp(() { - final countries = [ - const DerivResidenceModel( - code: 'us', name: 'United States', isDisabled: false), - const DerivResidenceModel( - code: 'fr', name: 'France', isDisabled: false), - const DerivResidenceModel( - code: 'de', name: 'Germany', isDisabled: false), - ]; - cubit = DerivCountrySelectionCubit(Future.value(countries)); - }); - - test('initial state is DerivCountrySelectionInitialState', () { - expect(cubit.state, const DerivCountrySelectionInitialState()); - }); - - blocTest( - 'emits DerivCountrySelectionLoadedState with filtered countries when fetchResidenceCounties is called', - build: () => cubit, - act: (cubit) => cubit.fetchResidenceCounties(), - expect: () => [ - const DerivCountrySelectionLoadedState([ - DerivResidenceModel( - code: 'us', name: 'United States', isDisabled: false), - ]), - ], - ); - }); -} diff --git a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_signup_layout_test.dart b/packages/deriv_auth_ui/test/features/signup/layouts/deriv_signup_layout_test.dart deleted file mode 100644 index c3186feb6..000000000 --- a/packages/deriv_auth_ui/test/features/signup/layouts/deriv_signup_layout_test.dart +++ /dev/null @@ -1,190 +0,0 @@ -// ignore_for_file: always_specify_types - -import 'package:deriv_auth/deriv_auth.dart'; -import 'package:deriv_auth_ui/deriv_auth_ui.dart'; -import 'package:deriv_auth_ui/src/features/login/widgets/deriv_social_auth_panel.dart'; -import 'package:deriv_ui/deriv_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:patrol/patrol.dart'; - -import '../../../mocks.dart'; -import '../../../pump_app.dart'; - -void main() { - group('DerivSignupLayout', () { - late MockSignupCubit signupCubit; - - const String signupPageLabel = 'Create a free account'; - const String signupPageDescription = 'Start trading within minutes.'; - - setUpAll(() { - signupCubit = MockSignupCubit(); - - when(() => signupCubit.state) - .thenAnswer((_) => const DerivSignupInitialState()); - - when(() => signupCubit.stream).thenAnswer( - (_) => Stream.fromIterable([const DerivSignupInitialState()])); - }); - - patrolTest('renders correctly', (PatrolTester $) async { - await $.pumpApp( - settle: false, - BlocProvider.value( - value: signupCubit, - child: DerivSignupLayout( - signupPageLabel: signupPageLabel, - signupPageDescription: signupPageDescription, - onSocialAuthButtonPressed: (_) {}, - onSingupError: (_) {}, - onSingupEmailSent: (_) {}, - onSignupPressed: () {}, - onLoginTapped: () {}, - ), - )); - - expect($(DerivSignupLayout), findsOneWidget); - expect($(BaseTextField).$('Email'), findsOneWidget); - expect($(ElevatedButton).$('Create free demo account'), findsOneWidget); - expect($(DerivSocialAuthPanel), findsOneWidget); - }); - - patrolTest( - 'onSocialAuthButtonPressed is called upon tapping social auth option', - (PatrolTester $) async { - bool isOnSocialAuthButtonPressedCalled = false; - - await $.pumpApp(BlocProvider.value( - value: signupCubit, - child: DerivSignupLayout( - signupPageLabel: signupPageLabel, - signupPageDescription: signupPageDescription, - onSocialAuthButtonPressed: (_) { - isOnSocialAuthButtonPressedCalled = true; - }, - onSingupError: (_) {}, - onSingupEmailSent: (_) {}, - onSignupPressed: () {}, - onLoginTapped: () {}, - ), - )); - - await $.tap($(DerivSocialAuthPanel)); - - expect(isOnSocialAuthButtonPressedCalled, true); - }); - - patrolTest('onSignupEmailSent is called upon sign up email sent', - (PatrolTester $) async { - bool isOnSignupEmailSentCalled = false; - - when(() => signupCubit.state) - .thenAnswer((_) => const DerivSignupEmailSentState()); - - when(() => signupCubit.stream).thenAnswer( - (_) => Stream.fromIterable([const DerivSignupEmailSentState()])); - - await $.pumpApp(BlocProvider.value( - value: signupCubit, - child: DerivSignupLayout( - signupPageLabel: signupPageLabel, - signupPageDescription: signupPageDescription, - onSocialAuthButtonPressed: (_) {}, - onSingupError: (_) {}, - onSingupEmailSent: (_) => isOnSignupEmailSentCalled = true, - onSignupPressed: () {}, - onLoginTapped: () {}, - ), - )); - - expect(isOnSignupEmailSentCalled, true); - }); - - patrolTest('onSignupPressed is called upon tapping signup button', - (PatrolTester $) async { - bool isOnSignupPressedCalled = false; - - when(() => signupCubit.sendVerificationEmail('test@gmail.com')) - .thenAnswer((_) async => const DerivSignupEmailSentState()); - - await $.pumpApp(BlocProvider.value( - value: signupCubit, - child: DerivSignupLayout( - signupPageLabel: signupPageLabel, - signupPageDescription: signupPageDescription, - onSocialAuthButtonPressed: (_) {}, - onSingupError: (_) {}, - onSingupEmailSent: (_) {}, - onSignupPressed: () => isOnSignupPressedCalled = true, - onLoginTapped: () {}, - ), - )); - - final signUpButton = $(ElevatedButton).$('Create free demo account'); - - await $.enterText($(BaseTextField), 'test@gmail.com'); - await $.tap(signUpButton); - - expect(isOnSignupPressedCalled, true); - - verify(() => signupCubit.sendVerificationEmail('test@gmail.com')) - .called(1); - }); - - patrolTest('onLoginTapped is called upon tapping login button', - (PatrolTester $) async { - bool isOnLoginTappedCalled = false; - - await $.pumpApp(BlocProvider.value( - value: signupCubit, - child: DerivSignupLayout( - signupPageLabel: signupPageLabel, - signupPageDescription: signupPageDescription, - onSocialAuthButtonPressed: (_) {}, - onSingupError: (_) {}, - onSingupEmailSent: (_) {}, - onSignupPressed: () {}, - onLoginTapped: () { - isOnLoginTappedCalled = true; - }, - ), - )); - - final loginButton = $(InkWell).$('Log in'); - - await $.scrollUntilVisible(finder: loginButton); - await $.tap(loginButton); - - expect(isOnLoginTappedCalled, true); - }); - - patrolTest('onSignupError is called upon signup error state', - (PatrolTester $) async { - bool isOnSignupErrorCalled = false; - - when(() => signupCubit.state).thenAnswer( - (_) => const DerivSignupErrorState('This is a test error message')); - - when(() => signupCubit.stream).thenAnswer( - (_) => Stream.fromIterable([const DerivSignupErrorState('')])); - - await $.pumpApp(BlocProvider.value( - value: signupCubit, - child: DerivSignupLayout( - signupPageLabel: signupPageLabel, - signupPageDescription: signupPageDescription, - onSocialAuthButtonPressed: (_) {}, - onSingupError: (_) => isOnSignupErrorCalled = true, - onSingupEmailSent: (_) {}, - onSignupPressed: () {}, - onLoginTapped: () {}, - ), - )); - - expect(isOnSignupErrorCalled, true); - }); - }); -} diff --git a/packages/deriv_banner/CHANGELOG.md b/packages/deriv_banner/CHANGELOG.md index 761894f12..4047204d7 100644 --- a/packages/deriv_banner/CHANGELOG.md +++ b/packages/deriv_banner/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.1+1 + + - **REFACTOR**(deriv_banner): updated kotlin version ([#399](https://github.com/regentmarkets/flutter-deriv-packages/issues/399)). ([9c19b5b4](https://github.com/regentmarkets/flutter-deriv-packages/commit/9c19b5b45aaa40897a7b9884794ab9cbb29fe4ff)) + ## 0.0.1 - initial release. diff --git a/packages/deriv_banner/android/build.gradle b/packages/deriv_banner/android/build.gradle index c7aee8bfb..ab64b958e 100644 --- a/packages/deriv_banner/android/build.gradle +++ b/packages/deriv_banner/android/build.gradle @@ -2,7 +2,7 @@ group 'com.deriv.app.deriv_banner' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.5.20' repositories { google() mavenCentral() diff --git a/packages/deriv_banner/example/pubspec.yaml b/packages/deriv_banner/example/pubspec.yaml index 3a0e4b147..94c5b38b7 100644 --- a/packages/deriv_banner/example/pubspec.yaml +++ b/packages/deriv_banner/example/pubspec.yaml @@ -24,7 +24,7 @@ dev_dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_lint - ref: dev + ref: deriv_lint-v1.0.0 flutter: uses-material-design: true diff --git a/packages/deriv_banner/pubspec.yaml b/packages/deriv_banner/pubspec.yaml index 53b074ff2..2bb0cf0f5 100644 --- a/packages/deriv_banner/pubspec.yaml +++ b/packages/deriv_banner/pubspec.yaml @@ -1,7 +1,7 @@ name: deriv_banner description: A new flutter plugin project. -version: 0.0.1 +version: 0.0.1+1 homepage: https://deriv.com/ publish_to: "none" @@ -22,7 +22,7 @@ dev_dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_lint - ref: dev + ref: deriv_lint-v1.0.0 flutter: plugin: diff --git a/packages/deriv_bloc_manager/README.md b/packages/deriv_bloc_manager/README.md index fecfc8c77..11b77964b 100644 --- a/packages/deriv_bloc_manager/README.md +++ b/packages/deriv_bloc_manager/README.md @@ -51,7 +51,7 @@ This section shows how to create a new bloc/cubit following the proposed archite ```dart abstract class BaseStateListener {} -abstract class AuthStateListener implements BaseStateListener { +abstract class DerivAuthStateListener implements BaseStateListener { void onLogin(Authorize authorizedAccount); void onLogout(); @@ -64,7 +64,7 @@ abstract class ConnectivityStateListener implements BaseStateListener { } ``` -2- Create a cubit for a feature, let’s call it `FeatureCubit`. The cubit class will implement both `AuthStateListener` and `ConnectivityStateListener` so it can expose the 4 methods in addition to any other feature-specific states. The type of the state `FeatureCubit` is managing in this example, is **Status** with initial value as `initial`; +2- Create a cubit for a feature, let’s call it `FeatureCubit`. The cubit class will implement both `DerivAuthStateListener` and `ConnectivityStateListener` so it can expose the 4 methods in addition to any other feature-specific states. The type of the state `FeatureCubit` is managing in this example, is **Status** with initial value as `initial`; ```dart enum Status { @@ -82,7 +82,7 @@ The `FeatureCubit` will expose the common/share `onConnect`, `onDisconnect`, `on ```dart import 'package:bloc/bloc.dart'; -class FeatureCubit extends Cubit implements ConnectivityStateListener, AuthStateListener { +class FeatureCubit extends Cubit implements ConnectivityStateListener, DerivAuthStateListener { FeatureCubit() : super(Status.initial); void loading() => emit(Status.loading); diff --git a/packages/deriv_cipher/.gitignore b/packages/deriv_cipher/.gitignore new file mode 100644 index 000000000..4bb6b70cb --- /dev/null +++ b/packages/deriv_cipher/.gitignore @@ -0,0 +1,32 @@ +# 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 +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ + + +**/chipher_helper.* diff --git a/packages/deriv_cipher/.metadata b/packages/deriv_cipher/.metadata new file mode 100644 index 000000000..c24d00d29 --- /dev/null +++ b/packages/deriv_cipher/.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: 5464c5bac742001448fe4fc0597be939379f88ea + channel: stable + +project_type: package diff --git a/packages/deriv_cipher/CHANGELOG.md b/packages/deriv_cipher/CHANGELOG.md new file mode 100644 index 000000000..6b5cb6889 --- /dev/null +++ b/packages/deriv_cipher/CHANGELOG.md @@ -0,0 +1,5 @@ +## 0.0.2 + + - **FEAT**(deriv_cipher): add cipher package ([#836](https://github.com/regentmarkets/flutter-deriv-packages/issues/836)). ([d64a6473](https://github.com/regentmarkets/flutter-deriv-packages/commit/d64a64736ad3dadb3fd4237e370f5a81f0c6f646)) + + diff --git a/packages/deriv_auth_ui/LICENSE b/packages/deriv_cipher/LICENSE similarity index 100% rename from packages/deriv_auth_ui/LICENSE rename to packages/deriv_cipher/LICENSE diff --git a/packages/deriv_cipher/README.md b/packages/deriv_cipher/README.md new file mode 100644 index 000000000..c04c4ab5f --- /dev/null +++ b/packages/deriv_cipher/README.md @@ -0,0 +1,35 @@ +# Deriv Cipher + +A package to encrypt and decrypt data using AES encryption. + +## Usage + +To use this package, add `deriv_cipher` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/packages-and-plugins/using-packages). + +```yaml +dependencies: + deriv_cipher: ^0.0.1 +``` + +### Import the package + +```dart +import 'package:deriv_cipher/deriv_cipher.dart'; +``` + +### Encrypt data + +```dart + +final cipher = Cipher(); +final encryptedData = cipher.encrypt('data', key>>); +``` + +### Decrypt data + +```dart + +final cipher = Cipher(); +final decryptedData = cipher.decrypt('encryptedData', key>>); +``` + diff --git a/packages/deriv_auth_ui/analysis_options.yaml b/packages/deriv_cipher/analysis_options.yaml similarity index 94% rename from packages/deriv_auth_ui/analysis_options.yaml rename to packages/deriv_cipher/analysis_options.yaml index 3d0c05ee4..bf3a92f06 100644 --- a/packages/deriv_auth_ui/analysis_options.yaml +++ b/packages/deriv_cipher/analysis_options.yaml @@ -1,10 +1,10 @@ analyzer: - exclude: - - lib/generated/** - - lib/src/core/helpers/assets.dart - - language: - strict-raw-types: true + errors: + todo: ignore + missing_required_param: warning + missing_return: warning + # https://github.com/flutter/flutter/pull/24528 + sdk_version_async_exported_from_core: ignore linter: rules: @@ -58,12 +58,11 @@ linter: - flutter_style_todos - hash_and_equals - implementation_imports - - iterable_contains_unrelated_type + - collection_methods_unrelated_type - join_return_with_assignment - library_names - library_prefixes # - lines_longer_than_80_chars - - list_remove_unrelated_type - no_adjacent_strings_in_list - no_duplicate_case_values - no_leading_underscores_for_local_identifiers diff --git a/packages/deriv_cipher/lib/base_cipher.dart b/packages/deriv_cipher/lib/base_cipher.dart new file mode 100644 index 000000000..95f9c0b76 --- /dev/null +++ b/packages/deriv_cipher/lib/base_cipher.dart @@ -0,0 +1,8 @@ +/// Base cipher class that encrypts and decrypts strings. +abstract class BaseCipher { + /// Encrypts a string using a key. + String encrypt({required String message, required List> key}); + + /// Decrypts a string using a key. + String decrypt({required String message, required List> key}); +} diff --git a/packages/deriv_cipher/lib/cipher.dart b/packages/deriv_cipher/lib/cipher.dart new file mode 100644 index 000000000..bf9c4d0d8 --- /dev/null +++ b/packages/deriv_cipher/lib/cipher.dart @@ -0,0 +1,40 @@ +import 'dart:convert'; + +import 'package:encrypt/encrypt.dart'; + +import 'base_cipher.dart'; +import 'helper.dart'; + +/// A cipher class that encrypts and decrypts strings. +class Cipher extends BaseCipher { + /// Returns a singleton instance of [Cipher]. + factory Cipher() => _instance; + + Cipher._(); + + static final Cipher _instance = Cipher._(); + + @override + String encrypt({required String message, required List> key}) { + assert(message.isNotEmpty); + assert(convert2DIntListToString(key).length == 32); + + final Key keyBytes = Key.fromUtf8(convert2DIntListToString(key)); + final IV iv = IV.fromLength(16); + final Encrypter encrypter = Encrypter(AES(keyBytes)); + + return encrypter.encrypt(message, iv: iv).base64; + } + + @override + String decrypt({required String message, required List> key}) { + assert(message.isNotEmpty); + assert(convert2DIntListToString(key).length == 32); + + final Key keyBytes = Key.fromUtf8(convert2DIntListToString(key)); + final IV iv = IV.fromLength(16); + final Encrypter encrypter = Encrypter(AES(keyBytes)); + + return encrypter.decrypt(Encrypted(base64.decode(message)), iv: iv); + } +} diff --git a/packages/deriv_cipher/lib/deriv_cipher.dart b/packages/deriv_cipher/lib/deriv_cipher.dart new file mode 100644 index 000000000..2a83ee5f3 --- /dev/null +++ b/packages/deriv_cipher/lib/deriv_cipher.dart @@ -0,0 +1,2 @@ +export 'base_cipher.dart'; +export 'cipher.dart'; diff --git a/packages/deriv_cipher/lib/helper.dart b/packages/deriv_cipher/lib/helper.dart new file mode 100644 index 000000000..1076f3d03 --- /dev/null +++ b/packages/deriv_cipher/lib/helper.dart @@ -0,0 +1,13 @@ +/// Convert a list of integers to a string. +String convertBytesToString(List input) => String.fromCharCodes(input); + +/// Convert a 2D list of integers to a string. +String convert2DIntListToString(List> list) { + final StringBuffer string = StringBuffer(); + + for (final List row in list) { + string.write(convertBytesToString(row)); + } + + return '$string'; +} diff --git a/packages/deriv_cipher/pubspec.yaml b/packages/deriv_cipher/pubspec.yaml new file mode 100644 index 000000000..4ce34ee54 --- /dev/null +++ b/packages/deriv_cipher/pubspec.yaml @@ -0,0 +1,24 @@ +name: deriv_cipher +description: Deriv cipher library. + +version: 0.0.2 + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: "3.10.2" + +dependencies: + flutter: + sdk: flutter + + encrypt: 5.0.1 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^1.0.0 + +flutter: + assets: + - test/.env.test + - test/.env.empty.test diff --git a/packages/deriv_cipher/test/.env.empty.test b/packages/deriv_cipher/test/.env.empty.test new file mode 100644 index 000000000..e69de29bb diff --git a/packages/deriv_cipher/test/.env.test b/packages/deriv_cipher/test/.env.test new file mode 100644 index 000000000..9d4672fb9 --- /dev/null +++ b/packages/deriv_cipher/test/.env.test @@ -0,0 +1,12 @@ +# Configuration file for the application. +STRING_VAR = hello world +INT_VAR = 123 +DOUBLE_VAR = 3.14 +BOOL_VAR = true + +# Exceptional test cases. +VAR_WITH_EQUALS = hello=world +VAR_WITH_HASH = hello#world + +# Encrypted test cases. +ENCRYPTED_VAR = dVyH3QjdHYcjcS2TQ1XenmDVvf5ViN8ZpSVEcjfFhsk= diff --git a/packages/deriv_cipher/test/cipher_test.dart b/packages/deriv_cipher/test/cipher_test.dart new file mode 100644 index 000000000..d976a1b96 --- /dev/null +++ b/packages/deriv_cipher/test/cipher_test.dart @@ -0,0 +1,113 @@ +import 'package:deriv_cipher/cipher.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('cipher tests =>', () { + test('cipher class should be singleton.', () { + final Cipher cipher1 = Cipher(); + final Cipher cipher2 = Cipher(); + + expect(cipher1, equals(cipher2)); + expect(cipher1.hashCode, equals(cipher2.hashCode)); + }); + + test('encrypt and decrypt should return the same string.', () { + const String message = 'secret message'; + const List> key = >[ + [74, 87, 100, 76, 122], + [118, 80, 99, 71, 74, 104], + [78, 57, 88, 54, 98], + [69, 107, 121, 102, 109], + [117, 89, 116, 53, 120], + [73, 48, 82, 111, 84, 97] + ]; + + final String encrypted = Cipher().encrypt(message: message, key: key); + final String decrypted = Cipher().decrypt(message: encrypted, key: key); + + expect(decrypted, equals(message)); + }); + + test( + 'encrypt and decrypt should produce different results with different keys.', + () { + const String message = 'secret message'; + const List> key1 = [ + [74, 87, 100, 76, 122, 118], + [80, 99, 71, 74, 104, 78], + [57, 88, 54, 98, 69, 107], + [121, 102, 109, 117, 89, 116], + [53, 120, 73, 48, 82, 111], + [84, 97] + ]; + const List> key2 = [ + [56, 104, 87, 110, 75, 112], + [85, 101, 55, 68, 107, 71], + [99, 89, 102, 106, 88, 57], + [122, 74, 98, 81, 115, 76], + [120, 50, 116, 86, 114, 84], + [52, 118] + ]; + + final String encrypted1 = Cipher().encrypt(message: message, key: key1); + final String encrypted2 = Cipher().encrypt(message: message, key: key2); + + expect(encrypted1, isNot(equals(encrypted2))); + + final String decrypted1 = + Cipher().decrypt(message: encrypted1, key: key1); + final String decrypted2 = + Cipher().decrypt(message: encrypted2, key: key2); + + expect(decrypted1, equals(message)); + expect(decrypted2, equals(message)); + expect(decrypted1, equals(decrypted2)); + }); + + test('encrypt and decrypt should throw assertion error with empty strings.', + () { + const String message = ''; + const List> key = >[ + [74, 87, 100, 76, 122], + [118, 80, 99, 71, 74, 104], + [78, 57, 88, 54, 98], + [69, 107, 121, 102, 109], + [117, 89, 116, 53, 120], + [73, 48, 82, 111, 84, 97] + ]; + expect( + () => Cipher().encrypt(message: message, key: key), + throwsAssertionError, + ); + + expect( + () => Cipher().decrypt(message: message, key: key), + throwsAssertionError, + ); + }); + + test( + 'encrypt and decrypt should throw assertion error with key != 32 character.', + () { + const String message = 'secret message'; + const List> key = [ + [74, 87, 100, 76, 122], + [118, 80, 99, 71, 74], + [104, 78, 57, 88, 54], + [98, 69, 53, 120, 73], + [48, 82, 111, 84, 97], + [] + ]; + + expect( + () => Cipher().encrypt(message: message, key: key), + throwsAssertionError, + ); + + expect( + () => Cipher().decrypt(message: message, key: key), + throwsAssertionError, + ); + }); + }); +} diff --git a/packages/deriv_date_range_picker/CHANGELOG.md b/packages/deriv_date_range_picker/CHANGELOG.md index 41cc7d819..496612e46 100644 --- a/packages/deriv_date_range_picker/CHANGELOG.md +++ b/packages/deriv_date_range_picker/CHANGELOG.md @@ -1,3 +1,51 @@ +## 0.0.1+12 + + - Update a dependency to the latest release. + +## 0.0.1+11 + + - **FIX**(deriv_date_range_picker): iupgrade intl version ([#859](https://github.com/regentmarkets/flutter-deriv-packages/issues/859)). ([c500b57d](https://github.com/regentmarkets/flutter-deriv-packages/commit/c500b57d1558b10d7d60603d8de36af88db3dfb7)) + +## 0.0.1+10 + + - Update a dependency to the latest release. + +## 0.0.1+9 + + - Update a dependency to the latest release. + +## 0.0.1+8 + + - Update a dependency to the latest release. + +## 0.0.1+7 + + - Update a dependency to the latest release. + +## 0.0.1+6 + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + +## 0.0.1+5 + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + +## 0.0.1+4 + + - Update a dependency to the latest release. + +## 0.0.1+3 + + - Update a dependency to the latest release. + +## 0.0.1+2 + + - **FIX**: change color to general. ([cac78e49](https://github.com/regentmarkets/flutter-deriv-packages/commit/cac78e49f1650fe1ba5f7698b97ce7a5adaa1308)) + +## 0.0.1+1 + + - Update a dependency to the latest release. + ## 0.0.1 * TODO: Describe initial release. diff --git a/packages/deriv_date_range_picker/lib/src/date_range_picker.dart b/packages/deriv_date_range_picker/lib/src/date_range_picker.dart index f52a31216..3d5923708 100644 --- a/packages/deriv_date_range_picker/lib/src/date_range_picker.dart +++ b/packages/deriv_date_range_picker/lib/src/date_range_picker.dart @@ -237,7 +237,7 @@ class _DerivDateRangePickerState extends State { context.localization!.labelSelectedDateRange, style: context.theme.textStyle( textStyle: TextStyles.overline, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ), diff --git a/packages/deriv_date_range_picker/lib/src/widgets/date_range_text_field.dart b/packages/deriv_date_range_picker/lib/src/widgets/date_range_text_field.dart index 0aa9a69bc..49440cca3 100644 --- a/packages/deriv_date_range_picker/lib/src/widgets/date_range_text_field.dart +++ b/packages/deriv_date_range_picker/lib/src/widgets/date_range_text_field.dart @@ -109,7 +109,7 @@ class _DateRangeTextFieldState extends State<_DateRangeTextField> { : TextInputAction.done, style: context.theme.textStyle( textStyle: TextStyles.subheading, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), decoration: InputDecoration( enabledBorder: OutlineInputBorder( diff --git a/packages/deriv_date_range_picker/lib/src/widgets/input_date_range.dart b/packages/deriv_date_range_picker/lib/src/widgets/input_date_range.dart index 9676a7bdf..50111d233 100644 --- a/packages/deriv_date_range_picker/lib/src/widgets/input_date_range.dart +++ b/packages/deriv_date_range_picker/lib/src/widgets/input_date_range.dart @@ -124,7 +124,7 @@ class _InputDateRangeState extends State { context.localization!.labelSelectedDateRange, style: context.theme.textStyle( textStyle: TextStyles.overline, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ), diff --git a/packages/deriv_date_range_picker/lib/src/widgets/month_item.dart b/packages/deriv_date_range_picker/lib/src/widgets/month_item.dart index d9e6f5338..07a9167d7 100644 --- a/packages/deriv_date_range_picker/lib/src/widgets/month_item.dart +++ b/packages/deriv_date_range_picker/lib/src/widgets/month_item.dart @@ -157,7 +157,7 @@ class _MonthItemState extends State<_MonthItem> { localizations.formatMonthYear(widget.displayedMonth), style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ), @@ -197,7 +197,7 @@ class _MonthItemState extends State<_MonthItem> { TextStyle itemStyle = context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ); BoxDecoration? decoration; diff --git a/packages/deriv_date_range_picker/pubspec.yaml b/packages/deriv_date_range_picker/pubspec.yaml index d24ac1883..c862b1c35 100644 --- a/packages/deriv_date_range_picker/pubspec.yaml +++ b/packages/deriv_date_range_picker/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_date_range_picker description: A new Flutter package project. -version: 0.0.1 +version: 0.0.1+12 publish_to: "none" # Remove this line if you wish to publish to pub.dev environment: @@ -17,9 +17,9 @@ dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_theme - ref: dev + ref: deriv_theme-v2.8.0 - intl: ^0.18.0 + intl: ^0.19.0 dev_dependencies: flutter_test: diff --git a/packages/deriv_dependency_injector/CHANGELOG.md b/packages/deriv_dependency_injector/CHANGELOG.md index effe43c82..4a61e482d 100644 --- a/packages/deriv_dependency_injector/CHANGELOG.md +++ b/packages/deriv_dependency_injector/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.0.2 + + - **REFACTOR**(deriv_dependency_injector): [MOBC-534] deprecate deriv dependency injector ([#347](https://github.com/regentmarkets/flutter-deriv-packages/issues/347)). ([92426f15](https://github.com/regentmarkets/flutter-deriv-packages/commit/92426f15eaa5caa529724590e006fd0f65d6800e)) + +## 1.0.1 + + - **REFACTOR**(deriv_dependency_injector): [MOBC-534] deprecate deriv dependency injector ([#347](https://github.com/regentmarkets/flutter-deriv-packages/issues/347)). ([92426f15](https://github.com/regentmarkets/flutter-deriv-packages/commit/92426f15eaa5caa529724590e006fd0f65d6800e)) + ## 1.0.0 - Initial version. diff --git a/packages/deriv_dependency_injector/lib/src/injector.dart b/packages/deriv_dependency_injector/lib/src/injector.dart index c7b5bb832..fbc98ff08 100644 --- a/packages/deriv_dependency_injector/lib/src/injector.dart +++ b/packages/deriv_dependency_injector/lib/src/injector.dart @@ -2,6 +2,10 @@ import 'package:deriv_dependency_injector/dependency_injector.dart'; const String _defaultKey = 'default'; +@Deprecated('This package is deprecated and will be removed in the future. ' + 'Please use the deriv dependency injector package from: ' + 'https://github.com/deriv-com/deriv-dependency-injector') + /// Injector class for dependency injection. class Injector { /// Get the instance of the named injector or create a new one if it doesn't exist. diff --git a/packages/deriv_dependency_injector/pubspec.yaml b/packages/deriv_dependency_injector/pubspec.yaml index 0101e4b74..03cdcebef 100644 --- a/packages/deriv_dependency_injector/pubspec.yaml +++ b/packages/deriv_dependency_injector/pubspec.yaml @@ -1,7 +1,7 @@ name: deriv_dependency_injector description: A package for handling dependency injection in Dart. -version: 1.0.0 +version: 1.0.2 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/packages/deriv_env/CHANGELOG.md b/packages/deriv_env/CHANGELOG.md index 761894f12..ed1952296 100644 --- a/packages/deriv_env/CHANGELOG.md +++ b/packages/deriv_env/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.0.1+2 + + - **REFACTOR**(deriv_env): make package independent of env file ([#318](https://github.com/regentmarkets/flutter-deriv-packages/issues/318)). ([a7242c81](https://github.com/regentmarkets/flutter-deriv-packages/commit/a7242c81b97fda70a622d7bbbb97fe997067117a)) + +## 0.0.1+1 + + - **REFACTOR**(deriv_env): make package independent of env file ([#318](https://github.com/regentmarkets/flutter-deriv-packages/issues/318)). ([a7242c81](https://github.com/regentmarkets/flutter-deriv-packages/commit/a7242c81b97fda70a622d7bbbb97fe997067117a)) + ## 0.0.1 - initial release. diff --git a/packages/deriv_env/README.md b/packages/deriv_env/README.md index c03ca600a..43862781b 100644 --- a/packages/deriv_env/README.md +++ b/packages/deriv_env/README.md @@ -31,12 +31,12 @@ You can check if the environment variable is initialized using the `isInitialize bool isInitialized = Env().isInitialized; ``` -### Loading environment variables +### Initializing the Env -Before using any environment variables, you need to load them from a file. The `load` method loads the variables from a file with the specified filename (default is `.env`). +Before using any environment variables, you need to intialize the `Env` class and pass it an instnace of the EnvLoader and the path to the file. The `initialize` method loads the variables from a file with the specified filename (default is `.env`). ```dart -await Env().load(); +await Env().initialize(EnvLoader(filePath: '.env')); ``` The load method expects the file to contain key-value pairs separated by an equals sign (`=`) and each pair separated by a newline character (`\n`). Blank lines and comments (lines starting with a `#`) are ignored. diff --git a/packages/deriv_env/analysis_options.yaml b/packages/deriv_env/analysis_options.yaml index 91390bcb1..bf3a92f06 100644 --- a/packages/deriv_env/analysis_options.yaml +++ b/packages/deriv_env/analysis_options.yaml @@ -58,12 +58,11 @@ linter: - flutter_style_todos - hash_and_equals - implementation_imports - - iterable_contains_unrelated_type + - collection_methods_unrelated_type - join_return_with_assignment - library_names - library_prefixes # - lines_longer_than_80_chars - - list_remove_unrelated_type - no_adjacent_strings_in_list - no_duplicate_case_values - no_leading_underscores_for_local_identifiers diff --git a/packages/deriv_env/lib/base_env.dart b/packages/deriv_env/lib/base_env_loader.dart similarity index 55% rename from packages/deriv_env/lib/base_env.dart rename to packages/deriv_env/lib/base_env_loader.dart index 24700d332..59afabd53 100644 --- a/packages/deriv_env/lib/base_env.dart +++ b/packages/deriv_env/lib/base_env_loader.dart @@ -1,16 +1,19 @@ /// Base class for retrieve environment variables providers. -abstract class BaseEnv { +abstract class BaseEnvLoader { /// Returns `true` if [Env] is initialized, otherwise `false`. bool get isInitialized; /// Returns all environment variables as a [Map]. Map get entries; - /// Loads environment variables from a `.env` file. - /// - /// If [filename] is not provided, it will default to `.env`. - Future load([String filename = '.env']); + /// Loads environment variables. + Future loadEnvironment(); /// Retrieves an environment variable value by key. - T get(String key, {T? defaultValue}); + T get( + String key, { + T? defaultValue, + T Function(String value)? parser, + String decryptionKey = '', + }); } diff --git a/packages/deriv_env/lib/deriv_env.dart b/packages/deriv_env/lib/deriv_env.dart new file mode 100644 index 000000000..27ef791bf --- /dev/null +++ b/packages/deriv_env/lib/deriv_env.dart @@ -0,0 +1,3 @@ +export 'env.dart'; +export 'env_loader.dart'; +export 'base_env_loader.dart'; diff --git a/packages/deriv_env/lib/env.dart b/packages/deriv_env/lib/env.dart index 2d6af2877..3383f64a4 100644 --- a/packages/deriv_env/lib/env.dart +++ b/packages/deriv_env/lib/env.dart @@ -1,114 +1,56 @@ -import 'package:flutter/services.dart'; - -import 'base_env.dart'; -import 'cipher.dart'; - -/// [Env] class is a singleton class that provides access to environment variables. -class Env extends BaseEnv { - /// Returns the singleton instance of [Env]. - factory Env() => _instance; - - Env._(); +import 'base_env_loader.dart'; +import 'env_loader.dart'; + +/// This class is used to load environment variables from a .env file +class Env { + /// The singleton instance of [EnvLoader]. + factory Env() { + _instance ??= Env._internal(); + return _instance!; + } - static final Env _instance = Env._(); + Env._internal(); - bool _isInitialized = false; + static Env? _instance; - final Map _entries = {}; + /// The environment variables provider. + BaseEnvLoader? _env; - @override - bool get isInitialized => _isInitialized; + /// The instance of [BaseEnv]. + BaseEnvLoader? get env => _env; - @override - Map get entries { - _checkInitialization(); + /// Returns `true` if [Env] is initialized, otherwise `false`. + bool get isInitialized => _env?.isInitialized ?? false; - return _entries; + /// Initializes [EnvLoader] with an instance of [BaseEnv]. + /// Loads environment variables from a `.env` file. + /// + /// If [filename] is not provided, it will default to `.env`. + Future initialize(BaseEnvLoader env) async { + _env = env; + return _env!.loadEnvironment(); } - @override - Future load([String filename = '.env']) async { - _entries.clear(); - - final List fileEntries = await _getEntriesFromFile(filename); - - for (final String entry in fileEntries) { - final List items = entry.split('='); - - if (items.length > 1) { - _entries[items.first.trim()] = items.sublist(1).join('=').trim(); - } - } - - _isInitialized = true; + /// Loads environment variables from a `.env` file. + @Deprecated('Use initialize() method instead.') + Future load([String filePath = '.env']) async { + _env ??= EnvLoader(filePath: filePath); + return _env!.loadEnvironment(); } - @override + /// Retrieves an environment variable value by key. T get( String key, { T? defaultValue, T Function(String value)? parser, String decryptionKey = '', - }) { - _checkInitialization(); - - if (!_entries.containsKey(key)) { - if (defaultValue == null) { - throw Exception('$runtimeType does not contain a value for key: $key.'); - } - - return defaultValue; - } - - final String value = decryptionKey.isEmpty - ? _entries[key] - : Cipher().decrypt(message: _entries[key], key: decryptionKey); - - if (parser != null) { - return parser(value); - } - - switch (T) { - case int: - return int.tryParse(value) as T; - case double: - return double.tryParse(value) as T; - case bool: - return (value.toLowerCase() == 'true') as T; - - default: - return value as T; - } - } - - Future> _getEntriesFromFile(String filename) async { - final String envFileContent = await rootBundle.loadString(filename); - - if (envFileContent.isEmpty) { - throw Exception('$runtimeType: $filename is empty.'); - } - - final List entries = []; - final List content = envFileContent.split('\n'); - - for (final String line in content) { - if (line.isEmpty || line.startsWith('#')) { - continue; - } - - entries.add(line); - } - - return entries; - } - - void _checkInitialization() { - if (_isInitialized) { - return; - } - - throw Exception( - '$runtimeType is not initialized, call load() method first.', - ); - } + }) => + isInitialized + ? _env!.get( + key, + defaultValue: defaultValue, + parser: parser, + decryptionKey: decryptionKey, + ) + : throw Exception('EnvLoader is not initialized.'); } diff --git a/packages/deriv_env/lib/env_loader.dart b/packages/deriv_env/lib/env_loader.dart new file mode 100644 index 000000000..c3a8d9412 --- /dev/null +++ b/packages/deriv_env/lib/env_loader.dart @@ -0,0 +1,119 @@ +import 'package:flutter/services.dart'; + +import 'base_env_loader.dart'; +import 'cipher.dart'; + +/// [Env] class is a singleton class that provides access to environment variables. +class EnvLoader extends BaseEnvLoader { + /// Creates a new instance of [EnvLoader]. + EnvLoader({required this.filePath}); + + /// The path to the file. + final String filePath; + + bool _isInitialized = false; + + final Map _entries = {}; + + @override + bool get isInitialized => _isInitialized; + + @override + Map get entries { + _checkInitialization(); + + return _entries; + } + + @override + Future loadEnvironment() async { + _entries.clear(); + + final List fileEntries = await _getEntriesFromFile(filePath); + + for (final String entry in fileEntries) { + final List items = entry.split('='); + + if (items.length > 1) { + _entries[items.first.trim()] = items.sublist(1).join('=').trim(); + } + } + + _isInitialized = true; + } + + @override + T get( + String key, { + T? defaultValue, + T Function(String value)? parser, + String decryptionKey = '', + }) { + _checkInitialization(); + + if (!_entries.containsKey(key)) { + if (defaultValue == null) { + throw Exception('$runtimeType does not contain a value for key: $key.'); + } + + return defaultValue; + } + + final String value = decryptionKey.isEmpty + ? _entries[key] + : Cipher().decrypt(message: _entries[key], key: decryptionKey); + + if (parser != null) { + return parser(value); + } + + switch (T) { + case int: + return int.tryParse(value) as T; + case double: + return double.tryParse(value) as T; + case bool: + if (value.toLowerCase() == 'true') { + return true as T; + } else if (value.toLowerCase() == 'false') { + return false as T; + } else { + throw FormatException('Invalid boolean value: $value'); + } + + default: + return value as T; + } + } + + Future> _getEntriesFromFile(String filename) async { + final String envFileContent = await rootBundle.loadString(filename); + + if (envFileContent.isEmpty) { + throw Exception('$runtimeType: $filename is empty.'); + } + + final List entries = []; + final List content = envFileContent.split('\n'); + + for (final String line in content) { + if (line.isEmpty || line.startsWith('#')) { + continue; + } + + entries.add(line); + } + + return entries; + } + + void _checkInitialization() { + if (_isInitialized) { + return; + } + + throw Exception( + '$runtimeType is not initialized, call load() method first.', + ); + } +} diff --git a/packages/deriv_env/pubspec.yaml b/packages/deriv_env/pubspec.yaml index ef12510bc..a8469b290 100644 --- a/packages/deriv_env/pubspec.yaml +++ b/packages/deriv_env/pubspec.yaml @@ -1,7 +1,7 @@ name: deriv_env description: A package to load and store environment variables. -version: 0.0.1 +version: 0.0.1+2 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/packages/deriv_env/test/cipher_test.dart b/packages/deriv_env/test/cipher_test.dart index 475facd4c..892633dba 100644 --- a/packages/deriv_env/test/cipher_test.dart +++ b/packages/deriv_env/test/cipher_test.dart @@ -1,6 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:deriv_env/Cipher.dart'; +import 'package:deriv_env/cipher.dart'; void main() { group('cipher tests =>', () { diff --git a/packages/deriv_env/test/env_test.dart b/packages/deriv_env/test/env_test.dart index 1221a7108..59b338fca 100644 --- a/packages/deriv_env/test/env_test.dart +++ b/packages/deriv_env/test/env_test.dart @@ -1,3 +1,4 @@ +import 'package:deriv_env/env_loader.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:deriv_env/env.dart'; @@ -5,6 +6,14 @@ import 'package:deriv_env/env.dart'; void main() { setUpAll(() => TestWidgetsFlutterBinding.ensureInitialized()); + const String stringKey = 'STRING_VAR'; + const String intKey = 'INT_VAR'; + const String doubleKey = 'DOUBLE_VAR'; + const String boolKey = 'BOOL_VAR'; + const String varWithEqualsKey = 'VAR_WITH_EQUALS'; + const String varWithHashKey = 'VAR_WITH_HASH'; + const String encryptedKey = 'ENCRYPTED_VAR'; + group('env class test =>', () { test('get() method should throw exception if env is not initialized.', () async { @@ -14,27 +23,27 @@ void main() { test('load() method should populate env map.', () async { expect(Env().isInitialized, isFalse); - await Env().load('test/.env.test'); + await Env().initialize(EnvLoader(filePath: 'test/.env.test')); expect(Env().isInitialized, isTrue); - expect(Env().entries.length, 7); + expect(Env().env!.entries.length, 7); - expect(Env().entries['STRING_VAR'], 'hello world'); - expect(Env().entries['INT_VAR'], '123'); - expect(Env().entries['DOUBLE_VAR'], '3.14'); - expect(Env().entries['BOOL_VAR'], 'true'); - expect(Env().entries['VAR_WITH_EQUALS'], 'hello=world'); - expect(Env().entries['VAR_WITH_HASH'], 'hello#world'); + expect(Env().env!.entries[stringKey], 'hello world'); + expect(Env().env!.entries[intKey], '123'); + expect(Env().env!.entries[doubleKey], '3.14'); + expect(Env().env!.entries[boolKey], 'true'); + expect(Env().env!.entries[varWithEqualsKey], 'hello=world'); + expect(Env().env!.entries[varWithHashKey], 'hello#world'); expect( - Env().entries['ENCRYPTED_VAR'], + Env().env!.entries[encryptedKey], 'dVyH3QjdHYcjcS2TQ1XenmDVvf5ViN8ZpSVEcjfFhsk=', ); }); test('get() method should return default value if key is not found.', () async { - await Env().load('test/.env.test'); + await Env().initialize(EnvLoader(filePath: 'test/.env.test')); expect( Env().get('INVALID_KEY', defaultValue: 'default'), @@ -43,31 +52,31 @@ void main() { }); test('get() method should parse value as int.', () async { - await Env().load('test/.env.test'); + await Env().initialize(EnvLoader(filePath: 'test/.env.test')); - expect(Env().get('INT_VAR'), 123); + expect(Env().get(intKey), 123); }); test('get() method should parse value as double.', () async { - await Env().load('test/.env.test'); + await Env().initialize(EnvLoader(filePath: 'test/.env.test')); - expect(Env().get('DOUBLE_VAR'), 3.14); + expect(Env().get(doubleKey), 3.14); }); test('get() method should parse value as bool.', () async { - await Env().load('test/.env.test'); + await Env().initialize(EnvLoader(filePath: 'test/.env.test')); - expect(Env().get('BOOL_VAR'), isTrue); + expect(Env().get(boolKey), isTrue); }); test( 'get() method should parse value with a parser factory if it is provided.', () async { - await Env().load('test/.env.test'); + await Env().initialize(EnvLoader(filePath: 'test/.env.test')); expect( Env().get( - 'STRING_VAR', + stringKey, parser: (String value) => value.toUpperCase(), ), 'HELLO WORLD', @@ -75,7 +84,7 @@ void main() { expect( Env().get( - 'INT_VAR', + intKey, parser: (String value) => int.parse(value) * 2, ), 246, @@ -83,7 +92,7 @@ void main() { expect( Env().get( - 'DOUBLE_VAR', + doubleKey, parser: (String value) => double.parse(value) * 2, ), 6.28, @@ -91,7 +100,7 @@ void main() { expect( Env().get( - 'DOUBLE_VAR', + doubleKey, parser: (String value) => double.parse(value) > 3.14, ), false, @@ -100,18 +109,18 @@ void main() { test('check handling variables with special characters like `#` and `=`.', () async { - await Env().load('test/.env.test'); + await Env().initialize(EnvLoader(filePath: 'test/.env.test')); - expect(Env().entries['VAR_WITH_EQUALS'], 'hello=world'); - expect(Env().entries['VAR_WITH_HASH'], 'hello#world'); + expect(Env().env!.entries[varWithEqualsKey], 'hello=world'); + expect(Env().env!.entries[varWithHashKey], 'hello#world'); }); test('handle encrypted variable.', () async { - await Env().load('test/.env.test'); + await Env().initialize(EnvLoader(filePath: 'test/.env.test')); expect( Env().get( - 'ENCRYPTED_VAR', + encryptedKey, decryptionKey: 'TbKjMndW1L8vczgGQfPo2IyUxh6XAEay', ), 'ecnrypted message', @@ -119,7 +128,9 @@ void main() { }); test('throws an exception if file is empty.', () async { - expect(() => Env().load('test/.env.empty.test'), throwsException); + expect( + () => Env().initialize(EnvLoader(filePath: 'test/.env.empty.test')), + throwsException); }); test('throws an exception if env is not initialized.', () async { @@ -127,7 +138,7 @@ void main() { }); test('throws an exception if key is not found.', () async { - await Env().load('test/.env.test'); + await Env().initialize(EnvLoader(filePath: 'test/.env.test')); expect(() => Env().get('INVALID_KEY'), throwsException); }); @@ -135,7 +146,7 @@ void main() { test( 'does not throw an exception if key is not found and a default value is provided.', () async { - await Env().load('test/.env.test'); + await Env().initialize(EnvLoader(filePath: 'test/.env.test')); expect(() => Env().get('INVALID_KEY', defaultValue: 42), returnsNormally); }); diff --git a/packages/deriv_expandable_bottom_sheet/CHANGELOG.md b/packages/deriv_expandable_bottom_sheet/CHANGELOG.md index 41cc7d819..6f4f34ddf 100644 --- a/packages/deriv_expandable_bottom_sheet/CHANGELOG.md +++ b/packages/deriv_expandable_bottom_sheet/CHANGELOG.md @@ -1,3 +1,47 @@ +## 0.0.2 + + - **FEAT**(analytics): Add indicator events ([#846](https://github.com/regentmarkets/flutter-deriv-packages/issues/846)). ([1d30ddc9](https://github.com/regentmarkets/flutter-deriv-packages/commit/1d30ddc9c7510e280cc90fc7e1308b945d5758ed)) + +## 0.0.1+10 + + - Update a dependency to the latest release. + +## 0.0.1+9 + + - Update a dependency to the latest release. + +## 0.0.1+8 + + - Update a dependency to the latest release. + +## 0.0.1+7 + + - Update a dependency to the latest release. + +## 0.0.1+6 + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + +## 0.0.1+5 + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + +## 0.0.1+4 + + - Update a dependency to the latest release. + +## 0.0.1+3 + + - Update a dependency to the latest release. + +## 0.0.1+2 + + - Update a dependency to the latest release. + +## 0.0.1+1 + + - Update a dependency to the latest release. + ## 0.0.1 * TODO: Describe initial release. diff --git a/packages/deriv_expandable_bottom_sheet/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/packages/deriv_expandable_bottom_sheet/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java index d007606a4..539ab022f 100644 --- a/packages/deriv_expandable_bottom_sheet/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java +++ b/packages/deriv_expandable_bottom_sheet/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java @@ -1,23 +1,19 @@ package io.flutter.plugins; -import io.flutter.plugin.common.PluginRegistry; +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import io.flutter.Log; + +import io.flutter.embedding.engine.FlutterEngine; /** * Generated file. Do not edit. + * This file is generated by the Flutter tool based on the + * plugins that support the Android platform. */ +@Keep public final class GeneratedPluginRegistrant { - public static void registerWith(PluginRegistry registry) { - if (alreadyRegisteredWith(registry)) { - return; - } - } - - private static boolean alreadyRegisteredWith(PluginRegistry registry) { - final String key = GeneratedPluginRegistrant.class.getCanonicalName(); - if (registry.hasPlugin(key)) { - return true; - } - registry.registrarFor(key); - return false; + private static final String TAG = "GeneratedPluginRegistrant"; + public static void registerWith(@NonNull FlutterEngine flutterEngine) { } } diff --git a/packages/deriv_expandable_bottom_sheet/pubspec.yaml b/packages/deriv_expandable_bottom_sheet/pubspec.yaml index cfa726683..2a5a1235c 100644 --- a/packages/deriv_expandable_bottom_sheet/pubspec.yaml +++ b/packages/deriv_expandable_bottom_sheet/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_expandable_bottom_sheet description: A new Flutter package project. -version: 0.0.1 +version: 0.0.2 publish_to: none environment: @@ -15,7 +15,7 @@ dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_theme - ref: dev + ref: deriv_theme-v2.8.0 dev_dependencies: flutter_test: diff --git a/packages/deriv_auth_ui/.gitignore b/packages/deriv_feature_flag/.gitignore similarity index 100% rename from packages/deriv_auth_ui/.gitignore rename to packages/deriv_feature_flag/.gitignore diff --git a/packages/deriv_auth_ui/.metadata b/packages/deriv_feature_flag/.metadata similarity index 100% rename from packages/deriv_auth_ui/.metadata rename to packages/deriv_feature_flag/.metadata diff --git a/packages/deriv_feature_flag/CHANGELOG.md b/packages/deriv_feature_flag/CHANGELOG.md new file mode 100644 index 000000000..20a2699a5 --- /dev/null +++ b/packages/deriv_feature_flag/CHANGELOG.md @@ -0,0 +1,22 @@ +## 0.1.2 + + - **FEAT**(deriv_feature_flag): set attributes ([#874](https://github.com/regentmarkets/flutter-deriv-packages/issues/874)). ([bb92d976](https://github.com/regentmarkets/flutter-deriv-packages/commit/bb92d9764408613905da8451ce97689db09c6991)) + +## 0.1.1 + + - **FEAT**(deriv_feature_flag): Add a methods to retrieve values for feature flag types other than boolean ([#513](https://github.com/regentmarkets/flutter-deriv-packages/issues/513)). ([dd30f341](https://github.com/regentmarkets/flutter-deriv-packages/commit/dd30f3419b8d0ca887b4cfc58280db4bc4738076)) + +## 0.1.0+1 + + - **FIX**(deriv_feature_flag): remove env dependancy ([#477](https://github.com/regentmarkets/flutter-deriv-packages/issues/477)). ([c62b20eb](https://github.com/regentmarkets/flutter-deriv-packages/commit/c62b20eb88cf1397ecf4437a7854ff19187d7662)) + +## 0.1.0 + +> Note: This release has breaking changes. + + - **DOCS**(deriv_feature_flag): update readme. ([9c86a182](https://github.com/regentmarkets/flutter-deriv-packages/commit/9c86a18271df410161099e0d7ffa3002b17fd3ca)) + - **BREAKING** **REFACTOR**(deriv_feature_flag): remove static from initialize method. ([6c9c50e5](https://github.com/regentmarkets/flutter-deriv-packages/commit/6c9c50e550d1cf32723d1beb8238c74a37444c2d)) + +## 0.0.1 + +- Provide basic feature flag functionaliy for apps. diff --git a/packages/deriv_feature_flag/LICENSE b/packages/deriv_feature_flag/LICENSE new file mode 100644 index 000000000..ba75c69f7 --- /dev/null +++ b/packages/deriv_feature_flag/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/packages/deriv_feature_flag/README.md b/packages/deriv_feature_flag/README.md new file mode 100644 index 000000000..61061bc6e --- /dev/null +++ b/packages/deriv_feature_flag/README.md @@ -0,0 +1,49 @@ +## Deriv Feature Flag + +This package provides feature flag functionality for mobile applications. + +## Getting started + +### To use the lints, add a dependency in your `pubspec.yaml`: + +```yaml +dependencies: + deriv_feature_flag: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_feature_flag + ref: ref +``` + +### Configuring your .env file + +Following keys should be added to your .env file: + +```yaml +GROWTHBOOK_API_KEY=your_api_key +GROWTHBOOK_HOST_URL=your_host_url +``` + +### Initialize and provide your feature flags key,value pairs: + +```dart +final config = FeatureFlagConfig( + features: { + 'sample_feature_key': true, + }, +); +await DerivFeatureFlag().initialize(config); +``` + +Then use it in your project: + +```dart +if(DerivFeatureFlag().isFeatureOn('sample_feature_key',defaultValue: false)){ + /// add the functionality that should be done if feature flag is on. +} + +``` + +## Additional information + +TODO: At this phase we only use GrowthBook for feature flagging. To find more information please check GrowthBook [documentation](https://docs.growthbook.io/lib/flutter) diff --git a/packages/deriv_feature_flag/analysis_options.yaml b/packages/deriv_feature_flag/analysis_options.yaml new file mode 100644 index 000000000..a5744c1cf --- /dev/null +++ b/packages/deriv_feature_flag/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/deriv_feature_flag/lib/deriv_feature_flag.dart b/packages/deriv_feature_flag/lib/deriv_feature_flag.dart new file mode 100644 index 000000000..8b3ea5bd2 --- /dev/null +++ b/packages/deriv_feature_flag/lib/deriv_feature_flag.dart @@ -0,0 +1,2 @@ +export 'package:deriv_feature_flag/feature_flag/feature_flag_config.dart'; +export 'package:deriv_feature_flag/deriv_feature_flag.dart'; diff --git a/packages/deriv_feature_flag/lib/feature_flag/deriv_feature_flag.dart b/packages/deriv_feature_flag/lib/feature_flag/deriv_feature_flag.dart new file mode 100644 index 000000000..f6bf9356b --- /dev/null +++ b/packages/deriv_feature_flag/lib/feature_flag/deriv_feature_flag.dart @@ -0,0 +1,77 @@ +import 'package:deriv_feature_flag/feature_flag/feature_flag_config.dart'; +import 'package:deriv_feature_flag/feature_flag/feature_flag_repository.dart'; +import 'package:deriv_feature_flag/growthbook/deriv_growth_book.dart'; +import 'package:flutter/material.dart'; + +class DerivFeatureFlag { + /// Initializes the FeatureFlag service for the whole app. + Future initialize(FeatureFlagConfig featureFlagConfig) async { + final DerivGrowthBook derivGrowthBook = DerivGrowthBook( + featureFlagConfig: featureFlagConfig, + ); + // TODO(Ramin): FeatureFlagRepository should be non-singleton or another way + // to be able to send different attributes during one app run session. + // Since sending attributes happens in the initialization and if during one + // app run session we might want to send different attributes (e.g. country) + await FeatureFlagRepository.getInstance() + .setup(derivGrowthBook: derivGrowthBook); + } + + /// Only for testing purposes. + @visibleForTesting + static Future initializeTest(DerivGrowthBook derivGrowthBook) async { + await FeatureFlagRepository.getInstance() + .setup(derivGrowthBook: derivGrowthBook); + } + + /// only for testing purposes. + @visibleForTesting + static FeatureFlagRepository get featureFlagRepository => + FeatureFlagRepository.getInstance(); + + /// To check if Feature Flag is on or off. + bool isFeatureOn(String key, {bool defaultValue = false}) => + FeatureFlagRepository.getInstance().isFeatureOn( + key, + defaultValue: defaultValue, + ); + + /// Returns the value of a feature flag. + /// + /// The returned value, depending on the feature flag, can be a boolean, + /// string, num, or a Map. + dynamic getFeatureFlagValue(String key, {dynamic defaultValue = false}) => + FeatureFlagRepository.getInstance().getFeatureValue( + key, + defaultValue: defaultValue, + ); + + /// Returns the value of a boolean feature flag. + bool getBoolFeatureValue(String key, {bool defaultValue = false}) => + (FeatureFlagRepository.getInstance().getFeatureValue(key) as bool?) ?? + defaultValue; + + /// Returns the value of a String feature flag. + String getStringFeatureValue(String key, {String defaultValue = ''}) => + (FeatureFlagRepository.getInstance().getFeatureValue(key) as String?) ?? + defaultValue; + + /// Returns the value of a num feature flag. + num getNumFeatureValue(String key, {num defaultValue = 0}) => + (FeatureFlagRepository.getInstance().getFeatureValue(key) as num?) ?? + defaultValue; + + /// Set attributes to target specific user. + void setAttributes(Map attributes) { + FeatureFlagRepository.getInstance().setAttributes(attributes); + } + + /// only for testing purposes. + @visibleForTesting + bool isFeatureOnTest(FeatureFlagRepository featureFlagRepository, String key, + {bool defaultValue = false}) => + featureFlagRepository.isFeatureOn( + key, + defaultValue: defaultValue, + ); +} diff --git a/packages/deriv_feature_flag/lib/feature_flag/feature_flag_config.dart b/packages/deriv_feature_flag/lib/feature_flag/feature_flag_config.dart new file mode 100644 index 000000000..dded2b4f4 --- /dev/null +++ b/packages/deriv_feature_flag/lib/feature_flag/feature_flag_config.dart @@ -0,0 +1,33 @@ +class FeatureFlagConfig { + /// set of client side applications features. + final Map features; + + /// Map of user attributes that are used to assign variations. + final Map attributes; + + /// If true, random assignment is disabled and only explicitly forced + /// variations are used. + final bool qaMode; + + /// Switch to globally disable all experiments. Default true + final bool enable; + + /// Force specific experiments to always assign a specific variation (QA). + final Map forcedVariations; + + /// Feature flag service host url + final String hostUrl; + + /// Feature flag service client key + final String clientKey; + + FeatureFlagConfig({ + this.attributes = const {}, + this.qaMode = false, + this.enable = true, + this.forcedVariations = const {}, + this.features = const {}, + required this.hostUrl, + required this.clientKey, + }); +} diff --git a/packages/deriv_feature_flag/lib/feature_flag/feature_flag_repository.dart b/packages/deriv_feature_flag/lib/feature_flag/feature_flag_repository.dart new file mode 100644 index 000000000..ef8214873 --- /dev/null +++ b/packages/deriv_feature_flag/lib/feature_flag/feature_flag_repository.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +import 'package:deriv_feature_flag/growthbook/deriv_growth_book.dart'; +import 'package:growthbook_sdk_flutter/growthbook_sdk_flutter.dart'; + +/// Service that provides feature flag functionality for us. +class FeatureFlagRepository { + /// Getter for the instance + factory FeatureFlagRepository.getInstance() => _instance; + + // Private constructor + FeatureFlagRepository._(); + + // Singleton instance + static final FeatureFlagRepository _instance = FeatureFlagRepository._(); + + // Growth Book SDK instance. + late final GrowthBookSDK _growthBookSDK; + + /// initialize the GrowthBook sdk instance. + Future setup({required DerivGrowthBook derivGrowthBook}) async { + _growthBookSDK = await derivGrowthBook.initializeSDK(); + } + + /// get the feature flags value from the sdk. + bool isFeatureOn(String key, {bool defaultValue = false}) => + _growthBookSDK.feature(key).on ?? defaultValue; + + /// Returns the value of a feature flag. + /// + /// The returned value, depending on the feature flag, can be a boolean, + /// string, num, or a Map. + dynamic getFeatureValue(String key, {dynamic defaultValue = false}) => + _growthBookSDK.feature(key).value ?? defaultValue; + + /// Set attributes to target specific user. + void setAttributes(Map attributes) { + _growthBookSDK.setAttributes(attributes); + } + + @visibleForTesting + GrowthBookSDK get growthBookSDK => _growthBookSDK; +} diff --git a/packages/deriv_feature_flag/lib/growthbook/base_growth_book.dart b/packages/deriv_feature_flag/lib/growthbook/base_growth_book.dart new file mode 100644 index 000000000..235153f13 --- /dev/null +++ b/packages/deriv_feature_flag/lib/growthbook/base_growth_book.dart @@ -0,0 +1,10 @@ +import 'package:growthbook_sdk_flutter/growthbook_sdk_flutter.dart'; + +/// The class that handles GrowthBook SDK functionalities. +abstract class BaseGrowthBook { + /// Class constructor that creates an instance of Growth Book SDK builder. + BaseGrowthBook(); + + /// initialize the sdk. + Future initializeSDK(); +} diff --git a/packages/deriv_feature_flag/lib/growthbook/deriv_growth_book.dart b/packages/deriv_feature_flag/lib/growthbook/deriv_growth_book.dart new file mode 100644 index 000000000..4e30b4115 --- /dev/null +++ b/packages/deriv_feature_flag/lib/growthbook/deriv_growth_book.dart @@ -0,0 +1,27 @@ +import 'package:deriv_feature_flag/feature_flag/feature_flag_config.dart'; +import 'package:deriv_feature_flag/growthbook/base_growth_book.dart'; +import 'package:deriv_feature_flag/growthbook/growth_book_config.dart'; +import 'package:growthbook_sdk_flutter/growthbook_sdk_flutter.dart'; + +/// This class contains the reference data for +class DerivGrowthBook extends BaseGrowthBook implements GrowthBookConfig { + /// Growth Book SDK builder. + late final GBSDKBuilderApp _gbsdkBuilderApp; + + DerivGrowthBook({required FeatureFlagConfig featureFlagConfig}) { + _gbsdkBuilderApp = GBSDKBuilderApp( + hostURL: featureFlagConfig.hostUrl, + apiKey: featureFlagConfig.clientKey, + attributes: featureFlagConfig.attributes, + enable: featureFlagConfig.enable, + qaMode: featureFlagConfig.qaMode, + forcedVariations: featureFlagConfig.forcedVariations, + growthBookTrackingCallBack: GrowthBookConfig.trackingCallBack, + gbFeatures: GrowthBookConfig.gbFeatures(featureFlagConfig.features), + onInitializationFailure: GrowthBookConfig.onInitializationFailure, + ); + } + + @override + Future initializeSDK() async => _gbsdkBuilderApp.initialize(); +} diff --git a/packages/deriv_feature_flag/lib/growthbook/growth_book_config.dart b/packages/deriv_feature_flag/lib/growthbook/growth_book_config.dart new file mode 100644 index 000000000..cde52aa29 --- /dev/null +++ b/packages/deriv_feature_flag/lib/growthbook/growth_book_config.dart @@ -0,0 +1,36 @@ +import 'dart:developer' as logger; +import 'package:growthbook_sdk_flutter/growthbook_sdk_flutter.dart'; + +/// A class that contains the whole GrowthBook config data. +class GrowthBookConfig { + /// Convert client side FeatureFlags to GrowthBook FeatureFlags. + static Map gbFeatures(Map features) { + if (features.isEmpty) { + return const {}; + } + + final gbFeatures = {}; + + for (final entry in features.entries) { + gbFeatures[entry.key] = GBFeature(defaultValue: entry.value); + } + + return gbFeatures; + } + + /// Provide proper log or service initialization failure. + static void onInitializationFailure(GBError? gbError) => + logger.log('GrowthBook initilization failed: ${gbError?.error}'); + + /// Provide appropriate log after setting up the service. + static void trackingCallBack( + GBExperiment experiment, + GBExperimentResult result, + ) { + final key = experiment.key ?? ''; + final id = result.variationID?.toString() ?? ''; + + logger.log('Experiment Id: $key'); + logger.log('Variation Id: $id'); + } +} diff --git a/packages/deriv_feature_flag/pubspec.yaml b/packages/deriv_feature_flag/pubspec.yaml new file mode 100644 index 000000000..50e25a1ba --- /dev/null +++ b/packages/deriv_feature_flag/pubspec.yaml @@ -0,0 +1,18 @@ +name: deriv_feature_flag +description: wrapper to handle feature flag for deriv applications +version: 0.1.2 +publish_to: none + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + growthbook_sdk_flutter: ^3.0.0+0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + mocktail: ^1.0.0 diff --git a/packages/deriv_feature_flag/test/feature_flag/deriv_feature_flag_test.dart b/packages/deriv_feature_flag/test/feature_flag/deriv_feature_flag_test.dart new file mode 100644 index 000000000..042e343a5 --- /dev/null +++ b/packages/deriv_feature_flag/test/feature_flag/deriv_feature_flag_test.dart @@ -0,0 +1,90 @@ +import 'package:deriv_feature_flag/deriv_feature_flag.dart'; +import 'package:deriv_feature_flag/feature_flag/deriv_feature_flag.dart'; +import 'package:deriv_feature_flag/feature_flag/feature_flag_repository.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:growthbook_sdk_flutter/growthbook_sdk_flutter.dart'; +import 'package:mocktail/mocktail.dart'; + +import 'mock_classes.dart'; + +void main() async { + final FeatureFlagConfig featureFlagConfig = FeatureFlagConfig( + hostUrl: '', + clientKey: '', + features: { + Features.isSocialAuthEnabled.key: GBFeature(defaultValue: true), + }, + ); + + final mockDerivGrowthBook = MockDerivGrowthBook( + featureFlagConfig: featureFlagConfig, + ); + + final mockFeatureFlagRepository = MockFeatureFlagRepository(); + await DerivFeatureFlag.initializeTest(mockDerivGrowthBook); + + setUp(() async { + WidgetsFlutterBinding.ensureInitialized(); + }); + + group('FeatureFlagService', () { + test('setup should set the repository properly.', () async { + // Act & Assert + expect( + DerivFeatureFlag.featureFlagRepository, isA()); + }); + + test('isFeatureOn should be delegated to repository properly.', () { + // when the feature flag is true. + when(() => mockFeatureFlagRepository.isFeatureOn( + Features.isSocialAuthEnabled.key, + defaultValue: true)).thenReturn(true); + + // we get the value from the package. + final result = DerivFeatureFlag().isFeatureOnTest( + mockFeatureFlagRepository, + Features.isSocialAuthEnabled.key, + defaultValue: true, + ); + + // The data should match. + expect(result, equals(true)); + }); + test('isFeatureOn function is only called once in each interaction.', () { + // when the feature flag is true. + when(() => DerivFeatureFlag().isFeatureOnTest( + mockFeatureFlagRepository, + Features.isSocialAuthEnabled.key, + defaultValue: true, + )).thenReturn(true); + + // verify that the method is only called once. + verify(() => mockFeatureFlagRepository.isFeatureOn( + Features.isSocialAuthEnabled.key, + defaultValue: true, + )).called(1); + }); + + test( + 'when isFeatureOn is called for an undefined featureflag, package delivers it properly.', + () { + const String key = 'nonexistent_feature'; + // when the feature flag is undefined with false as its default value + when(() => mockFeatureFlagRepository.isFeatureOn( + key, + defaultValue: true, + )).thenReturn(false); + + // we get the value from the packag. + final result = DerivFeatureFlag().isFeatureOnTest( + mockFeatureFlagRepository, + key, + defaultValue: true, + ); + + // The package deliver the correect answer. + expect(result, equals(false)); + }); + }); +} diff --git a/packages/deriv_feature_flag/test/feature_flag/feature_flag_repository_test.dart b/packages/deriv_feature_flag/test/feature_flag/feature_flag_repository_test.dart new file mode 100644 index 000000000..7380dfc2e --- /dev/null +++ b/packages/deriv_feature_flag/test/feature_flag/feature_flag_repository_test.dart @@ -0,0 +1,66 @@ +import 'package:deriv_feature_flag/feature_flag/feature_flag_config.dart'; +import 'package:deriv_feature_flag/feature_flag/feature_flag_repository.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:growthbook_sdk_flutter/growthbook_sdk_flutter.dart'; +import 'package:mocktail/mocktail.dart'; + +import 'mock_classes.dart'; + +void main() { + final mockGrowthBookSDK = MockGrowthBookSDK(); + final FeatureFlagConfig featureFlagConfig = FeatureFlagConfig( + hostUrl: '', + clientKey: '', + features: { + Features.isSocialAuthEnabled.key: GBFeature(defaultValue: true), + }, + ); + final MockDerivGrowthBook mockDerivGrowthBook = MockDerivGrowthBook( + featureFlagConfig: featureFlagConfig, + ); + final featureFlagRepository = FeatureFlagRepository.getInstance(); + + setUp(() async { + WidgetsFlutterBinding.ensureInitialized(); + }); + + group('FeatureFlagRepository:', () { + test('initializes GrowthBook SDK correctly.', () async { + // setup the repository. + await featureFlagRepository.setup( + derivGrowthBook: mockDerivGrowthBook, + ); + // expects to return an instance if the sdk. + expect(featureFlagRepository.growthBookSDK, isA()); + }); + + test('returns false value when feature is not found.', () async { + // Assuming that the feature is not found in the SDK + final result = featureFlagRepository.isFeatureOn('nonexistent_feature', + defaultValue: true); + + // data should match. + expect(result, equals(false)); + }); + + test('returns feature value from SDK when feature is found.', () async { + // Assuming that the feature 'isSocialAuthEnabled' is found in the SDK + // and is set to true. + when(() => + mockGrowthBookSDK.feature(Features.isSocialAuthEnabled.key).value ?? + true).thenReturn(true); + + /// get the value from the repository. + final result = featureFlagRepository.isFeatureOn( + Features.isSocialAuthEnabled.key, + defaultValue: true, + ); + + /// data should match. + expect(result, equals(true)); + }); + + // TODO(Ramin): getFeatureValue test. need to first fix test setup since mock response using `when` here is not working correctly right now. + }); +} diff --git a/packages/deriv_feature_flag/test/feature_flag/mock_classes.dart b/packages/deriv_feature_flag/test/feature_flag/mock_classes.dart new file mode 100644 index 000000000..1ded34fbf --- /dev/null +++ b/packages/deriv_feature_flag/test/feature_flag/mock_classes.dart @@ -0,0 +1,47 @@ +import 'package:deriv_feature_flag/feature_flag/feature_flag_config.dart'; +import 'package:deriv_feature_flag/feature_flag/feature_flag_repository.dart'; +import 'package:deriv_feature_flag/growthbook/deriv_growth_book.dart'; +import 'package:deriv_feature_flag/growthbook/growth_book_config.dart'; +import 'package:growthbook_sdk_flutter/growthbook_sdk_flutter.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockDerivGrowthBook extends Mock implements DerivGrowthBook { + late final GBSDKBuilderApp _gbsdkBuilderApp; + + MockDerivGrowthBook({required FeatureFlagConfig featureFlagConfig}) { + _gbsdkBuilderApp = GBSDKBuilderApp( + hostURL: MockGrowthBookConfig.gbHostUrl, + apiKey: MockGrowthBookConfig.gbClientKey, + growthBookTrackingCallBack: GrowthBookConfig.trackingCallBack, + gbFeatures: GrowthBookConfig.gbFeatures(featureFlagConfig.features), + onInitializationFailure: GrowthBookConfig.onInitializationFailure, + ); + } + + @override + Future initializeSDK() => _gbsdkBuilderApp.initialize(); +} + +class MockGrowthBookConfig extends Mock implements GrowthBookConfig { + static String get gbClientKey => 'YOUR_GROWTHBOOK_HOST_URL'; + + /// Determine the Growth Book Host URL. + static String get gbHostUrl => 'https://example.growthbook.io/'; +} + +class MockGrowthBookSDK extends Mock implements GrowthBookSDK {} + +class MockFeatureFlagRepository extends Mock implements FeatureFlagRepository {} + +enum Features { + /// To Turn Social Auth on and off. + isSocialAuthEnabled('issocialauthenabled'); + + const Features(this._featureKey); + + /// name of the Feature Flag in GrowthBook. + final String _featureKey; + + /// getting the Feature Flag key. + String get key => _featureKey; +} diff --git a/packages/deriv_grouped_listview/CHANGELOG.md b/packages/deriv_grouped_listview/CHANGELOG.md index 41cc7d819..95317e07e 100644 --- a/packages/deriv_grouped_listview/CHANGELOG.md +++ b/packages/deriv_grouped_listview/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.2 + + - **FEAT**(analytics): Add indicator events ([#846](https://github.com/regentmarkets/flutter-deriv-packages/issues/846)). ([1d30ddc9](https://github.com/regentmarkets/flutter-deriv-packages/commit/1d30ddc9c7510e280cc90fc7e1308b945d5758ed)) + ## 0.0.1 * TODO: Describe initial release. diff --git a/packages/deriv_grouped_listview/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/packages/deriv_grouped_listview/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java index d007606a4..539ab022f 100644 --- a/packages/deriv_grouped_listview/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java +++ b/packages/deriv_grouped_listview/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java @@ -1,23 +1,19 @@ package io.flutter.plugins; -import io.flutter.plugin.common.PluginRegistry; +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import io.flutter.Log; + +import io.flutter.embedding.engine.FlutterEngine; /** * Generated file. Do not edit. + * This file is generated by the Flutter tool based on the + * plugins that support the Android platform. */ +@Keep public final class GeneratedPluginRegistrant { - public static void registerWith(PluginRegistry registry) { - if (alreadyRegisteredWith(registry)) { - return; - } - } - - private static boolean alreadyRegisteredWith(PluginRegistry registry) { - final String key = GeneratedPluginRegistrant.class.getCanonicalName(); - if (registry.hasPlugin(key)) { - return true; - } - registry.registrarFor(key); - return false; + private static final String TAG = "GeneratedPluginRegistrant"; + public static void registerWith(@NonNull FlutterEngine flutterEngine) { } } diff --git a/packages/deriv_grouped_listview/pubspec.yaml b/packages/deriv_grouped_listview/pubspec.yaml index d101c3fa3..64f5e206b 100644 --- a/packages/deriv_grouped_listview/pubspec.yaml +++ b/packages/deriv_grouped_listview/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_grouped_listview description: A new Flutter package project. -version: 0.0.1 +version: 0.0.2 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/packages/deriv_http_client/CHANGELOG.md b/packages/deriv_http_client/CHANGELOG.md index effe43c82..15316daf0 100644 --- a/packages/deriv_http_client/CHANGELOG.md +++ b/packages/deriv_http_client/CHANGELOG.md @@ -1,3 +1,17 @@ +## 2.0.2 + + - **REFACTOR**(deriv_http_client): upgrade flutter_system_proxy dep ([#854](https://github.com/regentmarkets/flutter-deriv-packages/issues/854)). ([30f8bc5d](https://github.com/regentmarkets/flutter-deriv-packages/commit/30f8bc5d87a40b17f344608b855bbd6261b94696)) + +## 2.0.1 + + - **REFACTOR**(deriv_http_client): upgrade flutter_system_proxy dep ([#854](https://github.com/regentmarkets/flutter-deriv-packages/issues/854)). ([30f8bc5d](https://github.com/regentmarkets/flutter-deriv-packages/commit/30f8bc5d87a40b17f344608b855bbd6261b94696)) + +## 2.0.0 + +> Note: This release has breaking changes. + + - **BREAKING** **REFACTOR**(deriv_auth): ramin_make_auth_and_web_view_proxy_aware ([#483](https://github.com/regentmarkets/flutter-deriv-packages/issues/483)). ([e99afc92](https://github.com/regentmarkets/flutter-deriv-packages/commit/e99afc926531c0c36c567d0ac8c66e906fa27ea5)) + ## 1.0.0 - Initial version. diff --git a/packages/deriv_http_client/lib/src/http_client.dart b/packages/deriv_http_client/lib/src/http_client.dart index 6247028b8..b4b9508bc 100644 --- a/packages/deriv_http_client/lib/src/http_client.dart +++ b/packages/deriv_http_client/lib/src/http_client.dart @@ -1,6 +1,10 @@ +import 'dart:async'; import 'dart:convert' as convert; +import 'package:flutter_system_proxy/flutter_system_proxy.dart'; import 'package:http/http.dart' as http; +import 'dart:io' as io; +import 'package:http/io_client.dart'; import 'base_http_client.dart'; import 'http_client_exception.dart'; @@ -59,3 +63,39 @@ class HttpClient extends BaseHttpClient { return jsonResponse; } } + +/// An implementation of [BaseHttpClient] that is aware of proxy settings. +class ProxyAwareHttpClient implements BaseHttpClient { + ProxyAwareHttpClient(String url) { + _setupProxy(url); + } + + late HttpClient _httpClient; + final Completer _setupCompleter = Completer(); + + Future _setupProxy(String url) async { + final String proxy = await FlutterSystemProxy.findProxyFromEnvironment(url); + final io.HttpClient httpClient = io.HttpClient(); + httpClient.findProxy = (Uri uri) => proxy; + + _httpClient = HttpClient(IOClient(httpClient)); + + _setupCompleter.complete(); + } + + @override + Future get(String url, {String? basicAuthToken}) async { + await _setupCompleter.future; + return _httpClient.get(url, basicAuthToken: basicAuthToken); + } + + @override + Future> post({ + required String url, + required Map jsonBody, + Map? headers, + }) async { + await _setupCompleter.future; + return _httpClient.post(url: url, jsonBody: jsonBody, headers: headers); + } +} diff --git a/packages/deriv_http_client/pubspec.yaml b/packages/deriv_http_client/pubspec.yaml index 9816c9f7e..048db586c 100644 --- a/packages/deriv_http_client/pubspec.yaml +++ b/packages/deriv_http_client/pubspec.yaml @@ -1,13 +1,17 @@ name: deriv_http_client description: A simple HTTP client for Deriv API services. -version: 1.0.0 +version: 2.0.2 environment: sdk: ">=3.0.0" dependencies: http: ^0.13.4 + flutter_system_proxy: + git: + url: https://github.com/BrowserStackCE/flutter_system_proxy.git + ref: v0.1.5 dev_dependencies: lints: ^2.0.0 diff --git a/packages/deriv_language_selector/.gitignore b/packages/deriv_language_selector/.gitignore new file mode 100644 index 000000000..96486fd93 --- /dev/null +++ b/packages/deriv_language_selector/.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/packages/deriv_language_selector/.metadata b/packages/deriv_language_selector/.metadata new file mode 100644 index 000000000..5c536bcec --- /dev/null +++ b/packages/deriv_language_selector/.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: "6c4930c4ac86fb286f30e31d0ec8bffbcbb9953e" + channel: "stable" + +project_type: package diff --git a/packages/deriv_language_selector/CHANGELOG.md b/packages/deriv_language_selector/CHANGELOG.md new file mode 100644 index 000000000..2d2f70c67 --- /dev/null +++ b/packages/deriv_language_selector/CHANGELOG.md @@ -0,0 +1,147 @@ +## 0.0.3+14 + + - Update a dependency to the latest release. + +## 0.0.3+13 + + - Update a dependency to the latest release. + +## 0.0.3+12 + + - Update a dependency to the latest release. + +## 0.0.3+11 + + - Update a dependency to the latest release. + +## 0.0.3+10 + + - Update a dependency to the latest release. + +## 0.0.3+9 + + - Update a dependency to the latest release. + +## 0.0.3+8 + + - Update a dependency to the latest release. + +## 0.0.3+7 + + - Update a dependency to the latest release. + +## 0.0.3+6 + + - Update a dependency to the latest release. + +## 0.0.3+5 + + - Update a dependency to the latest release. + +## 0.0.3+4 + + - Update a dependency to the latest release. + +## 0.0.3+3 + + - Update a dependency to the latest release. + +## 0.0.3+2 + + - Update a dependency to the latest release. + +## 0.0.3+1 + + - Update a dependency to the latest release. + +## 0.0.3 + + - **FEAT**(deriv_language_selector): Add a callback for language change. ([#778](https://github.com/regentmarkets/flutter-deriv-packages/issues/778)). ([21f6b8de](https://github.com/regentmarkets/flutter-deriv-packages/commit/21f6b8dee167ee1234bb1ee0a22b766305d5660a)) + +## 0.0.2+18 + + - Update a dependency to the latest release. + +## 0.0.2+17 + + - Update a dependency to the latest release. + +## 0.0.2+16 + + - Update a dependency to the latest release. + +## 0.0.2+15 + + - Update a dependency to the latest release. + +## 0.0.2+14 + + - Update a dependency to the latest release. + +## 0.0.2+13 + + - Update a dependency to the latest release. + +## 0.0.2+12 + + - Update a dependency to the latest release. + +## 0.0.2+11 + + - Update a dependency to the latest release. + +## 0.0.2+10 + + - Update a dependency to the latest release. + +## 0.0.2+9 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +## 0.0.2+8 + + - Update a dependency to the latest release. + +## 0.0.2+7 + + - Update a dependency to the latest release. + +## 0.0.2+6 + + - Update a dependency to the latest release. + +## 0.0.2+5 + + - Update a dependency to the latest release. + +## 0.0.2+4 + + - Update a dependency to the latest release. + +## 0.0.2+3 + + - Update a dependency to the latest release. + +## 0.0.2+2 + + - Update a dependency to the latest release. + +## 0.0.2+1 + + - Update a dependency to the latest release. + +## 0.0.2 + + - **FEAT**(deriv_language_selector): aliakbar/1586/set_device_lang_as_default_if_app_supports ([#540](https://github.com/regentmarkets/flutter-deriv-packages/issues/540)). ([88ba3104](https://github.com/regentmarkets/flutter-deriv-packages/commit/88ba31049eb3a718db3e1ee5e8e85f56689d94ba)) + +## 0.0.1+2 + + - Update a dependency to the latest release. + +## 0.0.1+1 + + - Update a dependency to the latest release. + +## 0.0.1 + +* TODO: Describe initial release. diff --git a/packages/deriv_language_selector/LICENSE b/packages/deriv_language_selector/LICENSE new file mode 100644 index 000000000..ba75c69f7 --- /dev/null +++ b/packages/deriv_language_selector/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/packages/deriv_language_selector/README.md b/packages/deriv_language_selector/README.md new file mode 100644 index 000000000..60a46299b --- /dev/null +++ b/packages/deriv_language_selector/README.md @@ -0,0 +1,178 @@ +# Deriv Language Selector + +A package to handle language changes in the app. + +## Features +- Bottomsheet widget with a list of languages to select from. +- A language selector button that opens a bottomsheet to select a language. +- Handles saving and retrieving the selected language from shared preferences. +- Handles state of the language of the app using Cubits. +- Provides interfaces to be implemented by the app to handle the language changes. +- Default flags and supported languages are: +`en, es, fr, id, it, pl, pt, ru, th, vi, zh, ar, de, ko, sw, tr` +Client apps have the flexibility to use their own flag asset and supported languages. + +## Usage +To use this package, add `deriv_language_selector` as a dependency in your pubspec.yaml file. + +```yaml +dependencies: + deriv_language_selector: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_language_selector + ref: deriv_language_selector-v0.0.1 +``` + +In your dart file, import the package. + +```dart +import 'package:deriv_language_selector/deriv_language_selector.dart'; +``` + +## Getting Started +### Implement `BaseLanguageRepository`. + +```dart +class LanguageRepository implements BaseLanguageRepository { + + + @override + FutureOr?> getSupportedLanguagesFromServer({ + required ValueSetter> onLanguageFetched, + }) async { + + // If the server sends as a Future. (Ref from Deriv P2P app) + await FirebaseRemoteConfig.instance.fetchAndActivate(); + final RemoteConfigValue remoteConfigValue = FirebaseRemoteConfig.instance + .getValue(FirebaseRemoteConfigKeys.localLanguages); + final String languagesData = remoteConfigValue.asString(); + + + if (languagesData.isNotEmpty) { + final List> listOfMaps = + List>.from(jsonDecode(languagesData)); + return listOfMaps + .where((Map element) => element['active'] == true) + .map((Map e) => e['countryCode'].toString()) + .toList(); + } else{ + return []; + } + + // If the server sends as stream (Ref from Deriv GO app) + final WebsiteStatusCubit websiteStatusCubitInstance = + BlocManager.instance.fetch(); + + websiteStatusCubitInstance.stream + .startWith(websiteStatusCubitInstance.state) + .listen( + (WebsiteStatusState state) { + if (state.websiteStatus != null) { + // this will update the active languages in [LanguageService] + // which is used by [LanguageCubit]. + onLanguageFetched.call(state.websiteStatus!.supportedLanguages!); + + BlocManager.instance.fetch().updateActiveLanguages(); + } + }, + ); + + return null; + } + + @override + Future loadLanguage(Locale locale) async { + await Localization.delegate.load(locale); + } + + @override + void reconnectToServerWithNewLanguage(Locale locale) { + // provide your implementation to reconnect to websocket with new language. + } +} +``` + +### Provide `LanguageCubit` to the root widget. + + +```dart +final LanguageService languageService = LanguageService( + languageRepository: LanguageRepository(), + languageDataSource: LanguageDataSource( + prefInstance: PrefService.instance!, //instance of shared preferences + localStorageKey: 'language', // optional, default is 'appLanguage' + ), + supportedLanguages: supportedLanguages, // List of supported `LanguageEntity` + ); + +BlocProvider( + create: (context) => LanguageCubit( + languageService: languageService, + ), + child: Widget(), +); +``` + + + + +### Use `LanguageSelector.button` to display the language selector button. + +The following parameters are available: + + * showLanguageBottomSheet(optional) - Function to show the language bottom sheet. The + bottom sheet widget is provided through this function. This is added to give client + apps flexibility to use their own handler for bottom sheet. Example, P2P uses + Overlay.showDialog while GO uses showModalBottomSheet. +* bottomsheetTitle - Title of the bottom sheet for the case when going for default bottom sheet. +* usePackageFlags (optional) - Determines if the package flag assets should be used. `IF set to false, + the client apps need to have assets added under the [assets/icons/flags] folder. The naming + convention should be [ic_flag_language_code].png. example, ic_flag_en.png.` + +```dart + +LanguageSelector.button( + bottomsheetTitle: context.localization.labelLanguage, + showLanguageBottomSheet: // callback for showing bottomsheet (optional). Default uses `showModalBottomSheet`. + usePackageFlags: false, // optional, default is true + ), +``` + +When the button is clicked, it will open a bottomsheet with a list of languages to select from. + + +### Use `LanguageSelector.bottomSheet` to display the language selector bottom sheet. + +The following parameters are available: + + * usePackageFlags (optional) - Determines if the package flag assets should be used. `IF set to false, + the client apps need to have assets added under the [assets/icons/flags] folder. The naming + convention should be [ic_flag_language_code].png. example, ic_flag_en.png.` + +```dart +showModalBottomSheet( + context: context, + builder: (context) { + return LanguageSelector.bottomSheet( + usePackageFlags: false, // optional, default is true + ); + }, +); +``` + +A helper function `showExpandableLanguageBottomSheet` is provided to show the bottom sheet in `ExpandableBottomSheet`. + +```dart + +showExpandableLanguageBottomSheet( + context: context, + bottomsheetTitle: context.localization.labelLanguage, + usePackageFlags: false, // optional, default is true +); +``` + + + + + diff --git a/packages/deriv_language_selector/analysis_options.yaml b/packages/deriv_language_selector/analysis_options.yaml new file mode 100644 index 000000000..74f62ede3 --- /dev/null +++ b/packages/deriv_language_selector/analysis_options.yaml @@ -0,0 +1,135 @@ +include: package:flutter_lints/flutter.yaml + +analyzer: + exclude: + - example/** + + errors: + todo: ignore + missing_required_param: warning + missing_return: warning + # https://github.com/flutter/flutter/pull/24528 + sdk_version_async_exported_from_core: ignore + + language: + strict-raw-types: true + +linter: + rules: + # Taken from https://github.com/dart-lang/linter/blob/master/example/all.yaml + - always_declare_return_types + - always_put_control_body_on_new_line + - always_put_required_named_parameters_first + - always_require_non_null_named_parameters + - always_specify_types + - annotate_overrides + - avoid_bool_literals_in_conditional_expressions + - avoid_catches_without_on_clauses + - avoid_empty_else + - avoid_equals_and_hash_code_on_mutable_classes + - avoid_field_initializers_in_const_classes + - avoid_function_literals_in_foreach_calls + - avoid_init_to_null + - avoid_null_checks_in_equality_operators + - avoid_positional_boolean_parameters + - avoid_print + - avoid_redundant_argument_values + - avoid_relative_lib_imports + - avoid_renaming_method_parameters + - avoid_return_types_on_setters + - avoid_returning_null + - avoid_returning_null_for_future + - avoid_returning_null_for_void + - avoid_setters_without_getters + - avoid_shadowing_type_parameters + - avoid_single_cascade_in_expression_statements + - avoid_slow_async_io + - avoid_types_as_parameter_names + - avoid_unnecessary_containers + - avoid_unused_constructor_parameters + - avoid_void_async + - await_only_futures + - camel_case_types + - cancel_subscriptions + - cascade_invocations + - close_sinks + - constant_identifier_names + - control_flow_in_finally + - curly_braces_in_flow_control_structures + - empty_catches + - empty_constructor_bodies + - empty_statements + - file_names + - flutter_style_todos + - hash_and_equals + - implementation_imports + - join_return_with_assignment + - library_names + - library_prefixes + - collection_methods_unrelated_type + - no_adjacent_strings_in_list + - no_duplicate_case_values + - non_constant_identifier_names + - null_closures + - only_throw_errors + - overridden_fields + - package_api_docs + - package_names + - package_prefixed_library_names + - parameter_assignments + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_constructors_over_static_methods + - prefer_contains + - prefer_expression_function_bodies # https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_foreach + - prefer_function_declarations_over_variables + - prefer_generic_function_type_aliases + - prefer_initializing_formals + - prefer_int_literals + - prefer_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_iterable_whereType + - prefer_mixin + - prefer_single_quotes + - prefer_typing_uninitialized_variables + - prefer_void_to_null + - public_member_api_docs + - recursive_getters + - slash_for_doc_comments + - sort_constructors_first + # - sort_pub_dependencies + - sort_unnamed_constructors_first + - test_types_in_equals + - throw_in_finally + - type_init_formals + - unawaited_futures + - unnecessary_await_in_return + - unnecessary_brace_in_string_interps + - unnecessary_const + - unnecessary_getters_setters + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_overrides + - unnecessary_parenthesis + - unnecessary_statements + - unnecessary_this + - unrelated_type_equality_checks + - use_function_type_syntax_for_parameters + - use_rethrow_when_possible + - use_setters_to_change_properties + - use_string_buffers + - use_to_and_as_if_applicable + - valid_regexps + - void_checks diff --git a/packages/deriv_language_selector/assets/icons/flags/ic_flag_ar.png b/packages/deriv_language_selector/assets/icons/flags/ic_flag_ar.png new file mode 100644 index 000000000..208d7590d Binary files /dev/null and b/packages/deriv_language_selector/assets/icons/flags/ic_flag_ar.png differ diff --git a/packages/deriv_language_selector/assets/icons/flags/ic_flag_de.png b/packages/deriv_language_selector/assets/icons/flags/ic_flag_de.png new file mode 100644 index 000000000..2e8763000 Binary files /dev/null and b/packages/deriv_language_selector/assets/icons/flags/ic_flag_de.png differ diff --git a/packages/deriv_language_selector/assets/icons/flags/ic_flag_en.png b/packages/deriv_language_selector/assets/icons/flags/ic_flag_en.png new file mode 100644 index 000000000..56c4ca788 Binary files /dev/null and b/packages/deriv_language_selector/assets/icons/flags/ic_flag_en.png differ diff --git a/packages/deriv_language_selector/assets/icons/flags/ic_flag_es.png b/packages/deriv_language_selector/assets/icons/flags/ic_flag_es.png new file mode 100644 index 000000000..05216694b Binary files /dev/null and b/packages/deriv_language_selector/assets/icons/flags/ic_flag_es.png differ diff --git a/packages/deriv_language_selector/assets/icons/flags/ic_flag_fr.png b/packages/deriv_language_selector/assets/icons/flags/ic_flag_fr.png new file mode 100644 index 000000000..3497fa7dd Binary files /dev/null and b/packages/deriv_language_selector/assets/icons/flags/ic_flag_fr.png differ diff --git a/packages/deriv_language_selector/assets/icons/flags/ic_flag_id.png b/packages/deriv_language_selector/assets/icons/flags/ic_flag_id.png new file mode 100644 index 000000000..39cd2ffdb Binary files /dev/null and b/packages/deriv_language_selector/assets/icons/flags/ic_flag_id.png differ diff --git a/packages/deriv_language_selector/assets/icons/flags/ic_flag_it.png b/packages/deriv_language_selector/assets/icons/flags/ic_flag_it.png new file mode 100644 index 000000000..450fac051 Binary files /dev/null and b/packages/deriv_language_selector/assets/icons/flags/ic_flag_it.png differ diff --git a/packages/deriv_language_selector/assets/icons/flags/ic_flag_ko.png b/packages/deriv_language_selector/assets/icons/flags/ic_flag_ko.png new file mode 100644 index 000000000..4f1f3cffc Binary files /dev/null and b/packages/deriv_language_selector/assets/icons/flags/ic_flag_ko.png differ diff --git a/packages/deriv_language_selector/assets/icons/flags/ic_flag_pl.png b/packages/deriv_language_selector/assets/icons/flags/ic_flag_pl.png new file mode 100644 index 000000000..9524ba3d2 Binary files /dev/null and b/packages/deriv_language_selector/assets/icons/flags/ic_flag_pl.png differ diff --git a/packages/deriv_language_selector/assets/icons/flags/ic_flag_pt.png b/packages/deriv_language_selector/assets/icons/flags/ic_flag_pt.png new file mode 100644 index 000000000..05bedfcb6 Binary files /dev/null and b/packages/deriv_language_selector/assets/icons/flags/ic_flag_pt.png differ diff --git a/packages/deriv_language_selector/assets/icons/flags/ic_flag_ru.png b/packages/deriv_language_selector/assets/icons/flags/ic_flag_ru.png new file mode 100644 index 000000000..b053b97df Binary files /dev/null and b/packages/deriv_language_selector/assets/icons/flags/ic_flag_ru.png differ diff --git a/packages/deriv_language_selector/assets/icons/flags/ic_flag_sw.png b/packages/deriv_language_selector/assets/icons/flags/ic_flag_sw.png new file mode 100644 index 000000000..96dfd94e7 Binary files /dev/null and b/packages/deriv_language_selector/assets/icons/flags/ic_flag_sw.png differ diff --git a/packages/deriv_language_selector/assets/icons/flags/ic_flag_th.png b/packages/deriv_language_selector/assets/icons/flags/ic_flag_th.png new file mode 100644 index 000000000..4053fe333 Binary files /dev/null and b/packages/deriv_language_selector/assets/icons/flags/ic_flag_th.png differ diff --git a/packages/deriv_language_selector/assets/icons/flags/ic_flag_tr.png b/packages/deriv_language_selector/assets/icons/flags/ic_flag_tr.png new file mode 100644 index 000000000..045f15559 Binary files /dev/null and b/packages/deriv_language_selector/assets/icons/flags/ic_flag_tr.png differ diff --git a/packages/deriv_language_selector/assets/icons/flags/ic_flag_vi.png b/packages/deriv_language_selector/assets/icons/flags/ic_flag_vi.png new file mode 100644 index 000000000..d83b1e5e7 Binary files /dev/null and b/packages/deriv_language_selector/assets/icons/flags/ic_flag_vi.png differ diff --git a/packages/deriv_language_selector/assets/icons/flags/ic_flag_zh.png b/packages/deriv_language_selector/assets/icons/flags/ic_flag_zh.png new file mode 100644 index 000000000..c854beeca Binary files /dev/null and b/packages/deriv_language_selector/assets/icons/flags/ic_flag_zh.png differ diff --git a/packages/deriv_language_selector/assets/screenshots/language_selector.gif b/packages/deriv_language_selector/assets/screenshots/language_selector.gif new file mode 100644 index 000000000..125606e00 Binary files /dev/null and b/packages/deriv_language_selector/assets/screenshots/language_selector.gif differ diff --git a/packages/deriv_language_selector/lib/deriv_language_selector.dart b/packages/deriv_language_selector/lib/deriv_language_selector.dart new file mode 100644 index 000000000..c62c850dd --- /dev/null +++ b/packages/deriv_language_selector/lib/deriv_language_selector.dart @@ -0,0 +1 @@ +export 'src/src.dart'; diff --git a/packages/deriv_language_selector/lib/src/cubits/language_cubit.dart b/packages/deriv_language_selector/lib/src/cubits/language_cubit.dart new file mode 100644 index 000000000..8d5965d38 --- /dev/null +++ b/packages/deriv_language_selector/lib/src/cubits/language_cubit.dart @@ -0,0 +1,57 @@ +import 'dart:async'; + +import 'package:deriv_language_selector/deriv_language_selector.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +part 'language_state.dart'; + +/// {@template language_cubit} +/// A [Cubit] which manages the language state of the app. +/// {@endtemplate} +class LanguageCubit extends Cubit { + /// {@macro language_cubit} + LanguageCubit({ + required this.languageService, + required this.onLanguageChanged, + }) : super(LanguageLoadedState( + language: languageService.defaultLanguage, + activeLanguages: languageService.languages, + )) { + _loadCurrentLanguage(); + } + + /// Language service. + final LanguageService languageService; + + /// Callback function to be called when the language is changed. + final void Function(LanguageModel selectedLanguage) onLanguageChanged; + + /// Updates the language and emits [LanguageLoadedState]. + Future updateLanguage( + LanguageModel language, + ) async { + await languageService.loadAndSetLanguage(language); + + onLanguageChanged(language); + emit(LanguageLoadedState( + language: language, activeLanguages: languageService.languages)); + languageService.reconnectToServerWithNewLanguage(language); + } + + /// Updates the active languages and emits [LanguageLoadedState]. + /// [languageService.languages] is changed in case active languages are + /// coming from a stream. + Future updateActiveLanguages() async { + emit(LanguageLoadedState( + language: state.language, activeLanguages: languageService.languages)); + } + + /// Loads the current language and emits [LanguageLoadedState]. + Future _loadCurrentLanguage() async { + final LanguageModel language = await languageService.getCurrentLanguage(); + await languageService.getActiveLanguages(); + await updateLanguage(language); + } +} diff --git a/packages/deriv_language_selector/lib/src/cubits/language_state.dart b/packages/deriv_language_selector/lib/src/cubits/language_state.dart new file mode 100644 index 000000000..06b0e519e --- /dev/null +++ b/packages/deriv_language_selector/lib/src/cubits/language_state.dart @@ -0,0 +1,31 @@ +part of 'language_cubit.dart'; + +/// Abstract for language state. +abstract class LanguageState extends Equatable { + /// Instantiate [LanguageState]. + const LanguageState({ + required this.language, + required this.activeLanguages, + }); + + /// Instance of [LanguageModel]. + final LanguageModel language; + + /// Languages currently actively supported in the app. + final List activeLanguages; +} + +/// Language Loaded state. +class LanguageLoadedState extends LanguageState { + /// Instantiate [LanguageLoadedState]. + const LanguageLoadedState( + {required LanguageModel language, + required List activeLanguages}) + : super(language: language, activeLanguages: activeLanguages); + + @override + List get props => [ + language, + activeLanguages, + ]; +} diff --git a/packages/deriv_language_selector/lib/src/data/base_language_data_source.dart b/packages/deriv_language_selector/lib/src/data/base_language_data_source.dart new file mode 100644 index 000000000..b4f33c06e --- /dev/null +++ b/packages/deriv_language_selector/lib/src/data/base_language_data_source.dart @@ -0,0 +1,9 @@ +/// Base class for language data source + +abstract class BaseLanguageDataSource { + /// Get the current language from shared preferences. + Future getLanguage({bool shouldReload = false}); + + /// Set the language in shared preferences. + Future setLanguage(String language); +} diff --git a/packages/deriv_language_selector/lib/src/data/language_data_source.dart b/packages/deriv_language_selector/lib/src/data/language_data_source.dart new file mode 100644 index 000000000..8073257eb --- /dev/null +++ b/packages/deriv_language_selector/lib/src/data/language_data_source.dart @@ -0,0 +1,30 @@ +import 'package:deriv_language_selector/deriv_language_selector.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +/// Implementation of [BaseLanguageDataSource] +final class LanguageDataSource implements BaseLanguageDataSource { + /// Instantiate [LanguageDataSource]. + LanguageDataSource({ + required this.prefInstance, + this.localStorageKey = 'appLanguage', + }); + + /// Local storage key of application language to + /// be stored in shared preferences. Default is `appLanguage`. + final String localStorageKey; + + /// Instance of shared preferences. + final SharedPreferences prefInstance; + + @override + Future getLanguage({bool shouldReload = false}) async { + if (shouldReload) { + await prefInstance.reload(); + } + return prefInstance.getString(localStorageKey); + } + + @override + Future setLanguage(String language) => + prefInstance.setString(localStorageKey, language); +} diff --git a/packages/deriv_language_selector/lib/src/helpers/language_bottom_sheet_helper.dart b/packages/deriv_language_selector/lib/src/helpers/language_bottom_sheet_helper.dart new file mode 100644 index 000000000..cc1542324 --- /dev/null +++ b/packages/deriv_language_selector/lib/src/helpers/language_bottom_sheet_helper.dart @@ -0,0 +1,27 @@ +import 'package:deriv_language_selector/deriv_language_selector.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +/// A helper function to show the language bottom sheet. +void showExpandableLanguageBottomSheet({ + required BuildContext context, + required String bottomsheetTitle, + bool usePackageFlags = true, +}) => + showModalBottomSheet( + context: context, + isScrollControlled: true, + useRootNavigator: true, + builder: (BuildContext innerContext) => BlocProvider.value( + value: BlocProvider.of(context), + child: ExpandableBottomSheet( + title: bottomsheetTitle, + upperContent: LanguageSelector.bottomSheet( + usePackageFlags: usePackageFlags, + ), + // TODO(sahani): Remove labelContractDetails + labelContractDetails: '', + ), + ), + ); diff --git a/packages/deriv_language_selector/lib/src/helpers/language_helper.dart b/packages/deriv_language_selector/lib/src/helpers/language_helper.dart new file mode 100644 index 000000000..19143e974 --- /dev/null +++ b/packages/deriv_language_selector/lib/src/helpers/language_helper.dart @@ -0,0 +1,70 @@ +import 'package:deriv_language_selector/deriv_language_selector.dart'; +import 'package:flutter/material.dart'; + +/// Default languages for the package. +List defaultLanguages = [ + LanguageEntity( + name: 'English', + locale: const Locale('en'), + ), + LanguageEntity( + name: 'Deutsch', + locale: const Locale('de'), + ), + LanguageEntity( + name: 'Español', + locale: const Locale('es'), + ), + LanguageEntity( + name: 'Русский', + locale: const Locale('ru'), + ), + LanguageEntity( + name: 'Tiếng Việt', + locale: const Locale('vi'), + ), + LanguageEntity( + name: 'Français', + locale: const Locale('fr'), + ), + LanguageEntity( + name: 'ไทย', + locale: const Locale('th'), + ), + LanguageEntity( + name: 'Italiano', + locale: const Locale('it'), + ), + LanguageEntity( + name: 'Polski', + locale: const Locale('pl'), + ), + LanguageEntity( + name: 'Português', + locale: const Locale('pt'), + ), + LanguageEntity( + name: 'العربية', + locale: const Locale('ar'), + ), + LanguageEntity( + name: '中文', + locale: const Locale('zh'), + ), + LanguageEntity( + name: '한국어', + locale: const Locale('ko'), + ), + LanguageEntity( + name: 'Bahasa Indonesia', + locale: const Locale('id'), + ), + LanguageEntity( + name: 'Türkçe', + locale: const Locale('tr'), + ), + LanguageEntity( + name: 'Swahili', + locale: const Locale('sw'), + ), +]; diff --git a/packages/deriv_language_selector/lib/src/models/language_entity.dart b/packages/deriv_language_selector/lib/src/models/language_entity.dart new file mode 100644 index 000000000..2cacd4b6b --- /dev/null +++ b/packages/deriv_language_selector/lib/src/models/language_entity.dart @@ -0,0 +1,24 @@ +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; + +/// Entity class for language. +class LanguageEntity { + /// Instantiate [LanguageEntity]. + LanguageEntity({ + required this.name, + required this.locale, + }); + + /// Name of the language. Example: English, Deutsch, etc. + final String name; + + /// Locale of the language. Example: en, de, etc. + final Locale locale; + + /// Converts [LanguageEntity] to [LanguageModel]. + LanguageModel toModel(String flag) => LanguageModel( + name: name, + code: locale.languageCode, + flag: flag, + ); +} diff --git a/packages/deriv_language_selector/lib/src/presentation/widgets/language_selector.dart b/packages/deriv_language_selector/lib/src/presentation/widgets/language_selector.dart new file mode 100644 index 000000000..15c3f73d1 --- /dev/null +++ b/packages/deriv_language_selector/lib/src/presentation/widgets/language_selector.dart @@ -0,0 +1,121 @@ +import 'package:deriv_language_selector/deriv_language_selector.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +/// A widget that displays the language selector. +/// It can be used as a button or a bottom sheet based on the constructor used. +/// It uses [LanguageCubit] to manage the language state. +class LanguageSelector extends StatelessWidget { + /// Instantiate [LanguageSelector] as a bottomsheet. + /// The bottom sheet provides list of languages to select from. + /// + /// The following parameters are accepted: + /// * [usePackageFlags (optional)] - Determines if the package flag assets should be used. IF set to false, + /// the client apps need to have assets added under the [assets/icons/flags] folder. The naming + /// convention should be [ic_flag_language_code].png. example, ic_flag_en.png. + /// + /// Exmaple: + /// ```dart + /// showModalBottomSheet( + /// context: context, + /// builder: (BuildContext context) => LanguageSelector.bottomSheet( + /// usePackageFlags: false, + /// ); + /// ``` + const LanguageSelector.bottomSheet({ + this.usePackageFlags = true, + Key? key, + }) : isBottomSheet = true, + showLanguageBottomSheet = null, + bottomsheetTitle = null, + super(key: key); + + /// Instantiate [LanguageSelector] as a button that opens + /// the language bottom sheet when clicked. + /// + /// The following parameters are accepted: + /// * [showLanguageBottomSheet(optional)] - Function to show the language bottom sheet. The + /// bottom sheet widget is provided through this function. This is added to give client + /// apps flexibility to use their own handler for bottom sheet. Example, P2P uses + /// Overlay.showDialog while GO uses showModalBottomSheet. + /// * [bottomsheetTitle] - Title of the bottom sheet for the case when going for default bottom sheet. + /// * [usePackageFlags (optional)] - Determines if the package flag assets should be used. IF set to false, + /// the client apps need to have assets added under the [assets/icons/flags] folder. The naming + /// convention should be [ic_flag_language_code].png. example, ic_flag_en.png. + /// + /// Exmaple: + /// ```dart + /// LanguageSelector.button( + /// bottomsheetTitle: 'Select Language', + /// usePackageFlags: false, + /// showLanguageBottomSheet: (Widget bottomsheet, BuildContext context) { + /// showModalBottomSheet( + /// context: context, + /// builder: (BuildContext context) => bottomsheet, + /// ); + /// }, + /// ); + /// ``` + const LanguageSelector.button({ + required this.bottomsheetTitle, + this.showLanguageBottomSheet, + this.usePackageFlags = true, + Key? key, + }) : isBottomSheet = false, + super(key: key); + + /// Function to show the language bottom sheet. + /// The bottom sheet widget is passed through this function + /// to client code to show the bottom sheet. If not provided, + /// defaults to [showExpandableLanguageBottomSheet]. + final Function(Widget bottomsheet, BuildContext context)? + showLanguageBottomSheet; + + /// Flag to determine if the widget is a bottom sheet or a button. + final bool? isBottomSheet; + + /// Title of the bottom sheet for the case when + /// going for default bottom sheet. + final String? bottomsheetTitle; + + /// Determines if the package flag assets should be used. + /// IF set to false, the client apps need to have assets added + /// under the [assets/icons/flags] folder. The naming convention + /// should be [ic_flag_language_code].png. + final bool usePackageFlags; + + @override + Widget build(BuildContext context) => + BlocBuilder( + bloc: BlocProvider.of(context), + builder: (BuildContext context, LanguageState state) => isBottomSheet! + ? _buildLanguageBottomSheet(context, state) + : LanguageSelectorWidget( + package: usePackageFlags ? 'deriv_language_selector' : null, + languageItem: state.language, + onPressed: () { + showLanguageBottomSheet != null + ? showLanguageBottomSheet!.call( + _buildLanguageBottomSheet(context, state), + context, + ) + : showExpandableLanguageBottomSheet( + context: context, + bottomsheetTitle: bottomsheetTitle!); + }), + ); + + LanguageItemList _buildLanguageBottomSheet( + BuildContext context, LanguageState state) => + LanguageItemList( + package: usePackageFlags ? 'deriv_language_selector' : null, + items: state.activeLanguages + ..sort( + (LanguageModel a, LanguageModel b) => a.name.compareTo(b.name)), + onLanguageSelected: (LanguageModel language) { + context.read().updateLanguage(language); + }, + selectedItem: state.language, + ); +} diff --git a/packages/deriv_language_selector/lib/src/repository/base_language_repository.dart b/packages/deriv_language_selector/lib/src/repository/base_language_repository.dart new file mode 100644 index 000000000..a0ead35a4 --- /dev/null +++ b/packages/deriv_language_selector/lib/src/repository/base_language_repository.dart @@ -0,0 +1,24 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +/// Base class for Language Repository that will +/// be implemented by client apps. +abstract class BaseLanguageRepository { + /// Get supported languages from server. + /// [onLanguageFetched] is helpful for case when languages are fetched from a stream. + /// It will update the active languages in [LanguageService] which is used by [LanguageCubit]. + /// + /// Example: Deriv Go app fetches languages from a stream. Deriv Go can use + /// this method to update the active languages in the app. + /// P2P fetches from Remote Config as Future. P2P can ignore this parameter. + FutureOr?> getSupportedLanguagesFromServer({ + required ValueSetter> onLanguageFetched, + }); + + /// Reconnect to server with new language. + void reconnectToServerWithNewLanguage(Locale locale); + + /// Load the selected language. + Future loadLanguage(Locale locale); +} diff --git a/packages/deriv_language_selector/lib/src/services/base_language_service.dart b/packages/deriv_language_selector/lib/src/services/base_language_service.dart new file mode 100644 index 000000000..83e76b848 --- /dev/null +++ b/packages/deriv_language_selector/lib/src/services/base_language_service.dart @@ -0,0 +1,20 @@ +import 'package:deriv_ui/deriv_ui.dart'; + +/// Base class for language service which interacts with +/// [BaseLanguageRepository] and [BaseLanguageDataSource]. +abstract class BaseLanguageService { + /// Get active languages for the app. + /// This will call [BaseLanguageRepository.getSupportedLanguagesFromServer] + /// and set the active languages which will be used by [LanguageCubit]. + Future getActiveLanguages(); + + /// Start loading the resources for `locale` and + /// saves the provided language in shared preferences through [BaseLanguageDataSource]. + Future loadAndSetLanguage(LanguageModel language); + + /// Reconnect to server with new language. + void reconnectToServerWithNewLanguage(LanguageModel language); + + /// Get the current language. + Future getCurrentLanguage(); +} diff --git a/packages/deriv_language_selector/lib/src/services/language_service.dart b/packages/deriv_language_selector/lib/src/services/language_service.dart new file mode 100644 index 000000000..71679626a --- /dev/null +++ b/packages/deriv_language_selector/lib/src/services/language_service.dart @@ -0,0 +1,122 @@ +import 'dart:io'; + +import 'package:deriv_language_selector/deriv_language_selector.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; + +/// Implementation of [BaseLanguageService]. +class LanguageService implements BaseLanguageService { + /// Instantiate [LanguageService]. + LanguageService({ + required this.languageRepository, + required this.languageDataSource, + this.supportedLanguages, + }) { + _languages = _generateLanguages( + [], + supportedLanguages ?? defaultLanguages, + ); + } + + /// Instance of [BaseLanguageRepository]. + final BaseLanguageRepository languageRepository; + + /// Instance of [BaseLanguageDataSource]. + final BaseLanguageDataSource languageDataSource; + + /// List of supported languages. + final List? supportedLanguages; + + late List _languages; + + /// Default language of the app. + LanguageModel get defaultLanguage => _languages.firstWhere( + (LanguageModel language) => language.code == platformLanguage, + orElse: () => _languages.first, + ); + + /// System/Device language code. + String get platformLanguage => Platform.localeName.split('_').first; + + /// List of active languages. + List get languages => _languages; + + @override + Future getCurrentLanguage() async { + final String? code = await languageDataSource.getLanguage(); + + if (code == null) { + return defaultLanguage; + } else { + return _languages.firstWhere( + (LanguageModel element) => element.code == code, + orElse: () => defaultLanguage, + ); + } + } + + @override + Future getActiveLanguages() async { + final List localLanguages = + supportedLanguages ?? defaultLanguages; + + final List activeLanguages = + (await languageRepository.getSupportedLanguagesFromServer( + onLanguageFetched: (List value) { + _setLanguages(value, + localLanguages); // useful for stream (refer to Deriv Go's getSupportedLanguagesFromServer method) + }, + )) ?? + []; + + _setLanguages(activeLanguages, localLanguages); + } + + /// Set the active languages. + void _setLanguages( + List activeLanguages, List localLanguages) { + _languages = _generateLanguages( + localLanguages + .where((LanguageEntity language) => + activeLanguages.contains(language.locale.languageCode)) + .toList(), + localLanguages); + } + + @override + Future loadAndSetLanguage(LanguageModel language) async { + await languageRepository.loadLanguage(Locale(language.code)); + + await languageDataSource.setLanguage(language.code); + } + + @override + void reconnectToServerWithNewLanguage(LanguageModel language) { + languageRepository.reconnectToServerWithNewLanguage(Locale(language.code)); + } + + static List _generateLanguages( + List activeLanguages, + List localLanguages, + ) { + // Local/scoped function to convert a LanguageEntity to a LanguageModel. + LanguageModel convertToModel(LanguageEntity language) => language.toModel( + 'assets/icons/flags/ic_flag_${language.locale.languageCode}.png'); + + // If there are no active languages, convert all local languages to models. + if (activeLanguages.isEmpty) { + return localLanguages.map(convertToModel).toList(); + } + + // Prepare a set of active languages based on locale for efficient lookup. + final Set activeLanguagesSet = activeLanguages + .map((LanguageEntity language) => language.locale) + .toSet(); + + return localLanguages + .where((LanguageEntity language) => + activeLanguagesSet.contains(language.locale)) + .map(convertToModel) + .toList(); + } +} diff --git a/packages/deriv_language_selector/lib/src/src.dart b/packages/deriv_language_selector/lib/src/src.dart new file mode 100644 index 000000000..22bb2eeeb --- /dev/null +++ b/packages/deriv_language_selector/lib/src/src.dart @@ -0,0 +1,10 @@ +export 'cubits/language_cubit.dart'; +export 'data/base_language_data_source.dart'; +export 'data/language_data_source.dart'; +export 'helpers/language_helper.dart'; +export 'helpers/language_bottom_sheet_helper.dart'; +export 'models/language_entity.dart'; +export 'presentation/widgets/language_selector.dart'; +export 'repository/base_language_repository.dart'; +export 'services/base_language_service.dart'; +export 'services/language_service.dart'; diff --git a/packages/deriv_language_selector/pubspec.yaml b/packages/deriv_language_selector/pubspec.yaml new file mode 100644 index 000000000..f74fb567f --- /dev/null +++ b/packages/deriv_language_selector/pubspec.yaml @@ -0,0 +1,45 @@ +name: deriv_language_selector +description: A package to select language for the app. It provides both UI and logic for language selection. +version: 0.0.3+14 +publish_to: "none" + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + deriv_ui: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_ui + ref: deriv_ui-v0.1.1 + shared_preferences: ^2.2.2 + flutter_bloc: ^8.1.4 + equatable: ^2.0.5 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + bloc_test: ^9.1.6 + mocktail: ^1.0.3 + +flutter: + assets: + - assets/icons/flags/ic_flag_en.png + - assets/icons/flags/ic_flag_id.png + - assets/icons/flags/ic_flag_pt.png + - assets/icons/flags/ic_flag_vi.png + - assets/icons/flags/ic_flag_zh.png + - assets/icons/flags/ic_flag_es.png + - assets/icons/flags/ic_flag_fr.png + - assets/icons/flags/ic_flag_it.png + - assets/icons/flags/ic_flag_de.png + - assets/icons/flags/ic_flag_ru.png + - assets/icons/flags/ic_flag_th.png + - assets/icons/flags/ic_flag_pl.png + - assets/icons/flags/ic_flag_ar.png + - assets/icons/flags/ic_flag_ko.png + - assets/icons/flags/ic_flag_sw.png diff --git a/packages/deriv_language_selector/test/cubits/language_cubit_test.dart b/packages/deriv_language_selector/test/cubits/language_cubit_test.dart new file mode 100644 index 000000000..dfdc7129d --- /dev/null +++ b/packages/deriv_language_selector/test/cubits/language_cubit_test.dart @@ -0,0 +1,67 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:deriv_language_selector/deriv_language_selector.dart'; +import 'package:deriv_ui/components/language_selector/models/language_model.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../mocks/mock.dart'; + +void main() { + group('LanguageCubit', () { + late MockLanguageService mockLanguageService; + late LanguageCubit languageCubit; + + setUp(() { + mockLanguageService = MockLanguageService(); + + registerFallbackValue(mockLanguageModel); + + when(() => mockLanguageService.defaultLanguage) + .thenReturn(mockLanguageModel); + when(() => mockLanguageService.languages) + .thenReturn([mockLanguageModel]); + when(() => mockLanguageService.getCurrentLanguage()) + .thenAnswer((_) async => mockLanguageModel); + when(() => mockLanguageService.getActiveLanguages()) + .thenAnswer((_) async {}); + when(() => mockLanguageService.loadAndSetLanguage(any())) + .thenAnswer((_) async {}); + when(() => mockLanguageService.reconnectToServerWithNewLanguage(any())) + .thenAnswer((_) async {}); + + languageCubit = LanguageCubit(languageService: mockLanguageService, onLanguageChanged: (_) {}); + }); + + blocTest( + 'emits [LanguageLoadedState] when updateLanguage is called', + build: () => languageCubit, + seed: () => LanguageLoadedState( + language: LanguageModel(name: 'name', code: 'code', flag: 'flag'), + activeLanguages: mockLanguageService.languages, + ), + act: (LanguageCubit cubit) => cubit.updateLanguage(mockLanguageModel), + expect: () => [ + LanguageLoadedState( + language: mockLanguageModel, + activeLanguages: mockLanguageService.languages, + ), + ], + ); + + blocTest( + 'emits [LanguageLoadedState] when updateActiveLanguages is called', + build: () => languageCubit, + seed: () => LanguageLoadedState( + language: LanguageModel(name: 'name', code: 'code', flag: 'flag'), + activeLanguages: mockLanguageService.languages, + ), + act: (LanguageCubit cubit) => cubit.updateActiveLanguages(), + expect: () => [ + LanguageLoadedState( + language: mockLanguageModel, + activeLanguages: mockLanguageService.languages, + ), + ], + ); + }); +} diff --git a/packages/deriv_language_selector/test/data/language_data_source_test.dart b/packages/deriv_language_selector/test/data/language_data_source_test.dart new file mode 100644 index 000000000..7d54aa1f5 --- /dev/null +++ b/packages/deriv_language_selector/test/data/language_data_source_test.dart @@ -0,0 +1,62 @@ +import 'package:deriv_language_selector/deriv_language_selector.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../mocks/mock.dart'; + +void main() { + group('LanguageDataSource', () { + late SharedPreferences mockSharedPreferences; + late LanguageDataSource dataSource; + + setUp(() { + mockSharedPreferences = MockSharedPreferences(); + dataSource = LanguageDataSource(prefInstance: mockSharedPreferences); + }); + + test('.getLanguage returns stored language (no reload)', () async { + const String expectedLanguage = 'en'; + when(() => mockSharedPreferences.getString(dataSource.localStorageKey)) + .thenReturn(expectedLanguage); + + final String? language = await dataSource.getLanguage(); + + expect(language, expectedLanguage); + verifyNever(() => mockSharedPreferences.reload()); + }); + + test('.getLanguage reloads and returns stored language', () async { + const String expectedLanguage = 'en'; + when(() => mockSharedPreferences.getString(dataSource.localStorageKey)) + .thenReturn(expectedLanguage); + when(() => mockSharedPreferences.reload()).thenAnswer((_) async {}); + + final String? language = await dataSource.getLanguage(shouldReload: true); + + expect(language, expectedLanguage); + verify(() => mockSharedPreferences.reload()).called(1); + }); + + test('.getLanguage returns null if no language stored', () async { + when(() => mockSharedPreferences.getString(dataSource.localStorageKey)) + .thenReturn(null); + + final String? language = await dataSource.getLanguage(); + + expect(language, null); + }); + + test('.setLanguage stores the provided language', () async { + const String language = 'es'; + + when(() => mockSharedPreferences.setString( + dataSource.localStorageKey, language)).thenAnswer((_) async => true); + + await dataSource.setLanguage(language); + + verify(() => mockSharedPreferences.setString( + dataSource.localStorageKey, language)).called(1); + }); + }); +} diff --git a/packages/deriv_language_selector/test/mocks/mock.dart b/packages/deriv_language_selector/test/mocks/mock.dart new file mode 100644 index 000000000..a2ffe7a0f --- /dev/null +++ b/packages/deriv_language_selector/test/mocks/mock.dart @@ -0,0 +1,26 @@ +import 'package:deriv_language_selector/deriv_language_selector.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class MockLanguageService extends Mock implements LanguageService {} + +class MockLanguageDataSource extends Mock implements BaseLanguageDataSource {} + +class MockLanguageRepository extends Mock implements BaseLanguageRepository {} + +class MockSharedPreferences extends Mock implements SharedPreferences {} + +class MockLanguageCubit extends Mock implements LanguageCubit {} + +final LanguageModel mockLanguageModel = LanguageModel( + code: 'en', + name: 'English', + flag: 'assets/icons/flags/ic_flag_en.png', +); + +final LanguageEntity mockLanguageEntity = LanguageEntity( + locale: const Locale('en'), + name: 'English', +); diff --git a/packages/deriv_language_selector/test/presentation/widgets/language_selector_test.dart b/packages/deriv_language_selector/test/presentation/widgets/language_selector_test.dart new file mode 100644 index 000000000..43286f719 --- /dev/null +++ b/packages/deriv_language_selector/test/presentation/widgets/language_selector_test.dart @@ -0,0 +1,148 @@ +import 'package:deriv_language_selector/deriv_language_selector.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../mocks/mock.dart'; + +void main() { + late LanguageCubit mockCubit; + + setUp(() { + mockCubit = MockLanguageCubit(); + + when(() => mockCubit.state).thenReturn( + LanguageLoadedState( + language: mockLanguageModel, + activeLanguages: [ + mockLanguageModel, + ], + ), + ); + + when(() => mockCubit.stream).thenAnswer( + (_) => Stream.fromIterable( + [ + LanguageLoadedState( + language: mockLanguageModel, + activeLanguages: [ + mockLanguageModel, + ], + ), + ], + ), + ); + + when(() => mockCubit.close()).thenAnswer((_) async {}); + }); + group('LanguageSelector.button', () { + testWidgets('''triggers `showExpandableLanguageBottomSheet` if + custom bottom sheet handler not provided''', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BlocProvider( + create: (_) => mockCubit, + child: const LanguageSelector.button( + bottomsheetTitle: 'Select Language', + ), + ), + ), + ), + ); + + final Finder buttonFinder = find.byType(LanguageSelectorWidget); + final Finder bottomsheetFinder = find.byType(ExpandableBottomSheet); + + expect(buttonFinder, findsOneWidget); + + await tester.tap(buttonFinder); + + await tester.pumpAndSettle(); + + expect(bottomsheetFinder, findsOneWidget); + }); + + testWidgets('triggers custom bottom sheet handler if provided', + (WidgetTester tester) async { + bool customBottmsheetCalled = false; + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BlocProvider( + create: (_) => mockCubit, + child: LanguageSelector.button( + bottomsheetTitle: 'Select Language', + showLanguageBottomSheet: + (Widget bottomsheet, BuildContext context) { + customBottmsheetCalled = true; + + showModalBottomSheet( + context: context, + builder: (BuildContext context) => bottomsheet, + ); + }, + ), + ), + ), + ), + ); + + final Finder buttonFinder = find.byType(LanguageSelectorWidget); + + expect(buttonFinder, findsOneWidget); + + await tester.tap(buttonFinder); + + await tester.pumpAndSettle(); + + expect(customBottmsheetCalled, true); + }); + + testWidgets('Button displays current language', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BlocProvider( + create: (_) => mockCubit, + child: const LanguageSelector.button( + bottomsheetTitle: 'Select Language', + ), + ), + ), + ), + ); + + final Finder buttonFinder = + find.text(mockLanguageModel.code.toUpperCase()); + expect(buttonFinder, findsOneWidget); + }); + }); + + group('LanguageSelector.bottomSheet', () { + testWidgets('displays list of active languages', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BlocProvider( + create: (_) => mockCubit, + child: const LanguageSelector.bottomSheet(), + ), + ), + ), + ); + + final Finder itemFinder = + find.text(mockCubit.state.activeLanguages.first.name); + final Finder languageListFinder = find.byType(LanguageItemList); + + expect(itemFinder, findsOneWidget); + expect(languageListFinder, findsOneWidget); + }); + }); +} diff --git a/packages/deriv_language_selector/test/services/language_service_test.dart b/packages/deriv_language_selector/test/services/language_service_test.dart new file mode 100644 index 000000000..39ef147a3 --- /dev/null +++ b/packages/deriv_language_selector/test/services/language_service_test.dart @@ -0,0 +1,95 @@ +import 'package:deriv_language_selector/deriv_language_selector.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../mocks/mock.dart'; + +void main() { + group('LanguageService', () { + late MockLanguageRepository mockLanguageRepository; + late MockLanguageDataSource mockLanguageDataSource; + late LanguageService languageService; + + setUp(() { + registerFallbackValue(const Locale('en')); + + mockLanguageRepository = MockLanguageRepository(); + mockLanguageDataSource = MockLanguageDataSource(); + languageService = LanguageService( + languageRepository: mockLanguageRepository, + languageDataSource: mockLanguageDataSource, + supportedLanguages: [ + mockLanguageEntity + ], // Provide a list of supported languages + ); + }); + + test('.getCurrentLanguage returns default language when no language is set', + () async { + when(() => mockLanguageDataSource.getLanguage()) + .thenAnswer((_) async => null); + + expect(await languageService.getCurrentLanguage(), + languageService.defaultLanguage); + }); + + test('.getCurrentLanguage returns the set language', () async { + when(() => mockLanguageDataSource.getLanguage()) + .thenAnswer((_) async => mockLanguageModel.code); + + expect( + await languageService.getCurrentLanguage(), + isA().having( + (LanguageModel p0) => p0.code, + 'Language code', + mockLanguageModel.code, + )); + }); + + test('.loadAndSetLanguage calls load and set language', () async { + when(() => mockLanguageRepository.loadLanguage(any())) + .thenAnswer((_) async {}); + when(() => mockLanguageDataSource.setLanguage(mockLanguageModel.code)) + .thenAnswer((_) async {}); + + await languageService.loadAndSetLanguage(mockLanguageModel); + + verify(() => mockLanguageDataSource.setLanguage(mockLanguageModel.code)) + .called(1); + verify(() => mockLanguageRepository + .loadLanguage(Locale(mockLanguageModel.code))).called(1); + }); + + test( + '.reconnectToServerWithNewLanguage calls `reconnectToServerWithNewLanguage` from `LanguageRepository`', + () async { + when(() => mockLanguageRepository.reconnectToServerWithNewLanguage(any())) + .thenAnswer((_) async {}); + + languageService.reconnectToServerWithNewLanguage(mockLanguageModel); + + verify(() => mockLanguageRepository.reconnectToServerWithNewLanguage( + Locale(mockLanguageModel.code))).called(1); + }); + + test('.getActiveLanguages sets the active languages', () async { + when(() => mockLanguageRepository.getSupportedLanguagesFromServer( + onLanguageFetched: any(named: 'onLanguageFetched'), + )).thenAnswer((_) async => [mockLanguageModel.code]); + + await languageService.getActiveLanguages(); + + expect( + languageService.languages, + isA>().having( + (List p0) => p0 + .where((LanguageModel element) => + element.code == mockLanguageModel.code) + .isNotEmpty, + 'description', + true)); + }); + }); +} diff --git a/packages/deriv_lint/CHANGELOG.md b/packages/deriv_lint/CHANGELOG.md new file mode 100644 index 000000000..139597f9c --- /dev/null +++ b/packages/deriv_lint/CHANGELOG.md @@ -0,0 +1,2 @@ + + diff --git a/packages/deriv_live_chat/CHANGELOG.md b/packages/deriv_live_chat/CHANGELOG.md index d244c1fef..9e1f4e26c 100644 --- a/packages/deriv_live_chat/CHANGELOG.md +++ b/packages/deriv_live_chat/CHANGELOG.md @@ -1,3 +1,19 @@ +## 0.0.2 + + - **FEAT**(deriv_live_chat): Add reloadChatView method ([#791](https://github.com/regentmarkets/flutter-deriv-packages/issues/791)). ([f180cc56](https://github.com/regentmarkets/flutter-deriv-packages/commit/f180cc5602801c1a3e81a247053c1b8c9121a532)) + +## 0.0.1+3 + + - **FIX**(deriv_live_chat): Clear the session completely on a clear callback. ([#780](https://github.com/regentmarkets/flutter-deriv-packages/issues/780)). ([3b80c767](https://github.com/regentmarkets/flutter-deriv-packages/commit/3b80c76702956fcb4868b7fa8a75f3fa9e852209)) + +## 0.0.1+2 + + - **REFACTOR**(deriv_live_chat): Glitch_while_clicking_on_live_chat_icon_IOS ([#585](https://github.com/regentmarkets/flutter-deriv-packages/issues/585)). ([957faa3f](https://github.com/regentmarkets/flutter-deriv-packages/commit/957faa3fb16be0174e5e529cbcc068de31cc7bb3)) + +## 0.0.1+1 + + - **FIX**(deriv_live_chat): [DERG-750] Glitch on clicking LiveChat icon ([#307](https://github.com/regentmarkets/flutter-deriv-packages/issues/307)). ([658716d0](https://github.com/regentmarkets/flutter-deriv-packages/commit/658716d09caa60dbc02fdedeb7c7b452cbdc06c6)) + ## 0.0.1 - The initial release of the plugin. diff --git a/packages/deriv_live_chat/android/src/main/kotlin/com/deriv/app/deriv_live_chat/DerivLiveChatPlugin.kt b/packages/deriv_live_chat/android/src/main/kotlin/com/deriv/app/deriv_live_chat/DerivLiveChatPlugin.kt index 9151d6653..715449d73 100644 --- a/packages/deriv_live_chat/android/src/main/kotlin/com/deriv/app/deriv_live_chat/DerivLiveChatPlugin.kt +++ b/packages/deriv_live_chat/android/src/main/kotlin/com/deriv/app/deriv_live_chat/DerivLiveChatPlugin.kt @@ -91,7 +91,12 @@ class DerivLiveChatPlugin : FlutterPlugin, MethodCallHandler, } else if (call.method.equals("clear_live_chat_view")) { ChatWindowView.clearSession(chatWindowView?.context) chatWindowView?.reload() + chatWindowView = null + result.success(null) + } else if (call.method.equals("reload_live_chat_view")) { + chatWindowView?.reload() + chatWindowView = null result.success(null) } else { result.notImplemented() diff --git a/packages/deriv_live_chat/ios/Classes/SwiftDerivLiveChatPlugin.swift b/packages/deriv_live_chat/ios/Classes/SwiftDerivLiveChatPlugin.swift index e34c0a610..1ed1f889a 100644 --- a/packages/deriv_live_chat/ios/Classes/SwiftDerivLiveChatPlugin.swift +++ b/packages/deriv_live_chat/ios/Classes/SwiftDerivLiveChatPlugin.swift @@ -48,9 +48,9 @@ public class SwiftDerivLiveChatPlugin: NSObject, FlutterPlugin, LiveChatDelegate //Change colour of top and bottom notch for dark theme. let window = UIApplication.shared.windows.filter { $0.isKeyWindow }.first - let colorComponent = 37.0 / 255.0 - - window?.rootViewController?.view.backgroundColor = UIColor.init(red: colorComponent, green: colorComponent, blue: colorComponent, alpha: 1) + UIView.transition(with: window!.rootViewController!.view, duration: 1, options: .transitionCrossDissolve, animations: { + let colorComponent = 37.0 / 255.0 + window?.rootViewController?.view.backgroundColor = UIColor(red: colorComponent, green: colorComponent, blue: colorComponent, alpha: 1)}, completion: nil) result(nil) } @@ -60,6 +60,9 @@ public class SwiftDerivLiveChatPlugin: NSObject, FlutterPlugin, LiveChatDelegate case "clear_live_chat_view": LiveChat.clearSession() result(nil) + case "reload_live_chat_view": + LiveChat.dismissChat() + result(nil) default: result(FlutterMethodNotImplemented) diff --git a/packages/deriv_live_chat/ios/deriv_live_chat.podspec b/packages/deriv_live_chat/ios/deriv_live_chat.podspec index f9196f243..2aaf4f42e 100644 --- a/packages/deriv_live_chat/ios/deriv_live_chat.podspec +++ b/packages/deriv_live_chat/ios/deriv_live_chat.podspec @@ -4,7 +4,7 @@ # Pod::Spec.new do |s| s.name = 'deriv_live_chat' - s.version = '0.0.1' + s.version = '0.0.1+1' s.summary = 'A new flutter plugin project.' s.description = <<-DESC A new flutter plugin project. diff --git a/packages/deriv_live_chat/lib/deriv_live_chat.dart b/packages/deriv_live_chat/lib/deriv_live_chat.dart index 78599e411..93b5b5cc9 100644 --- a/packages/deriv_live_chat/lib/deriv_live_chat.dart +++ b/packages/deriv_live_chat/lib/deriv_live_chat.dart @@ -37,6 +37,10 @@ class DerivLiveChat { static Future clearChatView() async => _liveChatMethodChannel.invokeMethod('clear_live_chat_view'); + /// Reload chat window by invoking method channel. + static Future reloadChatView() async => + _liveChatMethodChannel.invokeMethod('reload_live_chat_view'); + /// Here we are receiving events stream. static Stream? get onEventReceived => _liveChatEventChannel.receiveBroadcastStream(); diff --git a/packages/deriv_live_chat/pubspec.yaml b/packages/deriv_live_chat/pubspec.yaml index 0a33777ff..891e254af 100644 --- a/packages/deriv_live_chat/pubspec.yaml +++ b/packages/deriv_live_chat/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_live_chat description: A plugin for live chat SDK support to dart. -version: 0.0.1 +version: 0.0.2 homepage: https://deriv.com/ publish_to: "none" @@ -20,7 +20,7 @@ dev_dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_lint - ref: dev + ref: deriv_lint-v1.0.0 flutter: plugin: diff --git a/packages/deriv_localizations/.gitignore b/packages/deriv_localizations/.gitignore new file mode 100644 index 000000000..24476c5d1 --- /dev/null +++ b/packages/deriv_localizations/.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/packages/deriv_localizations/.metadata b/packages/deriv_localizations/.metadata new file mode 100644 index 000000000..ab3e1c09c --- /dev/null +++ b/packages/deriv_localizations/.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: "ead455963c12b453cdb2358cad34969c76daf180" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: ead455963c12b453cdb2358cad34969c76daf180 + base_revision: ead455963c12b453cdb2358cad34969c76daf180 + - platform: android + create_revision: ead455963c12b453cdb2358cad34969c76daf180 + base_revision: ead455963c12b453cdb2358cad34969c76daf180 + - platform: ios + create_revision: ead455963c12b453cdb2358cad34969c76daf180 + base_revision: ead455963c12b453cdb2358cad34969c76daf180 + - platform: linux + create_revision: ead455963c12b453cdb2358cad34969c76daf180 + base_revision: ead455963c12b453cdb2358cad34969c76daf180 + - platform: macos + create_revision: ead455963c12b453cdb2358cad34969c76daf180 + base_revision: ead455963c12b453cdb2358cad34969c76daf180 + - platform: web + create_revision: ead455963c12b453cdb2358cad34969c76daf180 + base_revision: ead455963c12b453cdb2358cad34969c76daf180 + - platform: windows + create_revision: ead455963c12b453cdb2358cad34969c76daf180 + base_revision: ead455963c12b453cdb2358cad34969c76daf180 + + # 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/packages/deriv_localizations/CHANGELOG.md b/packages/deriv_localizations/CHANGELOG.md new file mode 100644 index 000000000..b59369002 --- /dev/null +++ b/packages/deriv_localizations/CHANGELOG.md @@ -0,0 +1,100 @@ +## 1.7.2 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#887](https://github.com/regentmarkets/flutter-deriv-packages/issues/887)). ([ba1b75b8](https://github.com/regentmarkets/flutter-deriv-packages/commit/ba1b75b85e103a46efab2a5224f04a280b282ee1)) + +## 1.7.1 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#881](https://github.com/regentmarkets/flutter-deriv-packages/issues/881)). ([ec78f45b](https://github.com/regentmarkets/flutter-deriv-packages/commit/ec78f45b46dfe02e99995c10699289193f145a75)) + +## 1.7.0 + + - **FEAT**(deriv_localizations): Update localizations for `deriv_mobile_chart_wrapper` ([#830](https://github.com/regentmarkets/flutter-deriv-packages/issues/830)). ([4adde10d](https://github.com/regentmarkets/flutter-deriv-packages/commit/4adde10d42b8cf9f2540782634c9f8527c17620d)) + +## 1.6.1 + + - **REFACTOR**(deriv_localizations): update intl version ([#849](https://github.com/regentmarkets/flutter-deriv-packages/issues/849)). ([0adb30fc](https://github.com/regentmarkets/flutter-deriv-packages/commit/0adb30fcdcba69f4bd71ede781a7db7490976827)) + +## 1.6.0 + + - **FEAT**(deriv_localizations): Update localizations generated file to get the new strings ([#808](https://github.com/regentmarkets/flutter-deriv-packages/issues/808)). ([28ae98bc](https://github.com/regentmarkets/flutter-deriv-packages/commit/28ae98bcd78ab725c3f35d6a88175c0e85be4c94)) + +## 1.5.6 + + - **FIX**(deriv_localizations): add strings for chart indicators ([#768](https://github.com/regentmarkets/flutter-deriv-packages/issues/768)). ([e4d52121](https://github.com/regentmarkets/flutter-deriv-packages/commit/e4d5212170996d14f834cc285f047ae821da7a71)) + +## 1.5.5 + + - **FIX**(deriv_localizations): update numpad range values ([#742](https://github.com/regentmarkets/flutter-deriv-packages/issues/742)). ([5ad26f52](https://github.com/regentmarkets/flutter-deriv-packages/commit/5ad26f52c897397bc544fe4b23ca805e90cba66a)) + +## 1.5.4 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#739](https://github.com/regentmarkets/flutter-deriv-packages/issues/739)). ([f8ef615a](https://github.com/regentmarkets/flutter-deriv-packages/commit/f8ef615a61b5abd5c2595a844d02543ce2086a6c)) + +## 1.5.3 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#708](https://github.com/regentmarkets/flutter-deriv-packages/issues/708)). ([8d8a9093](https://github.com/regentmarkets/flutter-deriv-packages/commit/8d8a90931441a4b0e9caeac437954010e4f8763b)) + +## 1.5.2 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#697](https://github.com/regentmarkets/flutter-deriv-packages/issues/697)). ([0aca8f2f](https://github.com/regentmarkets/flutter-deriv-packages/commit/0aca8f2fd4f0e3a16ab54a0bf040c1fabcff2324)) + +## 1.5.1 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#648](https://github.com/regentmarkets/flutter-deriv-packages/issues/648)). ([c925c22a](https://github.com/regentmarkets/flutter-deriv-packages/commit/c925c22a97e6e13568fefa0d86d993985093617c)) + +## 1.5.0 + + - **FEAT**(deriv_localizations): add localization for deriv mobile chart wrapper package ([#627](https://github.com/regentmarkets/flutter-deriv-packages/issues/627)). ([33f5e3a1](https://github.com/regentmarkets/flutter-deriv-packages/commit/33f5e3a1bc0765cb8559b5a39e300b8c088aa705)) + +## 1.4.4 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#617](https://github.com/regentmarkets/flutter-deriv-packages/issues/617)). ([1d5e5f14](https://github.com/regentmarkets/flutter-deriv-packages/commit/1d5e5f141640aa00546dcbb31d2db8eb9a994452)) + +## 1.4.3 + + - **REFACTOR**(deriv_localizations): Update p2p strings for passkeys ([#615](https://github.com/regentmarkets/flutter-deriv-packages/issues/615)). ([5fd85af2](https://github.com/regentmarkets/flutter-deriv-packages/commit/5fd85af24394ea68b8b0a7abc854b9c33b791c26)) + +## 1.4.2 + + - **FIX**(deriv-localization): fix passkey button strings ([#606](https://github.com/regentmarkets/flutter-deriv-packages/issues/606)). ([ed2a7ea9](https://github.com/regentmarkets/flutter-deriv-packages/commit/ed2a7ea958e34aa027ecb9ef6919f04fd5c7d5f1)) + +## 1.4.1 + + - **REFACTOR**(deriv_auth): update localization ([#594](https://github.com/regentmarkets/flutter-deriv-packages/issues/594)). ([5204c74f](https://github.com/regentmarkets/flutter-deriv-packages/commit/5204c74f609d946ea797e766e6bb652d82f76930)) + +## 1.4.0 + + - **FEAT**(deriv_passkeys): [UPM-547] Deriv passkeys package ([#425](https://github.com/regentmarkets/flutter-deriv-packages/issues/425)). ([c5509175](https://github.com/regentmarkets/flutter-deriv-packages/commit/c5509175edb6a94122cce6fe6f63a43d44904dc9)) + +## 1.3.2 + + - **REFACTOR**(deriv_localizations): Updating deriv localizations for passkeys package ([#581](https://github.com/regentmarkets/flutter-deriv-packages/issues/581)). ([0bf743c3](https://github.com/regentmarkets/flutter-deriv-packages/commit/0bf743c3b7a65f70935b32b68b7062ed07a1ae72)) + +## 1.3.1 + + - **REFACTOR**(deriv_localizations): [UPM-925] Updating passkeys strings in deriv localizations ([#568](https://github.com/regentmarkets/flutter-deriv-packages/issues/568)). ([3c8cabd1](https://github.com/regentmarkets/flutter-deriv-packages/commit/3c8cabd11fdfd302f129ed8a53e73c0c7e3fd7b5)) + +## 1.3.0 + + - **FEAT**(deriv_localization): Update localization string for deriv_auth ([#571](https://github.com/regentmarkets/flutter-deriv-packages/issues/571)). ([d2d76902](https://github.com/regentmarkets/flutter-deriv-packages/commit/d2d769023b651419270842d450a56a4bbd264327)) + +## 1.2.0 + + - **FEAT**(deriv_localizations): [UPM-856] Adding deriv passkeys localizations. ([#544](https://github.com/regentmarkets/flutter-deriv-packages/issues/544)). ([9a3b42e2](https://github.com/regentmarkets/flutter-deriv-packages/commit/9a3b42e2468c9586eae4eb03ba8a7b2712f44de2)) + +## 1.1.3 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#541](https://github.com/regentmarkets/flutter-deriv-packages/issues/541)). ([2129cb76](https://github.com/regentmarkets/flutter-deriv-packages/commit/2129cb76fce1ce120af0f1b357f24ff343dbd803)) + +## 1.1.2 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#534](https://github.com/regentmarkets/flutter-deriv-packages/issues/534)). ([bfe24625](https://github.com/regentmarkets/flutter-deriv-packages/commit/bfe24625b6c9076514f559d403f9ea7d339dc6be)) + +## 1.1.1 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated [#466](https://github.com/regentmarkets/flutter-deriv-packages/issues/466). ([df2f5cad](https://github.com/regentmarkets/flutter-deriv-packages/commit/df2f5cad5f4f2e32b34258188718e9f1c5406caa)) + +## 1.1.0 + + - **FEAT**(deriv_localizations): [MOBC-669] Added the localizations package. ([#370](https://github.com/regentmarkets/flutter-deriv-packages/issues/370)). ([278d3386](https://github.com/regentmarkets/flutter-deriv-packages/commit/278d33862c798a0c03742feeedb31ff6b6c1c2ff)) + diff --git a/packages/deriv_localizations/README.md b/packages/deriv_localizations/README.md new file mode 100644 index 000000000..d815e1532 --- /dev/null +++ b/packages/deriv_localizations/README.md @@ -0,0 +1,3 @@ +# deriv_localizations + +This package contains the localization arb(coming from Crowdin) and dart generated files for flutter_deriv_packages. \ No newline at end of file diff --git a/packages/deriv_localizations/analysis_options.yaml b/packages/deriv_localizations/analysis_options.yaml new file mode 100644 index 000000000..0d2902135 --- /dev/null +++ b/packages/deriv_localizations/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/deriv_localizations/l10n.sh b/packages/deriv_localizations/l10n.sh new file mode 100755 index 000000000..bf2e9fe61 --- /dev/null +++ b/packages/deriv_localizations/l10n.sh @@ -0,0 +1,51 @@ +#!/bin/bash + + +# Directories containing ARB files +feature_dirs=("deriv_auth" "deriv_passkeys" "deriv_mobile_chart_wrapper") + +# Base localization directory +base_l10n_dir="lib/l10n" + +# Function to convert snake_case to PascalCase +snake_to_pascal() { + echo "$1" | awk 'BEGIN{FS=OFS="_"} {for(i=1; i<=NF; i++) $i=toupper(substr($i,1,1)) substr($i,2)} 1' | sed 's/_//g' +} + +for dir in "${feature_dirs[@]}"; do + arb_dir="$base_l10n_dir/$dir" + output_dir="$base_l10n_dir/generated/$dir" + output_file="${dir}_localizations.dart" + output_class="$(snake_to_pascal $dir)Localizations" + + # Execute the command + flutter gen-l10n \ + --arb-dir="$arb_dir" \ + --output-localization-file="$output_file" \ + --output-class="$output_class" \ + --output-dir="$output_dir" \ + --no-synthetic-package +done + +ls $base_l10n_dir/generated + +# Add the generated localization files to Git +git add "$base_l10n_dir/generated" + +# Create deriv_localizations.dart barrel file under lib folder. +localizations_file="lib/deriv_localizations.dart" +echo "// Generated file for exporting localization classes" > $localizations_file + +for dir in "${feature_dirs[@]}"; do + echo "export 'l10n/generated/$dir/${dir}_localizations.dart';" >> $localizations_file +done + +echo "Created deriv_localizations.dart barrel file with exports." + +# Add and commit the deriv_localizations.dart barrel file to Git +git add $localizations_file + +# Commit the changes +git commit -m "Update localizations" + +echo "Localization generation complete." diff --git a/packages/deriv_localizations/lib/deriv_localizations.dart b/packages/deriv_localizations/lib/deriv_localizations.dart new file mode 100644 index 000000000..72b4f56fb --- /dev/null +++ b/packages/deriv_localizations/lib/deriv_localizations.dart @@ -0,0 +1,4 @@ +// Generated file for exporting localization classes +export 'l10n/generated/deriv_auth/deriv_auth_localizations.dart'; +export 'l10n/generated/deriv_passkeys/deriv_passkeys_localizations.dart'; +export 'l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations.dart'; diff --git a/packages/deriv_localizations/lib/l10n/deriv_auth/app_ar.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_ar.arb new file mode 100644 index 000000000..312e36431 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_ar.arb @@ -0,0 +1,117 @@ +{ + "labelNotAvailable": "غير متاح", + "warnNotAvailableCountriesTitle": "{app} غير متاح في بلدك", + "@warnNotAvailableCountriesTitle": { + "description": "A message with a single parameter", + "placeholders": { + "app": { + "type": "String", + "example": "Deriv GO" + } + } + }, + "actionOk": "حسنا", + "warnNotAvailableCountries": "إذا كانت لديك أي أسئلة، فاتصل بنا عبر ", + "labelLiveChat": "دردشة مباشرة", + "actionSignUpForFree": "قم بالتسجيل مجانًا", + "actionLogin": "تسجيل دخول", + "labelTwoFactorAuth": "المصادقة الثنائية (2FA)", + "informEnterTwoFactorAuthCode": "أدخل الرمز المكون من 6 أرقام من تطبيق المصادقة على هاتفك.", + "labelTwoFactorAuthenticationCode": "كود 2FA", + "actionProceed": "تقدم", + "labelLogIn": "تسجيل دخول", + "informLoginOptions": "أو قم بتسجيل الدخول باستخدام", + "labelEmail": "البريد الإلكتروني", + "labelPassword": "كلمة المرور", + "actionForgotPassword": "هل نسيت كلمة المرور؟", + "labelDontHaveAnAccountYet": "ليس لديك حساب حتى الآن؟", + "actionCreateANewAccount": "إنشاء حساب جديد", + "informInvalidEmailFormat": "أدخل عنوان بريد إلكتروني صالح", + "warnPasswordLength": "يجب إدخال 8-25 حرفًا.", + "labelResetPassword": "إعادة تعيين كلمة المرور", + "labelChooseNewPass": "اختر كلمة مرور جديدة", + "labelCreatePass": "كلمة المرور", + "informYourPassHasBeenReset": "تم إعادة تعيين كلمة المرور الخاصة بك", + "informRedirectLogin": "ستحتاج إلى تسجيل الدخول باستخدام كلمة المرور الجديدة. انتظر، سنعيد توجيهك.", + "actionResetPass": "إعادة تعيين كلمة المرور الخاصة بي", + "informInvalidPasswordFormat": "يرجى إدخال صيغة كلمة مرور صالحة", + "labelCheckEmail": "تحقق من بريدك الإلكتروني", + "informSendResetPasswordEmail": "لقد أرسلنا رسالة إلى {email} مع رابط لإعادة تعيين كلمة المرور الخاصة بك.", + "@informSendResetPasswordEmail": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "informResetPassByEmail": "سنرسل لك تعليمات عبر البريد الإلكتروني لإعادة تعيين كلمة المرور الخاصة بك.", + "labelSelectCountry": "أين تعيش؟", + "labelChooseCountry": "اختر البلد", + "warnCountryNotAvailable": "للأسف، Deriv غير متوفر في بلدك.", + "actionNext": "التالي", + "labelEmailIssueHeader": "إذا لم تشاهد بريدًا إلكترونيًا منا في غضون بضع دقائق، فقد تحدث بعض الأشياء:", + "labelEmailIssueSpam": "البريد الإلكتروني موجود في مجلد الرسائل غير المرغوب فيها (أحيانًا تضيع الأشياء هناك).", + "labelEmailIssueWrongEmail": "لقد أعطيتنا عن طريق الخطأ عنوان بريد إلكتروني آخر (عادةً ما يكون عنوان عمل أو عنوان شخصي بدلاً من العنوان الذي قصدته).", + "labelEmailIssueTypo": "عنوان البريد الإلكتروني الذي أدخلته به خطأ أو خطأ مطبعي (يحدث لأفضل منا).", + "labelEmailIssueFirewall": "لا يمكننا تسليم البريد الإلكتروني إلى هذا العنوان (عادةً بسبب جدران الحماية أو التصفية).", + "actionReenterEmail": "أعد إدخال بريدك الإلكتروني وحاول مرة أخرى", + "labelKeepPassword": "حافظ على أمان حسابك باستخدام كلمة مرور", + "labelCreatePassword": "قم بإنشاء كلمة مرور", + "actionStartTrading": "ابدأ التداول", + "actionPrevious": "السابق", + "labelSignUp": "قم بالتسجيل", + "labelOrSignUpWith": "أو قم بالتسجيل باستخدام", + "labelReferralInfoTitle": "رمز الإحالة التابع", + "infoReferralInfoDescription": "رمز أبجدي رقمي مقدم من شركة تابعة لـ Deriv، ينطبق على عمليات الاشتراك في البريد الإلكتروني فقط.", + "labelGotReferralCode": "هل لديك رمز إحالة؟", + "labelHaveAccount": "لديك حساب بالفعل؟", + "actionCreateAccount": "إنشاء حساب تجريبي مجاني", + "informInvalidReferralCode": "رمز الإحالة الذي أدخلته غير صالح. تحقق وحاول مرة أخرى.", + "labelVerifyYourEmail": "تحقق من بريدك الإلكتروني", + "labelThanksEmail": "شكرًا لك على التحقق من بريدك الإلكتروني", + "informLetsContinue": "دعونا نواصل.", + "actionContinue": "استمر", + "labelSearchCountry": "ابحث عن البلد", + "informVerificationEmailSent": "لقد أرسلنا رسالة إلى {email} رابط لتفعيل حسابك.", + "@informVerificationEmailSent": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "actionEmailNotReceived": "لم تستلم بريدك الإلكتروني؟", + "informPasswordPolicy": "يجب أن تحتوي كلمة المرور الخاصة بك على:", + "informPasswordPolicyLength": "8-25 حرفًا", + "informPasswordPolicyLowerAndUpper": "الأحرف الكبيرة والصغيرة", + "informPasswordPolicyNumber": "رقم واحد على الأقل", + "warnPasswordContainsSymbol": "استخدم الرموز لكلمة مرور قوية.", + "labelReferralCode": "رمز الإحالة", + "actionTryAgain": "حاول مرة أخرى", + "informInvalid2FACode": "الرمز الذي أدخلته غير صالح. تحقق وحاول مرة أخرى.", + "informFailedAuthentication": "قد يكون بريدك الإلكتروني أو كلمة المرور غير صحيحة. هل قمت بالتسجيل باستخدام حساب اجتماعي؟ تحقق وحاول مرة أخرى.", + "informDeactivatedAccount": "تم إلغاء تنشيط حسابك.", + "informFailedAuthorization": "فشلت عملية التخويل.", + "informInvalidResidence": "إقامة غير صالحة.", + "informInvalidCredentials": "بيانات اعتماد غير صالحة.", + "informMissingOtp": "كلمة مرور مفقودة لمرة واحدة.", + "informSelfClosed": "تم إغلاق الحساب الخاص بك.", + "informUnexpectedError": "حدث خطأ غير متوقع.", + "informUnsupportedCountry": "بلدك غير مدعوم.", + "informExpiredAccount": "انتهت صلاحية حسابك", + "labelCountryConsentBrazil": "أؤكد بموجب هذا أن طلبي لفتح حساب مع Deriv لتداول منتجات OTC الصادرة والمعروضة حصريًا خارج البرازيل قد بادرت به. أفهم تمامًا أن Deriv لا تخضع للتنظيم من قبل CVM ومن خلال الاتصال بـ Deriv أعتزم إقامة علاقة مع شركة أجنبية.", + "informConnectionError": "خطأ اتصال. يرجى المحاولة مرة أخرى لاحقًا.", + "informSwitchAccountError": "خطأ في تبديل الحساب. يرجى المحاولة مرة أخرى لاحقًا.", + "labelDeveloper": "المطوّر", + "labelEndpoint": "نقطة النهاية", + "semanticEndpoint": "نقطة النهاية", + "warnInvalidEndpoint": "نقطة نهاية غير صالحة", + "labelApplicationID": "معرف التطبيق", + "semanticApplicationID": "معرف التطبيق", + "warnInvalidApplicationID": "معرف تطبيق غير صالح", + "labelLanguage": "اللغة" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_auth/app_bn.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_bn.arb new file mode 100644 index 000000000..42ac04645 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_bn.arb @@ -0,0 +1,117 @@ +{ + "labelNotAvailable": "উপলব্ধ নেই", + "warnNotAvailableCountriesTitle": "{app} আপনার দেশে উপলব্ধ নেই", + "@warnNotAvailableCountriesTitle": { + "description": "A message with a single parameter", + "placeholders": { + "app": { + "type": "String", + "example": "Deriv GO" + } + } + }, + "actionOk": "ওকে", + "warnNotAvailableCountries": "আপনার যদি কোনও প্রশ্ন থাকে তবে আমাদের মাধ্যমে যোগাযোগ করুন ", + "labelLiveChat": "লাইভ চ্যাট", + "actionSignUpForFree": "বিনামূল্যে সাইন আপ করুন", + "actionLogin": "লগ ইন", + "labelTwoFactorAuth": "দুই ফ্যাক্টর প্রমাণীকরণ", + "informEnterTwoFactorAuthCode": "আপনার ফোনে প্রমাণীকরণকারী অ্যাপ থেকে 6-অঙ্কের কোড লিখুন।", + "labelTwoFactorAuthenticationCode": "2FA কোড", + "actionProceed": "এগিয়ে যাও", + "labelLogIn": "লগ ইন", + "informLoginOptions": "অথবা লগ ইন করুন", + "labelEmail": "ই-মেইল", + "labelPassword": "পাসওয়ার্ড", + "actionForgotPassword": "পাসওয়ার্ড ভুলে গেছেন?", + "labelDontHaveAnAccountYet": "এখনও অ্যাকাউন্ট নেই?", + "actionCreateANewAccount": "একটি নতুন অ্যাকাউন্ট তৈরি করুন", + "informInvalidEmailFormat": "একটি বৈধ ইমেল ঠিকানা লিখুন", + "warnPasswordLength": "আপনার 8-25 অক্ষর লিখতে হবে।", + "labelResetPassword": "পাসওয়ার্ড রিসেট", + "labelChooseNewPass": "একটি নতুন পাসওয়ার্ড বেছে নিন", + "labelCreatePass": "পাসওয়ার্ড", + "informYourPassHasBeenReset": "আপনার পাসওয়ার্ড পুনরায় সেট করা হয়েছে", + "informRedirectLogin": "আপনাকে আপনার নতুন পাসওয়ার্ড দিয়ে লগ ইন করতে হবে। ধরে রাখুন, আমরা আপনাকে পুনর্নির্দেশ করছি।", + "actionResetPass": "আমার পাসওয়ার্ড রিসেট", + "informInvalidPasswordFormat": "অনুগ্রহ করে একটি বৈধ পাসওয়ার্ড ফর্ম্যাট লিখুন", + "labelCheckEmail": "আপনার ইমেইল চেক করুন", + "informSendResetPasswordEmail": "আমরা আপনার পাসওয়ার্ড পুনরায় সেট {email} করার জন্য একটি লিঙ্ক সহ একটি বার্তা পাঠিয়েছি।", + "@informSendResetPasswordEmail": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "informResetPassByEmail": "আপনার পাসওয়ার্ড পুনরায় সেট করার জন্য আমরা আপনাকে নির্দেশাবলী ইমেইল করব।", + "labelSelectCountry": "আপনি কোথায় থাকেন?", + "labelChooseCountry": "দেশ নির্বাচন করুন", + "warnCountryNotAvailable": "দুর্ভাগ্যবশত, Deriv আপনার দেশে পাওয়া যায় না।", + "actionNext": "পরবর্তী", + "labelEmailIssueHeader": "যদি আপনি কয়েক মিনিটের মধ্যে আমাদের কাছ থেকে কোন ইমেল না দেখেন, তবে কিছু ঘটনা ঘটতে পারত:", + "labelEmailIssueSpam": "ইমেলটি আপনার স্প্যাম ফোল্ডারে রয়েছে (কখনও কখনও কিছু সেখানে হারিয়ে যায়)।", + "labelEmailIssueWrongEmail": "আপনি ঘটনাক্রমে আমাদের অন্য একটি ইমেল ঠিকানা দিয়েছেন (সাধারণত একটি কাজ বা আপনি বোঝানো এক পরিবর্তে একটি ব্যক্তিগত এক)।", + "labelEmailIssueTypo": "আপনার প্রবেশ করা ইমেল ঠিকানাটি একটি ভুল বা টাইপো ছিল (আমাদের সেরা ক্ষেত্রে ঘটে)।", + "labelEmailIssueFirewall": "আমরা এই ঠিকানায় ইমেলটি সরবরাহ করতে পারি না (সাধারণত ফায়ারওয়াল বা ফিল্টারিংয়ের কারণে)।", + "actionReenterEmail": "আপনার ইমেইল পুনরায় প্রবেশ করুন এবং পুনরায় চেষ্টা করুন", + "labelKeepPassword": "পাসওয়ার্ড দিয়ে আপনার অ্যাকাউন্টকে নিরাপদ রাখুন", + "labelCreatePassword": "পাসওয়ার্ড তৈরি করুন", + "actionStartTrading": "ট্রেডিং শুরু করুন", + "actionPrevious": "পূর্ববর্তী", + "labelSignUp": "সাইন আপ", + "labelOrSignUpWith": "অথবা সাইন আপ করুন", + "labelReferralInfoTitle": "অ্যাফিলিয়েট রেফারেল", + "infoReferralInfoDescription": "একটি Deriv অ্যাফিলিয়েট দ্বারা প্রদত্ত একটি আলফানিমেরিক কোড, শুধুমাত্র ইমেল সাইন আপের জন্য প্রযোজ্য।", + "labelGotReferralCode": "একটি রেফারেল কোড পেয়েছেন?", + "labelHaveAccount": "ইতিমধ্যে একটি অ্যাকাউন্ট আছে কি?", + "actionCreateAccount": "ফ্রি ডেমো অ্যাকাউন্ট তৈরি করুন", + "informInvalidReferralCode": "আপনি যে রেফারেল কোড লিখেছেন তা অবৈধ। চেক করুন এবং আবার চেষ্টা করুন।", + "labelVerifyYourEmail": "আমার ইমেইল যাচাই", + "labelThanksEmail": "আপনার ইমেইল যাচাই করার জন্য ধন্যবাদ", + "informLetsContinue": "চলুন চালিয়ে যাক।", + "actionContinue": "চালিয়ে যান", + "labelSearchCountry": "দেশ অনুসন্ধান করুন", + "informVerificationEmailSent": "আমরা আপনার অ্যাকাউন্ট সক্রিয় করার জন্য {email} একটি লিঙ্ক সহ একটি বার্তা পাঠিয়েছি।", + "@informVerificationEmailSent": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "actionEmailNotReceived": "আপনার ইমেইল কি পাওয়া যায়নি?", + "informPasswordPolicy": "আপনার পাসওয়ার্ড অবশ্যই থাকতে হবে:", + "informPasswordPolicyLength": "8-25 অক্ষর", + "informPasswordPolicyLowerAndUpper": "উচ্চ এবং নিম্ন কেস অক্ষর", + "informPasswordPolicyNumber": "অন্তত একটি সংখ্যা", + "warnPasswordContainsSymbol": "শক্তিশালী পাসওয়ার্ড জন্য প্রতীক ব্যবহার", + "labelReferralCode": "রেফারেল কোড", + "actionTryAgain": "পুনরায় চেষ্টা করুন", + "informInvalid2FACode": "আপনি যে কোডটি প্রবেশ করেছেন তা অবৈধ। চেক করুন এবং আবার চেষ্টা করুন।", + "informFailedAuthentication": "আপনার ইমেল বা পাসওয়ার্ড ভুল হতে পারে। আপনি কি একটি সামাজিক অ্যাকাউন্ট দিয়ে সাইন আপ করেছেন? চেক করুন এবং আবার চেষ্টা করুন।", + "informDeactivatedAccount": "আপনার অ্যাকাউন্ট নিষ্ক্রিয় করা হয়েছে।", + "informFailedAuthorization": "অনুমোদন ব্যর্থ হয়েছে।", + "informInvalidResidence": "অবৈধ বাসস্থান।", + "informInvalidCredentials": "অবৈধ শংসাপত্র।", + "informMissingOtp": "একবার পাসওয়ার্ড অনুপস্থিত।", + "informSelfClosed": "আপনার অ্যাকাউন্ট বন্ধ করা হয়েছে।", + "informUnexpectedError": "অপ্রত্যাশিত ত্রুটি ঘটেছে।", + "informUnsupportedCountry": "আপনার দেশ সমর্থিত হয়নি।", + "informExpiredAccount": "আপনার অ্যাকাউন্টের মেয়াদ শেষ হয়েছে", + "labelCountryConsentBrazil": "আমি এর মাধ্যমে নিশ্চিত করি যে ব্রাজিলের বাইরে একচেটিয়াভাবে জারি করা এবং দেওয়া ওটিসি পণ্যগুলি বাণিজ্য করার জন্য ডেরিভের সাথে একটি অ্যাকাউন্ট খোলার জন্য আমার অনুরোধ আমার দ্বারা শুরু আমি পুরোপুরি বুঝতে পারি যে ডেরিভ সিভিএম দ্বারা নিয়ন্ত্রিত নয় এবং ডেরিভের কাছে পৌঁছানোর মাধ্যমে আমি একটি বিদেশী সংস্থার সাথে সম্পর্ক স্থাপন করার ইচ্ছা করি।", + "informConnectionError": "সংযোগ ত্রুটি। দয়া করে পরে আবার চেষ্টা করুন।", + "informSwitchAccountError": "অ্যাকাউন্ট ত্রুটি পরিবর্তন করুন দয়া করে পরে আবার চেষ্টা করুন।", + "labelDeveloper": "ডেভেলপার", + "labelEndpoint": "শেষপ্রান্ত", + "semanticEndpoint": "শেষপ্রান্ত", + "warnInvalidEndpoint": "অবৈধ শেষ পয়েন্ট", + "labelApplicationID": "অ্যাপলিকেশন আইডি", + "semanticApplicationID": "অ্যাপলিকেশন আইডি", + "warnInvalidApplicationID": "অবৈধ অ্যাপ্লিকেশন আইডি", + "labelLanguage": "ভাষা" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_auth/app_de.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_de.arb new file mode 100644 index 000000000..e6e185c0e --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_de.arb @@ -0,0 +1,117 @@ +{ + "labelNotAvailable": "Nicht verfügbar", + "warnNotAvailableCountriesTitle": "{app} ist in deinem Land nicht verfügbar", + "@warnNotAvailableCountriesTitle": { + "description": "A message with a single parameter", + "placeholders": { + "app": { + "type": "String", + "example": "Deriv GO" + } + } + }, + "actionOk": "Okay", + "warnNotAvailableCountries": "Wenn Sie Fragen haben, kontaktieren Sie uns über ", + "labelLiveChat": "Live-chat", + "actionSignUpForFree": "Melde dich kostenlos an", + "actionLogin": "Anmelden", + "labelTwoFactorAuth": "Zwei-Faktor-Authentifizierung", + "informEnterTwoFactorAuthCode": "Geben Sie den 6-stelligen Code aus der Authentifizierungs-App auf Ihrem Telefon ein.", + "labelTwoFactorAuthenticationCode": "2FA-Kode", + "actionProceed": "Fortfahren", + "labelLogIn": "Anmelden", + "informLoginOptions": "Oder melden Sie sich an mit", + "labelEmail": "E-Mail", + "labelPassword": "Passwort", + "actionForgotPassword": "Passwort vergessen?", + "labelDontHaveAnAccountYet": "Sie haben noch kein Konto?", + "actionCreateANewAccount": "Erstelle ein neues Konto", + "informInvalidEmailFormat": "Geben Sie eine gültige E-Mail Adresse ein", + "warnPasswordLength": "Sie sollten 6-25 Zeichen eingeben.", + "labelResetPassword": "Passwort zurücksetzen", + "labelChooseNewPass": "Wählen Sie ein neues Passwort", + "labelCreatePass": "Passwort", + "informYourPassHasBeenReset": "Ihr Passwort wurde zurückgesetzt", + "informRedirectLogin": "Sie müssen sich mit Ihrem neuen Passwort anmelden. Warten Sie, wir leiten Sie weiter.", + "actionResetPass": "Setze mein Passwort zurück", + "informInvalidPasswordFormat": "Bitte geben Sie ein gültiges Passwortformat ein", + "labelCheckEmail": "Prüfen Sie Ihre E-Mail", + "informSendResetPasswordEmail": "Wir haben eine Nachricht an {email} mit einem Link zum Zurücksetzen Ihres Passworts gesendet.", + "@informSendResetPasswordEmail": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "informResetPassByEmail": "Wir werden Ihnen per E-Mail Anweisungen zum Zurücksetzen Ihres Passworts zusenden.", + "labelSelectCountry": "Wo wohnen Sie?", + "labelChooseCountry": "Land wählen", + "warnCountryNotAvailable": "Leider ist Deriv in Ihrem Land nicht verfügbar.", + "actionNext": "Weiter", + "labelEmailIssueHeader": "Wenn Sie innerhalb weniger Minuten keine E-Mail von uns sehen, könnten einige Dinge passiert sein:", + "labelEmailIssueSpam": "Die E-Mail befindet sich in Ihrem Spam-Ordner (manchmal gehen dort Dinge verloren).", + "labelEmailIssueWrongEmail": "Sie haben uns versehentlich eine andere E-Mail-Adresse gegeben (normalerweise eine geschäftliche oder eine persönliche Adresse anstelle der von Ihnen meinten).", + "labelEmailIssueTypo": "Die von Ihnen eingegebene E-Mail-Adresse hatte einen Fehler oder Tippfehler (passiert den Besten von uns).", + "labelEmailIssueFirewall": "Wir können die E-Mail an diese Adresse nicht zustellen (in der Regel aufgrund von Firewalls oder Filtern).", + "actionReenterEmail": "Gib deine E-Mail-Adresse erneut ein und versuche es erneut", + "labelKeepPassword": "Schützen Sie Ihr Konto mit einem Passwort", + "labelCreatePassword": "Erstelle ein Passwort", + "actionStartTrading": "Beginnen Sie zu Handeln", + "actionPrevious": "Vorherige", + "labelSignUp": "Anmelden", + "labelOrSignUpWith": "Oder melden Sie sich bei", + "labelReferralInfoTitle": "Empfehlungscode für Partner", + "infoReferralInfoDescription": "Ein alphanumerischer Code, der von einem Deriv-Partner bereitgestellt wird und nur für E-Mail-Anmeldungen gilt.", + "labelGotReferralCode": "Haben Sie einen Empfehlungscode?", + "labelHaveAccount": "Haben Sie schon ein Konto?", + "actionCreateAccount": "Kostenloses Demo-Konto erstellen", + "informInvalidReferralCode": "Der von Ihnen eingegebene Empfehlungscode ist ungültig. Überprüfen Sie ihn und versuchen Sie es erneut.", + "labelVerifyYourEmail": "Überprüfen Sie Ihre E-Mail", + "labelThanksEmail": "Danke, dass du deine E-Mail verifiziert hast", + "informLetsContinue": "Lassen Sie uns fortfahren.", + "actionContinue": "Weiter", + "labelSearchCountry": "Land suchen", + "informVerificationEmailSent": "Wir haben eine Nachricht an {email} mit einem Link zur Aktivierung Ihres Kontos gesendet.", + "@informVerificationEmailSent": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "actionEmailNotReceived": "Haben Sie Ihre E-Mail nicht erhalten?", + "informPasswordPolicy": "Ihr Passwort muss Folgendes enthalten:", + "informPasswordPolicyLength": "8-25 Zeichen", + "informPasswordPolicyLowerAndUpper": "Groß- und Kleinbuchstaben", + "informPasswordPolicyNumber": "Mindestens eine Nummer", + "warnPasswordContainsSymbol": "Verwenden Sie Symbole für ein sicheres Passwort.", + "labelReferralCode": "Empfehlungscode", + "actionTryAgain": "Erneut versuchen", + "informInvalid2FACode": "Der von Ihnen eingegebene Code ist ungültig. Überprüfen Sie ihn und versuchen Sie es erneut.", + "informFailedAuthentication": "Ihre E-Mail oder Ihr Passwort ist möglicherweise falsch. Haben Sie sich mit einem sozialen Konto angemeldet? Überprüfen Sie es und versuchen Sie es erneut.", + "informDeactivatedAccount": "Ihr Konto ist deaktiviert.", + "informFailedAuthorization": "Die Autorisierung ist fehlgeschlagen.", + "informInvalidResidence": "Ungültiger Wohnsitz.", + "informInvalidCredentials": "Ungültige Anmeldeinformationen.", + "informMissingOtp": "Fehlendes Einmalpasswort.", + "informSelfClosed": "Ihr Konto wurde geschlossen.", + "informUnexpectedError": "Ein unerwarteter Fehler ist aufgetreten.", + "informUnsupportedCountry": "Ihr Land wird nicht unterstützt.", + "informExpiredAccount": "Ihr Konto ist abgelaufen", + "labelCountryConsentBrazil": "Ich bestätige hiermit, dass mein Antrag auf Eröffnung eines Kontos bei Deriv für den Handel mit OTC-Produkten, die ausschließlich außerhalb Brasiliens ausgegeben und angeboten werden, von mir initiiert wurde. Ich verstehe voll und ganz, dass Deriv nicht von CVM reguliert wird, und indem ich mich an Deriv wende, beabsichtige ich, eine Beziehung zu einem ausländischen Unternehmen aufzubauen.", + "informConnectionError": "Verbindungsfehler. Bitte versuchen Sie es später erneut.", + "informSwitchAccountError": "Fehler beim Kontowechsel. Bitte versuchen Sie es später erneut.", + "labelDeveloper": "Entwickler", + "labelEndpoint": "Endpunkt", + "semanticEndpoint": "Endpunkt", + "warnInvalidEndpoint": "Ungültiger Endpunkt", + "labelApplicationID": "Anwendungs-ID", + "semanticApplicationID": "Anwendungs-ID", + "warnInvalidApplicationID": "ungültige Anwendungs-ID", + "labelLanguage": "Sprache" +} \ No newline at end of file diff --git a/packages/deriv_auth_ui/lib/l10n/intl_en.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_en.arb similarity index 70% rename from packages/deriv_auth_ui/lib/l10n/intl_en.arb rename to packages/deriv_localizations/lib/l10n/deriv_auth/app_en.arb index 337c4193e..8ac9be2bf 100644 --- a/packages/deriv_auth_ui/lib/l10n/intl_en.arb +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_en.arb @@ -10,9 +10,10 @@ } } }, + "actionOk": "OK", "warnNotAvailableCountries": "If you have any questions, contact us via ", "labelLiveChat": "Live chat", - "actionGetAFreeAccount": "Get a free account", + "actionSignUpForFree": "Sign up for free", "actionLogin": "Log in", "labelTwoFactorAuth": "Two-factor authentication", "informEnterTwoFactorAuthCode": "Enter the 6-digit code from the authenticator app on your phone.", @@ -26,10 +27,10 @@ "labelDontHaveAnAccountYet": "Don’t have an account yet?", "actionCreateANewAccount": "Create a new account", "informInvalidEmailFormat": "Enter a valid email address", - "warnPasswordLength": "You should enter 8-25 characters.", - "labelResetPassword": "Reset Password", + "warnPasswordLength": "You should enter 6-25 characters.", + "labelResetPassword": "Reset password", "labelChooseNewPass": "Choose a new password", - "labelCreatePass": "Create a password", + "labelCreatePass": "Password", "informYourPassHasBeenReset": "Your password has been reset", "informRedirectLogin": "You’ll need to log in with your new password. Hang on, we’re redirecting you.", "actionResetPass": "Reset my password", @@ -89,5 +90,28 @@ "informPasswordPolicyLowerAndUpper": "Upper and lower case letters", "informPasswordPolicyNumber": "At least one number", "warnPasswordContainsSymbol": "Use symbols for strong password.", - "labelReferralCode": "Referral Code" + "labelReferralCode": "Referral Code", + "actionTryAgain": "Try Again", + "informInvalid2FACode": "The code you entered is invalid. Check and try again.", + "informFailedAuthentication": "Your email or password may be incorrect. Did you sign up with a social account? Check and try again.", + "informDeactivatedAccount": "Your account is deactivated.", + "informFailedAuthorization": "Authorization failed.", + "informInvalidResidence": "Invalid residence.", + "informInvalidCredentials": "Invalid credentials.", + "informMissingOtp": "Missing one-time password.", + "informSelfClosed": "Your account has been closed.", + "informUnexpectedError": "An unexpected error occurred.", + "informUnsupportedCountry": "Your country is not supported.", + "informExpiredAccount": "Your account is expired", + "labelCountryConsentBrazil": "I hereby confirm that my request for opening an account with Deriv to trade OTC products issued and offered exclusively outside Brazil was initiated by me. I fully understand that Deriv is not regulated by CVM and by approaching Deriv I intend to set up a relation with a foreign company.", + "informConnectionError": "Connection error. Please try again later.", + "informSwitchAccountError": "Switch account error. Please try again later.", + "labelDeveloper": "Developer", + "labelEndpoint": "Endpoint", + "semanticEndpoint": "Endpoint", + "warnInvalidEndpoint": "invalid endpoint", + "labelApplicationID": "Application ID", + "semanticApplicationID": "Application ID", + "warnInvalidApplicationID": "invalid application ID", + "labelLanguage": "Language" } \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_auth/app_es.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_es.arb new file mode 100644 index 000000000..b28497b0b --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_es.arb @@ -0,0 +1,117 @@ +{ + "labelNotAvailable": "No disponible", + "warnNotAvailableCountriesTitle": "{app} no está disponible en tu país", + "@warnNotAvailableCountriesTitle": { + "description": "A message with a single parameter", + "placeholders": { + "app": { + "type": "String", + "example": "Deriv GO" + } + } + }, + "actionOk": "Aceptar", + "warnNotAvailableCountries": "Si tiene alguna pregunta, póngase en contacto con nosotros a través de ", + "labelLiveChat": "Live Chat", + "actionSignUpForFree": "Regístrate gratis", + "actionLogin": "Iniciar sesión", + "labelTwoFactorAuth": "Autenticación de dos factores", + "informEnterTwoFactorAuthCode": "Introduzca el código de 6 dígitos de la aplicación autenticadora de su teléfono.", + "labelTwoFactorAuthenticationCode": "Código 2FA", + "actionProceed": "Seguir", + "labelLogIn": "Iniciar sesión", + "informLoginOptions": "O inicie sesión con", + "labelEmail": "Correo electrónico", + "labelPassword": "Contraseña", + "actionForgotPassword": "¿Olvidó la contraseña?", + "labelDontHaveAnAccountYet": "¿Aún no tiene una cuenta?", + "actionCreateANewAccount": "Crear una cuenta nueva", + "informInvalidEmailFormat": "Introduzca una dirección de correo electrónico válida", + "warnPasswordLength": "Debería ingresar de 8 a 25 caracteres.", + "labelResetPassword": "Restablecer contraseña", + "labelChooseNewPass": "Elija una contraseña nueva", + "labelCreatePass": "Contraseña", + "informYourPassHasBeenReset": "Su contraseña ha sido restablecida", + "informRedirectLogin": "Tendrá que iniciar sesión con su nueva contraseña. Espere, le estamos redirigiendo.", + "actionResetPass": "Restablecer mi contraseña", + "informInvalidPasswordFormat": "Por favor, introduzca un formato de contraseña válido", + "labelCheckEmail": "Revise su correo electrónico", + "informSendResetPasswordEmail": "Hemos enviado un mensaje a {email} con un enlace para restablecer su contraseña.", + "@informSendResetPasswordEmail": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "informResetPassByEmail": "Le enviaremos instrucciones por correo electrónico para restablecer su contraseña.", + "labelSelectCountry": "¿Dónde vive?", + "labelChooseCountry": "Elija su país", + "warnCountryNotAvailable": "Lamentablemente, Deriv no está disponible en su país.", + "actionNext": "Siguiente", + "labelEmailIssueHeader": "Si no ve un correo electrónico nuestro en unos minutos, podrían haber sucedido algunas cosas:", + "labelEmailIssueSpam": "El correo electrónico está en su carpeta de spam (a veces las cosas se pierden allí).", + "labelEmailIssueWrongEmail": "Accidentalmente nos proporcionó otra dirección de correo electrónico (tal vez una de trabajo o una personal diferente a la que pensaba utilizar).", + "labelEmailIssueTypo": "La dirección de correo electrónico que ingresó tenía un fallo o error tipográfico (pasa hasta en las mejores familias).", + "labelEmailIssueFirewall": "No podemos enviar el correo electrónico a esta dirección (Usualmente debido a firewalls o al filtrado).", + "actionReenterEmail": "Vuelva a ingresar su correo e inténtelo de nuevo", + "labelKeepPassword": "Mantenga su cuenta segura con una contraseña", + "labelCreatePassword": "Cree una contraseña", + "actionStartTrading": "Comenzar a operar", + "actionPrevious": "Anterior", + "labelSignUp": "Crear cuenta", + "labelOrSignUpWith": "O regístrese con", + "labelReferralInfoTitle": "Código de referencia del afiliado", + "infoReferralInfoDescription": "Un código alfanumérico proporcionado por un afiliado de Deriv, aplicable únicamente a las inscripciones por correo electrónico.", + "labelGotReferralCode": "¿Tiene un código de referencia?", + "labelHaveAccount": "¿Ya tiene una cuenta?", + "actionCreateAccount": "Crear cuenta demo gratis", + "informInvalidReferralCode": "El código de referencia que ha introducido no es válido. Compruébelo e inténtelo de nuevo.", + "labelVerifyYourEmail": "Verifique su correo electrónico", + "labelThanksEmail": "Gracias por verificar su correo electrónico", + "informLetsContinue": "Continuemos.", + "actionContinue": "Continuar", + "labelSearchCountry": "Buscar país", + "informVerificationEmailSent": "Hemos enviado un mensaje a {email} con un enlace para activar su cuenta.", + "@informVerificationEmailSent": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "actionEmailNotReceived": "¿No recibió su correo electrónico?", + "informPasswordPolicy": "Su contraseña debe tener:", + "informPasswordPolicyLength": "8-25 caracteres", + "informPasswordPolicyLowerAndUpper": "Letras mayúsculas y minúsculas", + "informPasswordPolicyNumber": "Al menos un número", + "warnPasswordContainsSymbol": "Utilice símbolos para una contraseña segura.", + "labelReferralCode": "Código de Referencia", + "actionTryAgain": "Intentar de nuevo", + "informInvalid2FACode": "El código que ha introducido no es válido. Compruébelo e inténtelo de nuevo.", + "informFailedAuthentication": "Su correo electrónico o contraseña pueden ser incorrectos. ¿Se ha registrado con una cuenta social? Compruébelo e inténtelo de nuevo.", + "informDeactivatedAccount": "Tu cuenta está desactivada.", + "informFailedAuthorization": "Fallo en la autorización.", + "informInvalidResidence": "Residencia no válida.", + "informInvalidCredentials": "Credenciales no válidas.", + "informMissingOtp": "Falta la contraseña de un solo uso.", + "informSelfClosed": "Su cuenta ha sido cerrada.", + "informUnexpectedError": "Ha ocurrido un error inesperado.", + "informUnsupportedCountry": "Tu país no es compatible.", + "informExpiredAccount": "Tu cuenta ha caducado", + "labelCountryConsentBrazil": "Confirmo que mi solicitud de apertura de cuenta en Deriv para operar productos OTC emitidos y ofrecidos exclusivamente fuera de Brasil fue iniciada por mí. Entiendo plenamente que Deriv no está regulada por la CVM y al dirigirme a Deriv pretendo establecer una relación con una empresa extranjera.", + "informConnectionError": "Error de conexión. Vuelva a intentarlo más tarde.", + "informSwitchAccountError": "Error al cambiar de cuenta. Vuelva a intentarlo más tarde.", + "labelDeveloper": "Desarrollador", + "labelEndpoint": "Punto final", + "semanticEndpoint": "Punto final", + "warnInvalidEndpoint": "punto final no válido", + "labelApplicationID": "ID de aplicación", + "semanticApplicationID": "ID de aplicación", + "warnInvalidApplicationID": "ID de aplicación no válido", + "labelLanguage": "Idioma" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_auth/app_fr.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_fr.arb new file mode 100644 index 000000000..be90562c9 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_fr.arb @@ -0,0 +1,117 @@ +{ + "labelNotAvailable": "Non disponible", + "warnNotAvailableCountriesTitle": "{app} n'est pas disponible dans votre pays", + "@warnNotAvailableCountriesTitle": { + "description": "A message with a single parameter", + "placeholders": { + "app": { + "type": "String", + "example": "Deriv GO" + } + } + }, + "actionOk": "OK", + "warnNotAvailableCountries": "Si vous avez des questions, contactez-nous par ", + "labelLiveChat": "Chat en direct", + "actionSignUpForFree": "Inscrivez-vous gratuitement", + "actionLogin": "Se connecter", + "labelTwoFactorAuth": "Authentification à deux facteurs", + "informEnterTwoFactorAuthCode": "Saisissez le code à 6 chiffres qui s'affiche sur l'application d'authentification de votre téléphone.", + "labelTwoFactorAuthenticationCode": "code 2FA", + "actionProceed": "Continuer", + "labelLogIn": "Se connecter", + "informLoginOptions": "Ou connectez-vous avec", + "labelEmail": "E-mail", + "labelPassword": "Mot de passe", + "actionForgotPassword": "Mot de passe oublié ?", + "labelDontHaveAnAccountYet": "Vous n'avez pas encore de compte ?", + "actionCreateANewAccount": "Créer un compte", + "informInvalidEmailFormat": "Saisissez une adresse e-mail valide", + "warnPasswordLength": "Vous devez saisir entre 6 à 25 caractères.", + "labelResetPassword": "Réinitialiser le mot", + "labelChooseNewPass": "Définir un nouveau mot de passe", + "labelCreatePass": "Mot de passe", + "informYourPassHasBeenReset": "Votre mot de passe a été réinitialisé", + "informRedirectLogin": "Vous devrez vous connecter à l'aide de votre nouveau mot de passe. Patientez, nous vous redirigeons.", + "actionResetPass": "Réinitialiser mon mot de passe", + "informInvalidPasswordFormat": "Veuillez saisir un mot de passe valide", + "labelCheckEmail": "Vérifiez votre e-mail", + "informSendResetPasswordEmail": "Nous avons envoyé un message à l'adresse {email} contenant un lien de réinitialisation de votre mot de passe.", + "@informSendResetPasswordEmail": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "informResetPassByEmail": "Nous vous enverrons des instructions par e-mail pour la réinitialisation de votre mot de passe.", + "labelSelectCountry": "Où résidez-vous ?", + "labelChooseCountry": "Sélectionnez votre pays", + "warnCountryNotAvailable": "Malheureusement, Deriv n'est pas disponible dans votre pays.", + "actionNext": "Suivant", + "labelEmailIssueHeader": "Si vous ne recevez pas un courriel de notre part dans les minutes qui suivent, cela peut être dû aux raisons suivantes :", + "labelEmailIssueSpam": "L'e-mail se trouve dans votre dossier spam (parfois des choses s'y perdent).", + "labelEmailIssueWrongEmail": "Vous nous avez involontairement fourni une autre adresse électronique (généralement une adresse professionnelle ou personnelle différente de celle que vous vouliez fournir).", + "labelEmailIssueTypo": "L'adresse e-mail que vous avez fournie comportait une erreur ou une faute de frappe (cela arrive même aux meilleurs d'entre nous).", + "labelEmailIssueFirewall": "Nous ne parvenons pas à envoyer d'e-mail à cette adresse (généralement en raison de pare-feu ou de filtrage).", + "actionReenterEmail": "Saisissez à nouveau votre e-mail et réessayez", + "labelKeepPassword": "Protégez votre compte avec un mot de passe", + "labelCreatePassword": "Créer un mot de passe", + "actionStartTrading": "Commencer à trader", + "actionPrevious": "Précédent", + "labelSignUp": "S'inscrire", + "labelOrSignUpWith": "Ou inscrivez-vous avec", + "labelReferralInfoTitle": "Code de parrainage de l'affilié", + "infoReferralInfoDescription": "Code alphanumérique fourni par un affilié Deriv, applicable uniquement pour les inscriptions par e-mail.", + "labelGotReferralCode": "Vous avez un code de parrainage ?", + "labelHaveAccount": "Vous avez déjà un compte ?", + "actionCreateAccount": "Créer un compte démo", + "informInvalidReferralCode": "Le code de parrainage que vous avez saisi n'est pas valide. Vérifiez et réessayez.", + "labelVerifyYourEmail": "Vérifiez votre e-mail", + "labelThanksEmail": "Merci d'avoir vérifié votre e-mail", + "informLetsContinue": "Poursuivons.", + "actionContinue": "Continuer", + "labelSearchCountry": "Rechercher un pays", + "informVerificationEmailSent": "Nous avons envoyé un message à l'adresse {email} contenant un lien d'activation de votre compte.", + "@informVerificationEmailSent": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "actionEmailNotReceived": "Vous n'avez pas reçu d'e-mail ?", + "informPasswordPolicy": "Votre mot de passe doit comporter :", + "informPasswordPolicyLength": "Entre 8 à 25 caractères", + "informPasswordPolicyLowerAndUpper": "Des lettres majuscules et minuscules", + "informPasswordPolicyNumber": "Au moins un chiffre", + "warnPasswordContainsSymbol": "Utilisez des symboles pour obtenir un mot de passe fort.", + "labelReferralCode": "Code de parrainage", + "actionTryAgain": "Réessayer", + "informInvalid2FACode": "Le code que vous avez saisi n'est pas valide. Vérifiez et réessayez.", + "informFailedAuthentication": "Votre adresse e-mail ou votre mot de passe est peut-être incorrect. Vous êtes-vous inscrit avec un compte social ? Vérifiez et réessayez.", + "informDeactivatedAccount": "Votre compte est désactivé.", + "informFailedAuthorization": "Échec de l'autorisation.", + "informInvalidResidence": "Résidence non valide.", + "informInvalidCredentials": "Informations d'identification non valides.", + "informMissingOtp": "Mot de passe à usage unique manquant.", + "informSelfClosed": "Votre compte a été fermé.", + "informUnexpectedError": "Une erreur inattendue s'est produite.", + "informUnsupportedCountry": "Votre pays n'est pas pris en charge.", + "informExpiredAccount": "Votre compte a expiré", + "labelCountryConsentBrazil": "Je confirme par la présente que ma demande d'ouverture d'un compte auprès de Deriv pour y trader des produits OTC émis et offerts exclusivement en dehors du Brésil a été initiée par moi-même. Je comprends parfaitement que Deriv n'est pas réglementé par la Commission brésilienne des valeurs mobilières et des échanges (CVM) et qu'en contactant Deriv, j'ai l'intention d'établir une relation avec une société étrangère.", + "informConnectionError": "Erreur de connexion. Veuillez réessayer ultérieurement.", + "informSwitchAccountError": "Erreur de changement de compte. Veuillez réessayer ultérieurement.", + "labelDeveloper": "Développeur", + "labelEndpoint": "Point de terminaison", + "semanticEndpoint": "Point de terminaison", + "warnInvalidEndpoint": "point de terminaison non valide", + "labelApplicationID": "Identifiant de l'application", + "semanticApplicationID": "Identifiant de l'application", + "warnInvalidApplicationID": "identifiant de l'application non valide", + "labelLanguage": "Langue" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_auth/app_it.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_it.arb new file mode 100644 index 000000000..b99a23f9b --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_it.arb @@ -0,0 +1,117 @@ +{ + "labelNotAvailable": "Non disponibile", + "warnNotAvailableCountriesTitle": "{app} non è disponibile nel tuo Paese", + "@warnNotAvailableCountriesTitle": { + "description": "A message with a single parameter", + "placeholders": { + "app": { + "type": "String", + "example": "Deriv GO" + } + } + }, + "actionOk": "OK", + "warnNotAvailableCountries": "Se hai domande, non esitare a contattarci tramite ", + "labelLiveChat": "Chat live", + "actionSignUpForFree": "Registrati gratis", + "actionLogin": "Accedi", + "labelTwoFactorAuth": "Autenticazione a due fattori", + "informEnterTwoFactorAuthCode": "Inserisca il codice a 6 cifre dall'app Authenticator sul suo telefono.", + "labelTwoFactorAuthenticationCode": "Codice 2FA", + "actionProceed": "Continua", + "labelLogIn": "Accedi", + "informLoginOptions": "Oppure accedi con", + "labelEmail": "E-mail", + "labelPassword": "Password", + "actionForgotPassword": "Hai dimenticato la password?", + "labelDontHaveAnAccountYet": "Non ha ancora un account?", + "actionCreateANewAccount": "Crea un nuovo conto", + "informInvalidEmailFormat": "Inserisca un indirizzo e-mail valido", + "warnPasswordLength": "È necessario inserire da 8 a 25 caratteri.", + "labelResetPassword": "Reimposta la password", + "labelChooseNewPass": "Scegli una nuova password", + "labelCreatePass": "Password", + "informYourPassHasBeenReset": "La sua password è stata reimpostata", + "informRedirectLogin": "Dovrà accedere con la nuova password. Aspetti, la stiamo reindirizzando.", + "actionResetPass": "Reimposta la password", + "informInvalidPasswordFormat": "Inserisci un formato della password valido", + "labelCheckEmail": "Controlla la tua casella di posta", + "informSendResetPasswordEmail": "Abbiamo inviato un messaggio a {email} con un link per reimpostare la sua password.", + "@informSendResetPasswordEmail": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "informResetPassByEmail": "Ti manderemo una e-mail con le istruzioni per reimpostare la password.", + "labelSelectCountry": "Dove vivi?", + "labelChooseCountry": "Seleziona il Paese", + "warnCountryNotAvailable": "Deriv non è disponibile nel tuo Paese.", + "actionNext": "Successivo", + "labelEmailIssueHeader": "Se non ricevi una e-mail da parte nostra entro pochi minuti, potrebbero esserci stati dei problemi:", + "labelEmailIssueSpam": "L' e-mail è nella cartella spam (dove alle volte finiscono i messaggi).", + "labelEmailIssueWrongEmail": "Hai erroneamente indicato un altro indirizzo e-mail (normalmente quello di lavoro o personale, invece di quello che intendevi usare).", + "labelEmailIssueTypo": "L'indirizzo e-mail inserito contiene un errore o un refuso (è un errore comune, non preoccuparti!).", + "labelEmailIssueFirewall": "Non possiamo inviare l'e-mail a questo indirizzo (di solito a causa di filtri o firewall).", + "actionReenterEmail": "Reinserisci e-mail e prova ancora", + "labelKeepPassword": "Mantieni in sicurezza il tuo conto con una password", + "labelCreatePassword": "Crea una password", + "actionStartTrading": "Inizia il trading", + "actionPrevious": "Precedente", + "labelSignUp": "Registrati", + "labelOrSignUpWith": "Oppure registrati con", + "labelReferralInfoTitle": "Codice di riferimento dell'affiliato", + "infoReferralInfoDescription": "Un codice alfanumerico fornito da un affiliato Deriv, applicabile solo per le iscrizioni via e-mail.", + "labelGotReferralCode": "Ha un codice di riferimento?", + "labelHaveAccount": "Hai già un account?", + "actionCreateAccount": "Crea un conto di prova gratuito", + "informInvalidReferralCode": "Il codice di riferimento inserito non è valido. Controlli e riprovi.", + "labelVerifyYourEmail": "Verifica la tua email", + "labelThanksEmail": "Grazie per aver verificato la tua e-mail", + "informLetsContinue": "Continuiamo.", + "actionContinue": "Continua", + "labelSearchCountry": "Ricerca per Paese", + "informVerificationEmailSent": "Abbiamo inviato un messaggio a {email} con un link per attivare il suo account.", + "@informVerificationEmailSent": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "actionEmailNotReceived": "Non hai ricevuto l'e-mail?", + "informPasswordPolicy": "La sua password deve avere:", + "informPasswordPolicyLength": "8-25 caratteri", + "informPasswordPolicyLowerAndUpper": "Lettere maiuscole e minuscole", + "informPasswordPolicyNumber": "Almeno un numero", + "warnPasswordContainsSymbol": "Utilizzi i simboli per una password forte.", + "labelReferralCode": "Codice di riferimento", + "actionTryAgain": "Riprova", + "informInvalid2FACode": "Il codice inserito non è valido. Controlli e riprovi.", + "informFailedAuthentication": "La sua e-mail o la sua password potrebbero essere errate. Si è iscritto con un account social? Controlli e riprovi.", + "informDeactivatedAccount": "Il tuo account è disattivato.", + "informFailedAuthorization": "Autorizzazione non riuscita.", + "informInvalidResidence": "Residenza non valida.", + "informInvalidCredentials": "Credenziali non valide.", + "informMissingOtp": "Password monouso mancante.", + "informSelfClosed": "Questo conto è stato chiuso.", + "informUnexpectedError": "Ein unerwarteter Fehler ist aufgetreten.", + "informUnsupportedCountry": "Il tuo Paese non è supportato.", + "informExpiredAccount": "Il tuo account è scaduto", + "labelCountryConsentBrazil": "Con la presente confermo che la mia richiesta di apertura di un conto presso Deriv per negoziare prodotti OTC emessi e offerti esclusivamente al di fuori del Brasile è stata avviata da me. Sono pienamente consapevole che Deriv non è regolamentata dal CVM e che rivolgendomi a Deriv intendo instaurare un rapporto con una società straniera.", + "informConnectionError": "Errore di connessione. Riprova più tardi.", + "informSwitchAccountError": "Errore nel cambio di account. Riprova più tardi.", + "labelDeveloper": "Sviluppatore", + "labelEndpoint": "Termine", + "semanticEndpoint": "Termine", + "warnInvalidEndpoint": "termine non valido", + "labelApplicationID": "ID applicazione", + "semanticApplicationID": "ID applicazione", + "warnInvalidApplicationID": "ID applicazione non valido", + "labelLanguage": "Lingua" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_auth/app_km.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_km.arb new file mode 100644 index 000000000..e6a0a562b --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_km.arb @@ -0,0 +1,117 @@ +{ + "labelNotAvailable": "មិនមាន", + "warnNotAvailableCountriesTitle": "{app} មិនមាននៅក្នុងប្រទេសរបស់អ្នកទេ", + "@warnNotAvailableCountriesTitle": { + "description": "A message with a single parameter", + "placeholders": { + "app": { + "type": "String", + "example": "Deriv GO" + } + } + }, + "actionOk": "យល់ព្រម", + "warnNotAvailableCountries": "ប្រសិនបើអ្នកមានសំណួរណាមួយ សូមទាក់ទងមកយើងខ្ញុំតាមរយៈ ", + "labelLiveChat": "ជជែកផ្ទាល់", + "actionSignUpForFree": "ចុះឈ្មោះដោយឥតគិតថ្លៃ", + "actionLogin": "ចូល", + "labelTwoFactorAuth": "ការផ្ទៀងផ្ទាត់ភាពត្រឹមត្រូវពីរកម្រិត", + "informEnterTwoFactorAuthCode": "បញ្ចូលលេខកូដ 6 ខ្ទង់ពីកម្មវិធីផ្ទៀងផ្ទាត់ភាពត្រឹមត្រូវនៅលើទូរស័ព្ទរបស់អ្នក។", + "labelTwoFactorAuthenticationCode": "លេខកូដ 2FA", + "actionProceed": "បន្ត", + "labelLogIn": "ចូល", + "informLoginOptions": "ឬចូលជាមួយ", + "labelEmail": "អ៊ីមែល", + "labelPassword": "ពាក្យសម្ងាត់", + "actionForgotPassword": "ភ្លេចពាក្យសម្ងាត់?", + "labelDontHaveAnAccountYet": "មិនទាន់មានគណនីនៅឡើយទេ?", + "actionCreateANewAccount": "បង្កើតគណនីថ្មី", + "informInvalidEmailFormat": "បញ្ចូលអាសយដ្ឋានអ៊ីមែលដែលត្រឹមត្រូវ", + "warnPasswordLength": "អ្នកគួរតែបញ្ចូលតួអក្សរចំនួន 6-25 ។", + "labelResetPassword": "កំណត់ពាក្យសម្ងាត់ឡើងវិញ", + "labelChooseNewPass": "ជ្រើសរើសពាក្យសម្ងាត់ថ្មី", + "labelCreatePass": "ពាក្យសម្ងាត់", + "informYourPassHasBeenReset": "ពាក្យសម្ងាត់របស់អ្នកត្រូវបានកំណត់ឡើងវិញ", + "informRedirectLogin": "អ្នកនឹងត្រូវចូលដោយប្រើពាក្យសម្ងាត់ថ្មីរបស់អ្នក។ រង់ចាំបន្តិច យើងកំពុងបញ្ជូនអ្នកឡើងវិញ។", + "actionResetPass": "កំណត់ពាក្យសម្ងាត់របស់ខ្ញុំឡើងវិញ", + "informInvalidPasswordFormat": "សូមបញ្ចូលទម្រង់ពាក្យសម្ងាត់ត្រឹមត្រូវ", + "labelCheckEmail": "ពិនិត្យអ៊ីមែលរបស់អ្នក", + "informSendResetPasswordEmail": "យើងបានផ្ញើសារទៅ {email} ជាមួយនឹងតំណដើម្បីកំណត់ពាក្យសម្ងាត់របស់អ្នកឡើងវិញ។", + "@informSendResetPasswordEmail": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "informResetPassByEmail": "យើងនឹងផ្ញើអ៊ីមែលជូនអ្នកនូវការណែនាំដើម្បីកំណត់ពាក្យសម្ងាត់របស់អ្នកឡើងវិញ។", + "labelSelectCountry": "តើអ្នករស់នៅឯណា?", + "labelChooseCountry": "ជ្រើសរើសប្រទេស", + "warnCountryNotAvailable": "ជាអកុសល Deriv មិនមាននៅក្នុងប្រទេសរបស់អ្នកទេ។", + "actionNext": "បន្ទាប់", + "labelEmailIssueHeader": "ប្រសិនបើអ្នកមិនឃើញអ៊ីមែលពីយើងក្នុងរយៈពេលពីរបីនាទី អ្វីៗមួយចំនួនអាចនឹងកើតឡើង៖", + "labelEmailIssueSpam": "អ៊ីមែលនេះស្ថិតនៅក្នុងថតឯកសារឥតបានការរបស់អ្នក (ពេលខ្លះរបស់របរបាត់បង់នៅទីនោះ)។", + "labelEmailIssueWrongEmail": "អ្នកបានផ្តល់អាសយដ្ឋានអ៊ីមែលផ្សេងទៀតដល់យើងដោយចៃដន្យ (ជាធម្មតាជាការងារ ឬផ្ទាល់ខ្លួនជំនួសឱ្យអ្វីដែលអ្នកចង់បាន)។", + "labelEmailIssueTypo": "អាសយដ្ឋានអ៊ីមែលដែលអ្នកបានបញ្ចូលមានកំហុស ឬវាយអក្សរខុស (កើតឡើងចំពោះយើងភាគច្រើន)។", + "labelEmailIssueFirewall": "យើងមិនអាចផ្ញើអ៊ីមែលទៅកាន់អាសយដ្ឋាននេះបានទេ (ជាធម្មតាដោយសារតែជញ្ជាំងភ្លើង ឬការច្រោះ)។", + "actionReenterEmail": "បញ្ចូលអ៊ីមែលរបស់អ្នកឡើងវិញ ហើយសាកល្បងម្តងទៀត", + "labelKeepPassword": "រក្សាគណនីរបស់អ្នកឱ្យមានសុវត្ថិភាពជាមួយពាក្យសម្ងាត់", + "labelCreatePassword": "បង្កើតពាក្យសម្ងាត់", + "actionStartTrading": "ចាប់ផ្តើមការជួញដូរ", + "actionPrevious": "មុន", + "labelSignUp": "ចុះឈ្មោះ", + "labelOrSignUpWith": "ឬចុះឈ្មោះជាមួយ", + "labelReferralInfoTitle": "លេខកូដបញ្ជូនសម្ព័ន្ធ", + "infoReferralInfoDescription": "លេខកូដអក្សរក្រម និងលេខដែលផ្តល់ដោយសម្ព័ន្ធ Deriv ដែលអាចអនុវត្តបានសម្រាប់ការចុះឈ្មោះតាមអ៊ីមែលតែប៉ុណ្ណោះ។", + "labelGotReferralCode": "មានលេខកូដបញ្ជូនទេ?", + "labelHaveAccount": "មានគណនីរួចហើយឬនៅ?", + "actionCreateAccount": "បង្កើតគណនីបង្ហាញឥតគិតថ្លៃ", + "informInvalidReferralCode": "លេខកូដបញ្ជូនដែលអ្នកបានបញ្ចូលមិនត្រឹមត្រូវ។ ពិនិត្យ និងសាកល្បងម្តងទៀត។", + "labelVerifyYourEmail": "ផ្ទៀងផ្ទាត់អ៊ីមែលរបស់អ្នក", + "labelThanksEmail": "សូមអរគុណសម្រាប់ការផ្ទៀងផ្ទាត់អ៊ីមែលរបស់អ្នក", + "informLetsContinue": "តោះបន្ត។", + "actionContinue": "បន្ត", + "labelSearchCountry": "ស្វែងរកប្រទេស", + "informVerificationEmailSent": "យើងបានផ្ញើសារទៅ {email} ជាមួយនឹងតំណដើម្បីធ្វើឱ្យគណនីរបស់អ្នកសកម្ម។", + "@informVerificationEmailSent": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "actionEmailNotReceived": "មិនបានទទួលអ៊ីមែលរបស់អ្នកទេ?", + "informPasswordPolicy": "ពាក្យសម្ងាត់របស់អ្នកត្រូវតែមាន៖", + "informPasswordPolicyLength": "8-25 តួអក្សរ", + "informPasswordPolicyLowerAndUpper": "អក្សរធំ និងអក្សរតូច", + "informPasswordPolicyNumber": "យ៉ាងហោចណាស់មួយលេខ", + "warnPasswordContainsSymbol": "ប្រើនិមិត្តសញ្ញាសម្រាប់ពាក្យសម្ងាត់ខ្លាំង។", + "labelReferralCode": "លេខកូដបញ្ជូន", + "actionTryAgain": "សាកល្បងម្តងទៀត", + "informInvalid2FACode": "លេខកូដដែលអ្នកបានបញ្ចូលមិនត្រឹមត្រូវ។ ពិនិត្យ និងសាកល្បងម្តងទៀត។", + "informFailedAuthentication": "អ៊ីមែល ឬពាក្យសម្ងាត់របស់អ្នកអាចនឹងមិនត្រឹមត្រូវ។ តើអ្នកបានចុះឈ្មោះជាមួយគណនីសង្គមមែនទេ? ពិនិត្យ និងសាកល្បងម្តងទៀត។", + "informDeactivatedAccount": "គណនីរបស់អ្នកត្រូវបានអសកម្ម។", + "informFailedAuthorization": "ការអនុញ្ញាតបរាជ័យ។", + "informInvalidResidence": "ការស្នាក់នៅមិនត្រឹមត្រូវ។", + "informInvalidCredentials": "អត្តសញ្ញាណមិនត្រឹមត្រូវ។", + "informMissingOtp": "ពាក្យសម្ងាត់មួយដងបាត់។", + "informSelfClosed": "គណនីរបស់អ្នកត្រូវបានបិទ។", + "informUnexpectedError": "កំហុសដែលមិនបានរំពឹងទុកបានកើតឡើង។", + "informUnsupportedCountry": "ប្រទេសរបស់អ្នកមិនត្រូវបានគាំទ្រទេ។", + "informExpiredAccount": "គណនីរបស់អ្នកផុតកំណត់ហើយ", + "labelCountryConsentBrazil": "ខ្ញុំសូមបញ្ជាក់នៅទីនេះថា សំណើរបស់ខ្ញុំសម្រាប់ការបើកគណនីជាមួយ Deriv ដើម្បីធ្វើពាណិជ្ជកម្មផលិតផល OTC ដែលចេញ និងផ្តល់ជូនផ្តាច់មុខនៅខាងក្រៅប្រទេសប្រេស៊ីល ត្រូវបានផ្តួចផ្តើមដោយខ្ញុំ។ ខ្ញុំយល់ច្បាស់ទាំងស្រុងថា Deriv មិនត្រូវបានគ្រប់គ្រងដោយ CVM ហើយដោយការទាក់ទង Deriv ខ្ញុំមានបំណងបង្កើតទំនាក់ទំនងជាមួយក្រុមហ៊ុនបរទេស។", + "informConnectionError": "កំហុសក្នុងការតភ្ជាប់។ សូមព្យាយាមម្តងទៀតនៅពេលក្រោយ។", + "informSwitchAccountError": "កំហុសក្នុងការប្តូរគណនី។ សូមព្យាយាមម្តងទៀតនៅពេលក្រោយ។", + "labelDeveloper": "អ្នកអភិវឌ្ឍន៍", + "labelEndpoint": "ចំណុចបញ្ចប់", + "semanticEndpoint": "ចំណុចបញ្ចប់", + "warnInvalidEndpoint": "ចំណុចបញ្ចប់មិនត្រឹមត្រូវ", + "labelApplicationID": "លេខសម្គាល់កម្មវិធី", + "semanticApplicationID": "លេខសម្គាល់កម្មវិធី", + "warnInvalidApplicationID": "លេខសម្គាល់កម្មវិធីមិនត្រឹមត្រូវ", + "labelLanguage": "ភាសា" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_auth/app_ko.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_ko.arb new file mode 100644 index 000000000..b3202aa4b --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_ko.arb @@ -0,0 +1,117 @@ +{ + "labelNotAvailable": "사용할 수 없습니다", + "warnNotAvailableCountriesTitle": "귀하의 국가에서는 {app} 을(를) 사용할 수 없습니다.", + "@warnNotAvailableCountriesTitle": { + "description": "A message with a single parameter", + "placeholders": { + "app": { + "type": "String", + "example": "Deriv GO" + } + } + }, + "actionOk": "확인", + "warnNotAvailableCountries": "궁금하신 점은 다음을 통해 문의해 주세요 ", + "labelLiveChat": "실시간 채팅", + "actionSignUpForFree": "무료로 가입하기", + "actionLogin": "로그인", + "labelTwoFactorAuth": "2단계 인증", + "informEnterTwoFactorAuthCode": "휴대폰 인증 앱에서 6자리 코드를 입력하세요.", + "labelTwoFactorAuthenticationCode": "2FA 코드", + "actionProceed": "진행", + "labelLogIn": "로그인", + "informLoginOptions": "또는 다음을 통해 로그인", + "labelEmail": "이메일", + "labelPassword": "비밀번호", + "actionForgotPassword": "비밀번호를 잊으셨나요?", + "labelDontHaveAnAccountYet": "아직 계정이 없는가요?", + "actionCreateANewAccount": "새 계정 생성", + "informInvalidEmailFormat": "유효한 이메일 주소를 입력해 주세요", + "warnPasswordLength": "6~25자를 입력해야 합니다.", + "labelResetPassword": "비밀번호 재설정", + "labelChooseNewPass": "새 비밀번호를 선택하세요", + "labelCreatePass": "비밀번호", + "informYourPassHasBeenReset": "비밀번호가 재설정되었습니다", + "informRedirectLogin": "새 비밀번호르 로그인해야 합니다. 잠시만 기다려 주세요. 리디렉션 중입니다.", + "actionResetPass": "비밀번호 재설정", + "informInvalidPasswordFormat": "올바른 형식의 비밀번호를 입력해 주세요", + "labelCheckEmail": "이메일을 확인해 주세요", + "informSendResetPasswordEmail": "비밀번호 재설정을 위한 링크가 포함된 메시지가 {email}로 전송되었습니다.", + "@informSendResetPasswordEmail": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "informResetPassByEmail": "비밀번호 재설정을 위한 지침을 이메일로 전송해 드리겠습니다.", + "labelSelectCountry": "어디에 사시나요?", + "labelChooseCountry": "국가 선택", + "warnCountryNotAvailable": "아쉽게도 귀하의 국가에서는 Deriv를 이용하실 수 없습니다.", + "actionNext": "다음", + "labelEmailIssueHeader": "몇 분 내에 이메일을 받지 못하셨다면 다음과 같은 몇 가지 문제가 발생했을 수 있습니다:", + "labelEmailIssueSpam": "이메일이 스팸 폴더에 있습니다(이메일이 스팸 폴더에 전송될 수 있습니다).", + "labelEmailIssueWrongEmail": "실수로 다른 이메일 주소를 알려주셨습니다(의도하신 이메일 주소 대신 회사 및 개인 이메일 주소일 수 있습니다).", + "labelEmailIssueTypo": "입력하신 이메일 주소가 잘못되었거나 오타가 있습니다(누구에게나 발생할 수 있습니다).", + "labelEmailIssueFirewall": "이 주소로 이메일이 전송될 수 없습니다(일반적으로는 방화벽 또는 필터링이 원인입니다).", + "actionReenterEmail": "이메일을 다시 입력하시고 재시도해 주세요", + "labelKeepPassword": "비밀번호로 계정을 안전하게 보호하세요", + "labelCreatePassword": "비밀번호 생성", + "actionStartTrading": "거래 시작", + "actionPrevious": "이전", + "labelSignUp": "가입", + "labelOrSignUpWith": "또는 다음을 통해 가입", + "labelReferralInfoTitle": "제휴 추천 코드", + "infoReferralInfoDescription": "Deriv 제휴자가 제공하는 영숫자 코드이며 이메일 가입 시에만 사용할 수 있습니다.", + "labelGotReferralCode": "추천 코드가 있나요?", + "labelHaveAccount": "이미 계정을 보유하고 계시나요?", + "actionCreateAccount": "무료 데모 게정 생성", + "informInvalidReferralCode": "입력하신 추천 코드가 유효하지 않습니다. 확인 후 다시 시도해 주세요.", + "labelVerifyYourEmail": "이메일 인증", + "labelThanksEmail": "이메일을 인증해 주셔서 감사합니다", + "informLetsContinue": "계속 진행.", + "actionContinue": "계속", + "labelSearchCountry": "국가 검색", + "informVerificationEmailSent": "계정 활성화를 위한 링크가 포함된 메시지가 {email}로 전송되었습니다.", + "@informVerificationEmailSent": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "actionEmailNotReceived": "이메일을 받지 못하셨나요?", + "informPasswordPolicy": "비밀번호에는 반드시 다음을 포함해야 합니다:", + "informPasswordPolicyLength": "8~25자", + "informPasswordPolicyLowerAndUpper": "대문자 및 소문자", + "informPasswordPolicyNumber": "최소 하나 이상의 숫자", + "warnPasswordContainsSymbol": "비밀번호를 강화하기 위해 기호를 사용하세요.", + "labelReferralCode": "추천 코드", + "actionTryAgain": "재시도", + "informInvalid2FACode": "입력하신 링크가 유효하지 않습니다. 확인 및 다시 시도해 주세요.", + "informFailedAuthentication": "이메일 또는 비밀번호가 올바르지 않을 수 있습니다. 혹시 소셜 계정으로 가입하셨나요? 확인 후 다시 시도해 주시기 바랍니다.", + "informDeactivatedAccount": "계정이 비활성화되었습니다.", + "informFailedAuthorization": "인증에 실패했습니다.", + "informInvalidResidence": "유효하지 않은 거주지입니다.", + "informInvalidCredentials": "유효하지 않은 자격 증명입니다.", + "informMissingOtp": "일회용 비밀번호가 누락되었습니다.", + "informSelfClosed": "계정이 해지되었습니다.", + "informUnexpectedError": "예상하지 못한 오류가 발생했습니다.", + "informUnsupportedCountry": "귀하의 국가는 지원되지 않습니다.", + "informExpiredAccount": "계정이 만료되었습니다", + "labelCountryConsentBrazil": "본인은 브라질 이외 지역에서만 발행 및 제공되는 OTC 상품을 거래하기 위해 Deriv를 통한 계정을 개설하고자 하는 요청이 본인에 의해 시작되었음을 확정합니다. 본인은 Deriv가 CVM의 규제를 받지 않는다는 것을 충분히 이해하며 Deriv에 접근하여 외국 회사와 관계를 구축하고자 하는 것입니다.", + "informConnectionError": "연결 오류입니다. 나중에 다시 시도하시기 바랍니다.", + "informSwitchAccountError": "계정 전환 오류입니다. 나중에 다시 시도하시기 바랍니다.", + "labelDeveloper": "개발자", + "labelEndpoint": "엔드포인트", + "semanticEndpoint": "엔드포인트", + "warnInvalidEndpoint": "유효하지 않은 엔드포인트", + "labelApplicationID": "애플리케이션 ID", + "semanticApplicationID": "애플리케이션 ID", + "warnInvalidApplicationID": "유효하지 않은 애플리케이션 ID", + "labelLanguage": "언어" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_auth/app_mn.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_mn.arb new file mode 100644 index 000000000..8ac9be2bf --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_mn.arb @@ -0,0 +1,117 @@ +{ + "labelNotAvailable": "Not available", + "warnNotAvailableCountriesTitle": "{app} isn't available in your country", + "@warnNotAvailableCountriesTitle": { + "description": "A message with a single parameter", + "placeholders": { + "app": { + "type": "String", + "example": "Deriv GO" + } + } + }, + "actionOk": "OK", + "warnNotAvailableCountries": "If you have any questions, contact us via ", + "labelLiveChat": "Live chat", + "actionSignUpForFree": "Sign up for free", + "actionLogin": "Log in", + "labelTwoFactorAuth": "Two-factor authentication", + "informEnterTwoFactorAuthCode": "Enter the 6-digit code from the authenticator app on your phone.", + "labelTwoFactorAuthenticationCode": "2FA code", + "actionProceed": "Proceed", + "labelLogIn": "Log in", + "informLoginOptions": "Or log in with", + "labelEmail": "Email", + "labelPassword": "Password", + "actionForgotPassword": "Forgot password?", + "labelDontHaveAnAccountYet": "Don’t have an account yet?", + "actionCreateANewAccount": "Create a new account", + "informInvalidEmailFormat": "Enter a valid email address", + "warnPasswordLength": "You should enter 6-25 characters.", + "labelResetPassword": "Reset password", + "labelChooseNewPass": "Choose a new password", + "labelCreatePass": "Password", + "informYourPassHasBeenReset": "Your password has been reset", + "informRedirectLogin": "You’ll need to log in with your new password. Hang on, we’re redirecting you.", + "actionResetPass": "Reset my password", + "informInvalidPasswordFormat": "Please enter a valid password format", + "labelCheckEmail": "Check your email", + "informSendResetPasswordEmail": "We’ve sent a message to {email} with a link to reset your password.", + "@informSendResetPasswordEmail": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "informResetPassByEmail": "We'll email you instructions to reset your password.", + "labelSelectCountry": "Where do you live?", + "labelChooseCountry": "Choose country", + "warnCountryNotAvailable": "Unfortunately, Deriv is not available in your country.", + "actionNext": "Next", + "labelEmailIssueHeader": "If you don't see an email from us within a few minutes, a few things could have happened:", + "labelEmailIssueSpam": "The email is in your spam folder (Sometimes things get lost there).", + "labelEmailIssueWrongEmail": "You accidentally gave us another email address (Usually a work or a personal one instead of the one you meant).", + "labelEmailIssueTypo": "The email address you entered had a mistake or typo (happens to the best of us).", + "labelEmailIssueFirewall": "We can't deliver the email to this address (Usually because of firewalls or filtering).", + "actionReenterEmail": "Re-enter your email and try again", + "labelKeepPassword": "Keep your account secure with a password", + "labelCreatePassword": "Create a password", + "actionStartTrading": "Start trading", + "actionPrevious": "Previous", + "labelSignUp": "Sign up", + "labelOrSignUpWith": "Or sign up with", + "labelReferralInfoTitle": "Affiliate referral code", + "infoReferralInfoDescription": "An alphanumeric code provided by a Deriv affiliate, applicable for email sign-ups only.", + "labelGotReferralCode": "Got a referral code?", + "labelHaveAccount": "Already have an account?", + "actionCreateAccount": "Create free demo account", + "informInvalidReferralCode": "The referral code you entered is invalid. Check and try again.", + "labelVerifyYourEmail": "Verify your email", + "labelThanksEmail": "Thanks for verifying your email", + "informLetsContinue": "Let's continue.", + "actionContinue": "Continue", + "labelSearchCountry": "Search country", + "informVerificationEmailSent": "We've sent a message to {email} with a link to activate your account.", + "@informVerificationEmailSent": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "actionEmailNotReceived": "Didn't receive your email?", + "informPasswordPolicy": "Your password must have:", + "informPasswordPolicyLength": "8-25 characters", + "informPasswordPolicyLowerAndUpper": "Upper and lower case letters", + "informPasswordPolicyNumber": "At least one number", + "warnPasswordContainsSymbol": "Use symbols for strong password.", + "labelReferralCode": "Referral Code", + "actionTryAgain": "Try Again", + "informInvalid2FACode": "The code you entered is invalid. Check and try again.", + "informFailedAuthentication": "Your email or password may be incorrect. Did you sign up with a social account? Check and try again.", + "informDeactivatedAccount": "Your account is deactivated.", + "informFailedAuthorization": "Authorization failed.", + "informInvalidResidence": "Invalid residence.", + "informInvalidCredentials": "Invalid credentials.", + "informMissingOtp": "Missing one-time password.", + "informSelfClosed": "Your account has been closed.", + "informUnexpectedError": "An unexpected error occurred.", + "informUnsupportedCountry": "Your country is not supported.", + "informExpiredAccount": "Your account is expired", + "labelCountryConsentBrazil": "I hereby confirm that my request for opening an account with Deriv to trade OTC products issued and offered exclusively outside Brazil was initiated by me. I fully understand that Deriv is not regulated by CVM and by approaching Deriv I intend to set up a relation with a foreign company.", + "informConnectionError": "Connection error. Please try again later.", + "informSwitchAccountError": "Switch account error. Please try again later.", + "labelDeveloper": "Developer", + "labelEndpoint": "Endpoint", + "semanticEndpoint": "Endpoint", + "warnInvalidEndpoint": "invalid endpoint", + "labelApplicationID": "Application ID", + "semanticApplicationID": "Application ID", + "warnInvalidApplicationID": "invalid application ID", + "labelLanguage": "Language" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_auth/app_pl.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_pl.arb new file mode 100644 index 000000000..10f5404b7 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_pl.arb @@ -0,0 +1,117 @@ +{ + "labelNotAvailable": "Niedostępne", + "warnNotAvailableCountriesTitle": "Plik {app} nie jest dostępny w Twoim kraju", + "@warnNotAvailableCountriesTitle": { + "description": "A message with a single parameter", + "placeholders": { + "app": { + "type": "String", + "example": "Deriv GO" + } + } + }, + "actionOk": "OK", + "warnNotAvailableCountries": "Jeśli mają Państwo jakiekolwiek pytania, prosimy o kontakt ", + "labelLiveChat": "Czat na żywo", + "actionSignUpForFree": "Zarejestruj się za darmo", + "actionLogin": "Zaloguj", + "labelTwoFactorAuth": "Uwierzytelnianie dwuskładnikowe", + "informEnterTwoFactorAuthCode": "Proszę wprowadzić 6-cyfrowy kod z aplikacji uwierzytelniającej w telefonie.", + "labelTwoFactorAuthenticationCode": "Kod dwuskładnikowy", + "actionProceed": "Kontynuuj", + "labelLogIn": "Zaloguj", + "informLoginOptions": "Lub zaloguj się przez", + "labelEmail": "E-mail", + "labelPassword": "Hasło", + "actionForgotPassword": "Nie pamiętasz hasła?", + "labelDontHaveAnAccountYet": "Nie mają Państwo jeszcze konta?", + "actionCreateANewAccount": "Załóż nowe konto", + "informInvalidEmailFormat": "Proszę wprowadzić prawidłowy adres e-mail", + "warnPasswordLength": "Wprowadź od 8 do 25 znaków.", + "labelResetPassword": "Zresetuj hasło", + "labelChooseNewPass": "Wybierz nowe hasło", + "labelCreatePass": "Hasło", + "informYourPassHasBeenReset": "Państwa hasło zostało zresetowane", + "informRedirectLogin": "Proszę zalogować się przy użyciu nowego hasła. Proszę poczekać, przekierowujemy Pana/Panią.", + "actionResetPass": "Zresetuj moje hasło", + "informInvalidPasswordFormat": "Wprowadź prawidłowy format hasła", + "labelCheckEmail": "Sprawdź swój adres e-mail", + "informSendResetPasswordEmail": "Wysłaliśmy wiadomość na adres {email} z linkiem umożliwiającym zresetowanie hasła.", + "@informSendResetPasswordEmail": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "informResetPassByEmail": "Prześlemy Ci wiadomość e-mail z instrukcją resetowania hasła.", + "labelSelectCountry": "Gdzie mieszkasz?", + "labelChooseCountry": "Wybierz kraj", + "warnCountryNotAvailable": "Niestety Deriv jest niedostępny w Twoim kraju.", + "actionNext": "Następny", + "labelEmailIssueHeader": "Jeśli nie widzisz wiadomości e-mail od nas już od kilku minut, kilka rzeczy może być tego powodem:", + "labelEmailIssueSpam": "Wiadomość e-mail trafiła do folderu spam (czasami wiadomości się tam gubią).", + "labelEmailIssueWrongEmail": "Przez przypadek podałeś/podałaś nam inny adres e-mail (zazwyczaj jest to adres e-mail z pracy lun osobisty, zamiast właściwego).", + "labelEmailIssueTypo": "Wprowadzony przez Ciebie adres e-mail zawiera błąd lub literówkę (zdarza się najlepszym).", + "labelEmailIssueFirewall": "Nie jesteśmy w stanie dostarczyć wiadomości e-mail pod ten adres (zazwyczaj z powodu zapory ogniowej lub filtrów).", + "actionReenterEmail": "Wprowadź email i spróbuj ponownie", + "labelKeepPassword": "Zabezpiecz swoje konto hasłem", + "labelCreatePassword": "Utwórz hasło", + "actionStartTrading": "Rozpocznij handlowanie", + "actionPrevious": "Poprzedni", + "labelSignUp": "Zarejestruj się", + "labelOrSignUpWith": "Lub zarejestruj się przy użyciu", + "labelReferralInfoTitle": "Kod polecający partnera", + "infoReferralInfoDescription": "Kod alfanumeryczny dostarczony przez partnera Deriv, mający zastosowanie wyłącznie do rejestracji e-mail.", + "labelGotReferralCode": "Mają Państwo kod polecający?", + "labelHaveAccount": "Masz już konto?", + "actionCreateAccount": "Utwórz darmowe konto demonstracyjne", + "informInvalidReferralCode": "Wprowadzony kod polecający jest nieprawidłowy. Proszę sprawdzić i spróbować ponownie.", + "labelVerifyYourEmail": "Zweryfikuj adres e-mail", + "labelThanksEmail": "Dziękujemy za zweryfikowanie adresu e-mail", + "informLetsContinue": "Proszę kontynuować.", + "actionContinue": "Kontynuuj", + "labelSearchCountry": "Proszę wyszukać kraj", + "informVerificationEmailSent": "Wysłaliśmy wiadomość na adres {email} z linkiem do aktywacji Państwa konta.", + "@informVerificationEmailSent": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "actionEmailNotReceived": "Wiadomość e-mail nie dotarła?", + "informPasswordPolicy": "Państwa hasło musi zawierać:", + "informPasswordPolicyLength": "8-25 znaków", + "informPasswordPolicyLowerAndUpper": "Wielkie i małe litery", + "informPasswordPolicyNumber": "Co najmniej jeden numer", + "warnPasswordContainsSymbol": "Proszę używać symboli dla silnego hasła.", + "labelReferralCode": "Kod polecenia", + "actionTryAgain": "Spróbuj ponownie", + "informInvalid2FACode": "Wprowadzony kod jest nieprawidłowy. Proszę sprawdzić i spróbować ponownie.", + "informFailedAuthentication": "Państwa adres e-mail lub hasło mogą być nieprawidłowe. Czy zarejestrował(a) się Pan(i) za pomocą konta społecznościowego? Proszę sprawdzić i spróbować ponownie.", + "informDeactivatedAccount": "Twoje konto jest dezaktywowane.", + "informFailedAuthorization": "Autoryzacja nie powiodła się.", + "informInvalidResidence": "Nieprawidłowe miejsce zamieszkania.", + "informInvalidCredentials": "Nieprawidłowe poświadczenia.", + "informMissingOtp": "Brakujące jednorazowe hasło.", + "informSelfClosed": "To konto zostało zamknięte.", + "informUnexpectedError": "Wystąpił nieoczekiwany błąd.", + "informUnsupportedCountry": "Twój kraj nie jest wspierany.", + "informExpiredAccount": "Twoje konto wygasło", + "labelCountryConsentBrazil": "Niniejszym potwierdzam, że mój wniosek o otwarcie rachunku w Deriv w celu handlu produktami OTC emitowanymi i oferowanymi wyłącznie poza Brazylią został zainicjowany przeze mnie. W pełni rozumiem, że Deriv nie jest regulowany przez CVM i zwracając się do Deriv, zamierzam nawiązać relację z zagraniczną firmą.", + "informConnectionError": "Błąd połączenia. Spróbuj ponownie później.", + "informSwitchAccountError": "Błąd przełączania konta. Spróbuj ponownie później.", + "labelDeveloper": "Deweloper", + "labelEndpoint": "Punkt końcowy", + "semanticEndpoint": "punkt końcowy", + "warnInvalidEndpoint": "nieprawidłowy punkt końcowy", + "labelApplicationID": "ID aplikacji", + "semanticApplicationID": "ID aplikacji", + "warnInvalidApplicationID": "nieprawidłowe ID aplikacji", + "labelLanguage": "Język" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_auth/app_pt.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_pt.arb new file mode 100644 index 000000000..08888e070 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_pt.arb @@ -0,0 +1,117 @@ +{ + "labelNotAvailable": "Indisponível", + "warnNotAvailableCountriesTitle": "A {app} não está disponível no seu país", + "@warnNotAvailableCountriesTitle": { + "description": "A message with a single parameter", + "placeholders": { + "app": { + "type": "String", + "example": "Deriv GO" + } + } + }, + "actionOk": "OK", + "warnNotAvailableCountries": "Em caso de dúvidas, contacte-nos via ", + "labelLiveChat": "Live chat", + "actionSignUpForFree": "Registe-se gratuitamente", + "actionLogin": "Iniciar sessão", + "labelTwoFactorAuth": "Autenticação de dois fatores", + "informEnterTwoFactorAuthCode": "Introduza o código de 6 dígitos da aplicação de autenticação no seu telemóvel.", + "labelTwoFactorAuthenticationCode": "Código de autenticação de dois fatores (2FA)", + "actionProceed": "Continuar", + "labelLogIn": "Iniciar sessão", + "informLoginOptions": "Ou inicie sessão através de", + "labelEmail": "E-mail", + "labelPassword": "Palavra-passe", + "actionForgotPassword": "Esqueceu-se da palavra-passe?", + "labelDontHaveAnAccountYet": "Ainda não tem uma conta?", + "actionCreateANewAccount": "Criar nova conta", + "informInvalidEmailFormat": "Introduza um e-mail válido", + "warnPasswordLength": "Deve introduzir entre 6 a 25 caracteres.", + "labelResetPassword": "Repor palavra-passe", + "labelChooseNewPass": "Escolha uma nova palavra-passe", + "labelCreatePass": "Palavra-passe", + "informYourPassHasBeenReset": "A palavra-passe foi alterada", + "informRedirectLogin": "É necessário iniciar sessão com a sua nova palavra-passe. Aguarde, estamos a reencaminhá-lo.", + "actionResetPass": "Alterar palavra-passe", + "informInvalidPasswordFormat": "Introduza um formato de palavra-passe válido", + "labelCheckEmail": "Verifique seu e-mail", + "informSendResetPasswordEmail": "Enviámos uma mensagem para o e-mail {email} com um link para alterar a sua palavra-passe.", + "@informSendResetPasswordEmail": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "informResetPassByEmail": "Vamos enviar um e-mail com as instruções para alterar a sua palavra-passe.", + "labelSelectCountry": "Onde é que vive?", + "labelChooseCountry": "Selecionar país", + "warnCountryNotAvailable": "Infelizmente, a Deriv não está disponível no seu país.", + "actionNext": "Próximo", + "labelEmailIssueHeader": "Se não receber um e-mail nosso dentro de alguns minutos, é possível que:", + "labelEmailIssueSpam": "O e-mail encontre-se na pasta de spam (às vezes os e-mails ficam perdidos aí).", + "labelEmailIssueWrongEmail": "Acidentalmente, deu-nos outro e-mail (normalmente um e-mail de trabalho ou pessoal, em vez daquele que pretendia).", + "labelEmailIssueTypo": "O e-mail que introduziu tinha um erro ou uma gralha (acontece aos melhores).", + "labelEmailIssueFirewall": "Não foi possível enviar um e-mail para este endereço (normalmente devido a firewalls ou filtragem).", + "actionReenterEmail": "Volte a introduzir o seu e-mail e tente novamente", + "labelKeepPassword": "Mantenha a sua conta segura utilizando uma palavra-passe", + "labelCreatePassword": "Criar palavra-passe", + "actionStartTrading": "Começar a negociar", + "actionPrevious": "Anterior", + "labelSignUp": "Registe-se", + "labelOrSignUpWith": "Ou registe-se através de", + "labelReferralInfoTitle": "Código de referência de afiliado", + "infoReferralInfoDescription": "Código alfanumérico fornecido por um afiliado Deriv, aplicável apenas para registos por e-mail.", + "labelGotReferralCode": "Tem um código de referência?", + "labelHaveAccount": "Já tem uma conta?", + "actionCreateAccount": "Criar conta demo gratuita", + "informInvalidReferralCode": "O código de referência que introduziu não é válido. Verifique e tente novamente.", + "labelVerifyYourEmail": "Validar e-mail", + "labelThanksEmail": "Obrigado por validar o seu e-mail", + "informLetsContinue": "Vamos continuar.", + "actionContinue": "Continuar", + "labelSearchCountry": "Pesquisar país", + "informVerificationEmailSent": "Enviámos uma mensagem para o {email} com o link para ativar a sua conta.", + "@informVerificationEmailSent": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "actionEmailNotReceived": "Não recebeu o e-mail?", + "informPasswordPolicy": "A palavra-passe deve ter:", + "informPasswordPolicyLength": "Entre 8 a 25 caracteres", + "informPasswordPolicyLowerAndUpper": "Letras maiúsculas e minúsculas", + "informPasswordPolicyNumber": "Pelo menos um número", + "warnPasswordContainsSymbol": "Utilize símbolos para criar uma palavra-passe forte.", + "labelReferralCode": "Código de referência", + "actionTryAgain": "Tente novamente", + "informInvalid2FACode": "O código que introduziu não é válido. Verifique e tente novamente.", + "informFailedAuthentication": "O e-mail ou a palavra-passe podem estar incorretos. Registou-se através de umas das contas das redes sociais? Verifique e tente novamente.", + "informDeactivatedAccount": "A sua conta encontra-se desativada.", + "informFailedAuthorization": "A autorização não foi bem-sucedida.", + "informInvalidResidence": "Morada inválida.", + "informInvalidCredentials": "Credenciais inválidas.", + "informMissingOtp": "Falta a palavra-passe de utilização única.", + "informSelfClosed": "A sua conta foi encerrada.", + "informUnexpectedError": "Ocorreu um erro inesperado.", + "informUnsupportedCountry": "O seu país não está disponível.", + "informExpiredAccount": "A sua conta expirou", + "labelCountryConsentBrazil": "Venho por este meio confirmar que iniciei o pedido de abertura de conta na Deriv para negociar produtos OTC emitidos e oferecidos exclusivamente fora do Brasil. Estou ciente que a Deriv não é regulada pela CVM e que ao abordar a Deriv, estabeleço uma relação com uma empresa estrangeira.", + "informConnectionError": "Erro de ligação. Por favor, tente novamente mais tarde.", + "informSwitchAccountError": "Ocorreu um erro durante a troca de conta. Tente novamente mais tarde.", + "labelDeveloper": "Programador", + "labelEndpoint": "Endpoint", + "semanticEndpoint": "Endpoint", + "warnInvalidEndpoint": "endpoint inválido", + "labelApplicationID": "ID da aplicação", + "semanticApplicationID": "ID da aplicação", + "warnInvalidApplicationID": "ID da aplicação inválido", + "labelLanguage": "Idioma" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_auth/app_ru.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_ru.arb new file mode 100644 index 000000000..392046a43 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_ru.arb @@ -0,0 +1,117 @@ +{ + "labelNotAvailable": "Недоступно", + "warnNotAvailableCountriesTitle": "{app} недоступен в вашей стране", + "@warnNotAvailableCountriesTitle": { + "description": "A message with a single parameter", + "placeholders": { + "app": { + "type": "String", + "example": "Deriv GO" + } + } + }, + "actionOk": "OK", + "warnNotAvailableCountries": "Если у Вас возникли вопросы, свяжитесь с нами по ", + "labelLiveChat": "Чат", + "actionSignUpForFree": "Зарегистрируйтесь бесплатно", + "actionLogin": "Вход", + "labelTwoFactorAuth": "Двухфакторная аутентификация", + "informEnterTwoFactorAuthCode": "Введите 6-значный код из приложения аутентификатора на Вашем телефоне.", + "labelTwoFactorAuthenticationCode": "Код 2FA", + "actionProceed": "Далее", + "labelLogIn": "Вход", + "informLoginOptions": "Или войдите через", + "labelEmail": "Email", + "labelPassword": "Пароль", + "actionForgotPassword": "Забыли пароль?", + "labelDontHaveAnAccountYet": "У Вас еще нет учетной записи?", + "actionCreateANewAccount": "Откройте новый счет", + "informInvalidEmailFormat": "Введите действительный адрес электронной почты", + "warnPasswordLength": "Вы должны ввести 8-25 символов.", + "labelResetPassword": "Сбросить пароль", + "labelChooseNewPass": "Выберите новый пароль", + "labelCreatePass": "Пароль", + "informYourPassHasBeenReset": "Ваш пароль был сброшен", + "informRedirectLogin": "Вам нужно будет войти в систему с новым паролем. Подождите, мы перенаправляем Вас.", + "actionResetPass": "Изменить пароль", + "informInvalidPasswordFormat": "Введите пароль в действующем формате", + "labelCheckEmail": "Проверьте эл. почту", + "informSendResetPasswordEmail": "Мы отправили сообщение на адрес {email} со ссылкой для сброса Вашего пароля.", + "@informSendResetPasswordEmail": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "informResetPassByEmail": "Инструкции для изменения пароля будут отправлены на ваш эл. адрес.", + "labelSelectCountry": "Где вы живете?", + "labelChooseCountry": "Выберите страну", + "warnCountryNotAvailable": "К сожалению, Deriv недоступен в вашей стране.", + "actionNext": "Далее", + "labelEmailIssueHeader": "Если вы не получили наше письмо в течение нескольких минут, возможно, произошло следующее:", + "labelEmailIssueSpam": "Письмо попало в папку со спамом.", + "labelEmailIssueWrongEmail": "Вы по ошибке указали неправильный email (как правило, рабочий или личный email вместо нужного).", + "labelEmailIssueTypo": "Адрес эл. почты был введен с ошибкой или опечаткой (случается с лучшими из нас).", + "labelEmailIssueFirewall": "Мы не можем отправить письмо на этот адрес (из-за использования firewall или фильтрации).", + "actionReenterEmail": "Введите email и попробуйте снова", + "labelKeepPassword": "Защитите счет паролем", + "labelCreatePassword": "Придумайте пароль", + "actionStartTrading": "Начать торговлю", + "actionPrevious": "Назад", + "labelSignUp": "Регистрация", + "labelOrSignUpWith": "Или войдите через", + "labelReferralInfoTitle": "Партнерский реферальный код", + "infoReferralInfoDescription": "Буквенно-цифровой код, предоставляемый партнером Deriv, применимый только для регистрации по электронной почте.", + "labelGotReferralCode": "У Вас есть реферальный код?", + "labelHaveAccount": "Уже зарегистрированы?", + "actionCreateAccount": "Открыть бесплатный демо-счет", + "informInvalidReferralCode": "Введенный Вами реферальный код недействителен. Проверьте и попробуйте еще раз.", + "labelVerifyYourEmail": "Подтвердите эл. адрес", + "labelThanksEmail": "Спасибо за подтверждение электронной почты", + "informLetsContinue": "Давайте продолжим.", + "actionContinue": "Продолжить", + "labelSearchCountry": "Страна поиска", + "informVerificationEmailSent": "Мы отправили сообщение на {email} со ссылкой для активации Вашей учетной записи.", + "@informVerificationEmailSent": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "actionEmailNotReceived": "Не получили письмо?", + "informPasswordPolicy": "Ваш пароль должен иметь:", + "informPasswordPolicyLength": "8-25 символов", + "informPasswordPolicyLowerAndUpper": "Строчные и заглавные буквы", + "informPasswordPolicyNumber": "По крайней мере, одно число", + "warnPasswordContainsSymbol": "Используйте символы для создания надежного пароля.", + "labelReferralCode": "Реферальный код", + "actionTryAgain": "Повторить", + "informInvalid2FACode": "Введенный Вами код недействителен. Проверьте и попробуйте еще раз.", + "informFailedAuthentication": "Возможно, Ваш e-mail или пароль неверны. Вы зарегистрировались с помощью социальной учетной записи? Проверьте и попробуйте еще раз.", + "informDeactivatedAccount": "Ваш аккаунт деактивирован.", + "informFailedAuthorization": "Не удалось авторизоваться.", + "informInvalidResidence": "Недействительное место жительства.", + "informInvalidCredentials": "Неверные учетные данные.", + "informMissingOtp": "Отсутствует одноразовый пароль.", + "informSelfClosed": "Ваш счет был закрыт.", + "informUnexpectedError": "Произошла неожиданная ошибка.", + "informUnsupportedCountry": "Ваша страна не пользуется поддержкой.", + "informExpiredAccount": "Срок действия вашей учетной записи истек", + "labelCountryConsentBrazil": "Настоящим я подтверждаю, что запрос на открытие счета в Deriv для торговли внебиржевыми продуктами, выпущенными и предлагаемыми исключительно за пределами Бразилии, был инициирован мной. Я понимаю, что деятельность Deriv не регулируется CVM, и, обратившись в Deriv, вступаю в деловые отношения с иностранной компанией.", + "informConnectionError": "Ошибка подключения. Пожалуйста, попробуйте позже.", + "informSwitchAccountError": "Ошибка переключения учетной записи. Пожалуйста, попробуйте позже.", + "labelDeveloper": "Разработчик", + "labelEndpoint": "Конечная точка", + "semanticEndpoint": "Конечная точка", + "warnInvalidEndpoint": "неверная конечная точка", + "labelApplicationID": "ID приложения", + "semanticApplicationID": "ID приложения", + "warnInvalidApplicationID": "неверный ID приложения", + "labelLanguage": "Язык" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_auth/app_si.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_si.arb new file mode 100644 index 000000000..a821d796c --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_si.arb @@ -0,0 +1,117 @@ +{ + "labelNotAvailable": "ලබා ගත නොහැක", + "warnNotAvailableCountriesTitle": "{app} ඔබේ රටේ නොමැත", + "@warnNotAvailableCountriesTitle": { + "description": "A message with a single parameter", + "placeholders": { + "app": { + "type": "String", + "example": "Deriv GO" + } + } + }, + "actionOk": "හරි", + "warnNotAvailableCountries": "ඔබට කිසියම් ප්රශ්නයක් ඇත්නම්, අප අමතන්න ", + "labelLiveChat": "සජීවී කතාබස්", + "actionSignUpForFree": "නොමිලේ ලියාපදිංචි වන්න", + "actionLogin": "පුරනය වන්න", + "labelTwoFactorAuth": "ද්වි-සාධක සත්‍යාපනය", + "informEnterTwoFactorAuthCode": "ඔබේ දුරකථනයේ සත්යාපන යෙදුමෙන් ඉලක්කම් 6 ක කේතය ඇතුළත් කරන්න.", + "labelTwoFactorAuthenticationCode": "2FA කේතය", + "actionProceed": "ඉදිරියට යන්න", + "labelLogIn": "පුරනය වන්න", + "informLoginOptions": "නැතහොත් සමඟ ලොග් වන්න", + "labelEmail": "ඊ-තැපෑල", + "labelPassword": "මුරපදය", + "actionForgotPassword": "මුරපදය අමතක ද?", + "labelDontHaveAnAccountYet": "තවම ගිණුමක් නැද්ද?", + "actionCreateANewAccount": "නව ගිණුමක් සාදන්න", + "informInvalidEmailFormat": "වලංගු විද්යුත් තැපැල් ලිපිනයක් ඇතුළත් කරන්න", + "warnPasswordLength": "ඔබ අක්ෂර 8-25 ක් ඇතුළත් කළ යුතුය.", + "labelResetPassword": "මුරපදය නැවත සකස් කරන්න", + "labelChooseNewPass": "නව මුරපදයක් තෝරන්න", + "labelCreatePass": "මුරපදය", + "informYourPassHasBeenReset": "ඔබගේ මුරපදය නැවත සකසා ඇත", + "informRedirectLogin": "ඔබට ඔබගේ නව මුරපදය සමඟ ලොග් විය යුතුය. ඉතින්, අපි ඔයාව හරවා යවනවා.", + "actionResetPass": "මගේ මුරපදය යළි සකසන්න", + "informInvalidPasswordFormat": "කරුණාකර වලංගු මුරපද ආකෘතියක් ඇතුළත්", + "labelCheckEmail": "ඔබේ ඊ-තැපෑල පරීක්ෂා කරන්න​", + "informSendResetPasswordEmail": "ඔබගේ මුරපදය නැවත සකස් කිරීම සඳහා සබැඳියක් {email} සහිතව අපි වෙත පණිවිඩයක් යවා ඇත්තෙමු.", + "@informSendResetPasswordEmail": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "informResetPassByEmail": "ඔබේ මුරපදය යළි සැකසීමට අවශ්‍ය උපදෙස් අපි ඔබට ඊ-තැපෑලෙන් එවන්නෙමු.", + "labelSelectCountry": "ඔබ ජීවත් වන්නේ කොහේද?", + "labelChooseCountry": "රට තෝරන්න", + "warnCountryNotAvailable": "අවාසනාවකට මෙන්, ඩෙරිව් ඔබේ රටේ නොලැබේ.", + "actionNext": "ඊළඟ", + "labelEmailIssueHeader": "මිනිත්තු කිහිපයක් ඇතුළත ඔබට අපෙන් ඊ-තැපෑලක් නොලැබුණොත්, පහත සඳහන් දේවල් සිදු විය හැක:", + "labelEmailIssueSpam": "ඊ-තැපෑල ඔබේ අයාචිත තැපැල් ෆෝල්ඩරයේ ඇත (සමහර ඊ-තැපැල් ඒ වෙත යොමු වෙයි).", + "labelEmailIssueWrongEmail": "ඔබ අහම්බෙන් අපට වෙනත් ඊ-තැපැල් ලිපිනයක් ලබා දී ඇත​ (සාමාන්‍යයෙන් ඔබ අදහස් කරන එක වෙනුවට කාර්යාලීය හෝ පෞද්ගලික ඊ තැපැල් ලිපිනයක්).", + "labelEmailIssueTypo": "ඔබ ඇතුළත් කළ​ ඊ-තැපැල් ලිපිනයේ වැරදීමක් හෝ මුද්‍රණ දෝෂයක් ඇත (බොහෝ අයට මෙය සිදු වේ).", + "labelEmailIssueFirewall": "අපිට මේ ලිපිනයට ඊමේල් එක බාර දෙන්න බැහැ (සාමාන්යයෙන් ෆයර්වෝල් හෝ ෆිල්ටිං නිසා).", + "actionReenterEmail": "ඔබගේ ඊ-තැපෑල නැවත ඇතුළත් කර නැවත උත්සාහ කරන්න", + "labelKeepPassword": "මුරපදයකින් ඔබේ ගිණුම සුරක්ෂිතව තබා ගන්න", + "labelCreatePassword": "මුරපදයක් සාදන්න", + "actionStartTrading": "ගනුදෙනු ආරම්භ කරන්න", + "actionPrevious": "පෙර", + "labelSignUp": "ලියාපදිංචි වන්න", + "labelOrSignUpWith": "නැතහොත් ලියාපදිංචි වන්න", + "labelReferralInfoTitle": "අනුබද්ධ යොමු කේතය", + "infoReferralInfoDescription": "ඊමේල් ලියාපදිංචි කිරීම් සඳහා පමණක් අදාළ වන ඩෙරිව් අනුබද්ධ සමාගමක් විසින් සපයනු ලබන අක්ෂර කේතයක්.", + "labelGotReferralCode": "යොමු කේතයක් තිබේද?", + "labelHaveAccount": "දැනටමත් ගිණුමක් තිබේද?", + "actionCreateAccount": "නොමිලේ ආදර්ශන ගිණුමක් සාදන්න", + "informInvalidReferralCode": "ඔබ ඇතුළත් කළ යොමු කේතය අවලංගු වේ. පරීක්ෂා කර නැවත උත්සාහ කරන්න.", + "labelVerifyYourEmail": "ඔබගේ විද්යුත් තැපෑල තහවුරු", + "labelThanksEmail": "ඔබගේ විද්යුත් තැපෑල සත්යාපනය කිරීම ගැන ස්ත", + "informLetsContinue": "අපි දිගටම කරගෙන යමු.", + "actionContinue": "ඉදිරියට යන්න", + "labelSearchCountry": "රට සොයන්න", + "informVerificationEmailSent": "ඔබගේ ගිණුම සක්රිය කිරීම සඳහා සබැඳියක් ස {email} හිතව අපි වෙත පණිවිඩයක් යවා ඇත්තෙමු.", + "@informVerificationEmailSent": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "actionEmailNotReceived": "ඔබගේ ඊ-තැපෑල ලැබුණේ නැද්ද?", + "informPasswordPolicy": "ඔබගේ මුරපදය තිබිය යුතුය:", + "informPasswordPolicyLength": "අක්ෂර 8-25", + "informPasswordPolicyLowerAndUpper": "ඉහළ සහ පහළ අකුරු", + "informPasswordPolicyNumber": "අවම වශයෙන් එක් අංකයක්", + "warnPasswordContainsSymbol": "ශක්තිමත් මුරපදය සඳහා සංකේත භාවිතා කරන්න.", + "labelReferralCode": "යොමු කේතය", + "actionTryAgain": "නැවත උත්සාහ කරන්න", + "informInvalid2FACode": "ඔබ ඇතුළත් කළ කේතය අවලංගු වේ. පරීක්ෂා කර නැවත උත්සාහ කරන්න.", + "informFailedAuthentication": "ඔබගේ විද්යුත් තැපෑල හෝ මුරපදය වැරදි විය හැකිය. ඔබ සමාජ ගිණුමක් සමඟ ලියාපදිංචි වූවාද? පරීක්ෂා කර නැවත උත්සාහ කරන්න.", + "informDeactivatedAccount": "ඔබගේ ගිණුම අක්රිය කර ඇත.", + "informFailedAuthorization": "බලය අසාර්ථක විය.", + "informInvalidResidence": "අවලංගු පදිංචිය.", + "informInvalidCredentials": "අවලංගු අක්තපත්ර", + "informMissingOtp": "එක් වරක් මුරපදය අතුරුදහන් වේ.", + "informSelfClosed": "ඔබේ ගිණුම වසා ඇත.", + "informUnexpectedError": "අනපේක්ෂිත දෝෂයක් ඇති විය.", + "informUnsupportedCountry": "ඔබේ රට සහයෝගය නොදක්වයි.", + "informExpiredAccount": "ඔබගේ ගිණුම කල් ඉකුත් වී ඇත", + "labelCountryConsentBrazil": "බ්‍රසීලයෙන් පිටතදී නිකුත් කරන ලද සහ පිරිනමන OTC නිෂ්පාදන ගනුදෙනු කිරීමට Deriv සමඟ ගිණුමක් විවෘත කිරීම සඳහා වන මගේ ඉල්ලීම මා විසින් ආරම්භ කරන ලද බව මෙයින් තහවුරු කරමි. Deriv CVM මඟින් නියාමනය නොවන බව මට හොඳින් වැටහෙන අතර Deriv වෙත පිවිසීමෙන් මම විදේශීය සමාගමක් සමඟ සබඳතාවක් පිහිටුවීමට අදහස් කරමි.", + "informConnectionError": "සම්බන්ධතා දෝෂය. කරුණාකර පසුව නැවත උත්සාහ කරන්න.", + "informSwitchAccountError": "ගිණුම් දෝෂය මාරු කරන්න. කරුණාකර පසුව නැවත උත්සාහ කරන්න.", + "labelDeveloper": "සංවර්ධක", + "labelEndpoint": "අන්ත ලක්ෂ්‍යය", + "semanticEndpoint": "අන්ත ලක්ෂ්‍යය", + "warnInvalidEndpoint": "අවලංගු අවසාන ලක්ෂ්යය", + "labelApplicationID": "අයදුම් හැඳුනුම්පත", + "semanticApplicationID": "අයදුම් හැඳුනුම්පත", + "warnInvalidApplicationID": "අවලංගු යෙදුම් හැඳුනු", + "labelLanguage": "භාෂාව" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_auth/app_sw.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_sw.arb new file mode 100644 index 000000000..8368afeb6 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_sw.arb @@ -0,0 +1,117 @@ +{ + "labelNotAvailable": "Haipatikani", + "warnNotAvailableCountriesTitle": "{app} haipatikani katika nchi yako", + "@warnNotAvailableCountriesTitle": { + "description": "A message with a single parameter", + "placeholders": { + "app": { + "type": "String", + "example": "Deriv GO" + } + } + }, + "actionOk": "SAWA", + "warnNotAvailableCountries": "Ikiwa una maswali yoyote, wasiliana nasi kupitia ", + "labelLiveChat": "Mazungumzo mubashara", + "actionSignUpForFree": "Jisajili bila malipo", + "actionLogin": "Ingia", + "labelTwoFactorAuth": "Uthibitishaji-wa hatua mbili", + "informEnterTwoFactorAuthCode": "Ingiza nambari ya tarakimu 6 kutoka kwa programu ya uthibitishaji kwenye simu yako.", + "labelTwoFactorAuthenticationCode": "Nambari ya 2FA", + "actionProceed": "Endelea", + "labelLogIn": "Ingia", + "informLoginOptions": "Au ingia na", + "labelEmail": "Barua pepe", + "labelPassword": "Nenosiri", + "actionForgotPassword": "Umesahau nenosiri?", + "labelDontHaveAnAccountYet": "Huna akaunti bado?", + "actionCreateANewAccount": "Unda akaunti mpya", + "informInvalidEmailFormat": "Ingiza anwani halali ya barua pepe", + "warnPasswordLength": "Unapaswa kuingiza mchanganyiko wa tarakimu 8-25.", + "labelResetPassword": "Rudisha nenosiri", + "labelChooseNewPass": "Chagua nenosiri mpya", + "labelCreatePass": "Nenosiri", + "informYourPassHasBeenReset": "Nenosiri lako limewekwa upya", + "informRedirectLogin": "Utahitaji kuingia na nenosiri lako jipya. Endelea, tunakuelekeza.", + "actionResetPass": "Weka upya nywila yangu", + "informInvalidPasswordFormat": "Tafadhali ingiza muundo halali wa nenosiri", + "labelCheckEmail": "Angalia barua pepe yako", + "informSendResetPasswordEmail": "Tumetuma ujumbe kwa {email} na kiungo cha kuweka upya nenosiri lako.", + "@informSendResetPasswordEmail": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "informResetPassByEmail": "Tutakutumia barua pepe yenye maelekezo ya kuweka upya nenosiri lako.", + "labelSelectCountry": "Unaishi wapi?", + "labelChooseCountry": "Chagua nchi", + "warnCountryNotAvailable": "Kwa bahati mbaya, Deriv haipatikani katika nchi yako.", + "actionNext": "Ifuatayo", + "labelEmailIssueHeader": "Ikiwa huoni barua pepe kutoka kwetu ndani ya dakika chache, mambo machache yanaweza kuwa yametokea:", + "labelEmailIssueSpam": "Barua pepe iko kwenye folda yako ya spam (Wakati mwingine mambo yanapotelea huko).", + "labelEmailIssueWrongEmail": "Kwa bahati mbaya ulitupa anwani nyingine ya barua pepe (Labda ya kazi au ya binafsi badala ya ile uliyokusudia).", + "labelEmailIssueTypo": "Anwani ya barua pepe uliyoingiza ilikuwa na makosa (hutokea kwa ubora kwetu sote).", + "labelEmailIssueFirewall": "Hatuwezi kutoa barua pepe kwa anwani hii (Kawaida kwa sababu ya firewalls au kuchuja).", + "actionReenterEmail": "Ingiza tena barua pepe yako na ujaribu tena", + "labelKeepPassword": "Weka akaunti yako salama na nenosiri", + "labelCreatePassword": "Unda nenosiri", + "actionStartTrading": "Anza biashara", + "actionPrevious": "Iliyotangulia", + "labelSignUp": "Jisajili", + "labelOrSignUpWith": "Au jisajili kwa", + "labelReferralInfoTitle": "Nambari ya rufaa ya ushirika", + "infoReferralInfoDescription": "Nambari ya alfanisi iliyotolewa na mshirika wa Deriv, inayotumika kwa usajili wa barua pepe tu.", + "labelGotReferralCode": "Una nambari ya rufaa?", + "labelHaveAccount": "Tayari una akaunti?", + "actionCreateAccount": "Unda demo akaunti", + "informInvalidReferralCode": "Nambari ya rufaa uliyoingiza sio halali. Angalia na jaribu tena.", + "labelVerifyYourEmail": "thibitisha barua pepe yako", + "labelThanksEmail": "Asante kwa kuthibitisha barua pepe", + "informLetsContinue": "Hebu tuendelee.", + "actionContinue": "Endelea", + "labelSearchCountry": "Tafuta nchi", + "informVerificationEmailSent": "Tumetuma ujumbe kwa {email} na kiungo cha kuamsha akaunti yako.", + "@informVerificationEmailSent": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "actionEmailNotReceived": "Hukupokea barua pepe yako?", + "informPasswordPolicy": "Nenosiri lako lazima iwe na:", + "informPasswordPolicyLength": "8-25 herufi", + "informPasswordPolicyLowerAndUpper": "Barua za juu na ndogo", + "informPasswordPolicyNumber": "Angalau nambari moja", + "warnPasswordContainsSymbol": "Tumia alama kwa nenosiri yenye nguvu.", + "labelReferralCode": "Msimbo wa Rufaa", + "actionTryAgain": "Jaribu tena", + "informInvalid2FACode": "Nambari uliyoingiza ni halali. Angalia na jaribu tena.", + "informFailedAuthentication": "Barua pepe yako au nenosiri inaweza kuwa sahihi. Je! Ulijiandikisha na akaunti ya kijamii? Angalia na jaribu tena.", + "informDeactivatedAccount": "Akaunti yako imezimwa.", + "informFailedAuthorization": "Idhini ilishindwa.", + "informInvalidResidence": "Makazi halali.", + "informInvalidCredentials": "Hati zisizo sahihi.", + "informMissingOtp": "Kukosa nenosiri la wakati mmoja.", + "informSelfClosed": "Akaunti yako imefungwa.", + "informUnexpectedError": "Hitilafu isiyotarajiwa ilitokea", + "informUnsupportedCountry": "Nchi yako haikuungwa mkono.", + "informExpiredAccount": "Akaunti yako imekwisha", + "labelCountryConsentBrazil": "Ninathibitisha kwamba ombi langu la kufungua akaunti na Deriv kufanya biashara ya bidhaa za OTC zilizotolewa na kutolewa tu nje ya Brazil lilianzishwa na mimi. Ninaelewa kikamilifu kwamba Deriv haidhibitiwi na CVM na kwa kukaribia Deriv nina nia ya kuanzisha uhusiano na kampuni ya kigeni.", + "informConnectionError": "Hitilafu ya unganisho Tafadhali jaribu tena baadaye.", + "informSwitchAccountError": "Badilisha kosa la akaunti. Tafadhali jaribu tena baadaye.", + "labelDeveloper": "Msanidi programu", + "labelEndpoint": "Pointi ya mwisho", + "semanticEndpoint": "Pointi ya mwisho", + "warnInvalidEndpoint": "hatua ya mwisho halali", + "labelApplicationID": "Kitambulisho cha programu", + "semanticApplicationID": "Kitambulisho cha programu", + "warnInvalidApplicationID": "kitambulisho cha programu halali", + "labelLanguage": "Lugha" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_auth/app_th.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_th.arb new file mode 100644 index 000000000..d7e04d06f --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_th.arb @@ -0,0 +1,117 @@ +{ + "labelNotAvailable": "ไม่พร้อมใช้งาน", + "warnNotAvailableCountriesTitle": "{app} ไม่พร้อมใช้งานในประเทศของคุณ", + "@warnNotAvailableCountriesTitle": { + "description": "A message with a single parameter", + "placeholders": { + "app": { + "type": "String", + "example": "Deriv GO" + } + } + }, + "actionOk": "OK", + "warnNotAvailableCountries": "หากคุณมีคำถามใดๆ โปรดติดต่อเราผ่านทาง ", + "labelLiveChat": "แชทสด", + "actionSignUpForFree": "ลงทะเบียนได้ฟรี", + "actionLogin": "เข้าสู่ระบบ", + "labelTwoFactorAuth": "ระบบยืนยันตัวตนสองขั้นตอน", + "informEnterTwoFactorAuthCode": "ป้อนรหัส 6 หลักจากแอปตรวจสอบสิทธิ์บนโทรศัพท์ของคุณ", + "labelTwoFactorAuthenticationCode": "รหัส 2FA", + "actionProceed": "ประมวลผล", + "labelLogIn": "เข้าสู่ระบบ", + "informLoginOptions": "หรือเข้าสู่ระบบด้วย", + "labelEmail": "อีเมล์", + "labelPassword": "รหัสผ่าน", + "actionForgotPassword": "คุณลืมรหัสผ่านหรือไม่", + "labelDontHaveAnAccountYet": "ยังไม่มีบัญชีใช่ไหม?", + "actionCreateANewAccount": "สร้างบัญชีใหม่", + "informInvalidEmailFormat": "ป้อนที่อยู่อีเมล์ที่ถูกต้อง", + "warnPasswordLength": "คุณควรป้อนอักขระ 6-25 ตัว", + "labelResetPassword": "ตั้งค่ารหัสผ่าน", + "labelChooseNewPass": "เลือกรหัสผ่านอันใหม่", + "labelCreatePass": "รหัสผ่าน", + "informYourPassHasBeenReset": "รหัสผ่านของคุณถูกเปลี่ยนแปลงแล้ว", + "informRedirectLogin": "คุณจะต้องเข้าสู่ระบบด้วยรหัสผ่านใหม่ของคุณ รอสักครู่ เรากำลังพาคุณไป", + "actionResetPass": "ตั้งรหัสผ่านของฉันใหม่", + "informInvalidPasswordFormat": "กรุณากรอกรูปแบบรหัสผ่านที่ถูกต้อง", + "labelCheckEmail": "ตรวจสอบอีเมล์ของคุณ", + "informSendResetPasswordEmail": "เราได้ส่งข้อความไปที่ {email} พร้อมลิงก์เพื่อรีเซ็ตรหัสผ่านของคุณ", + "@informSendResetPasswordEmail": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "informResetPassByEmail": "เราจะส่งอีเมล์ให้คุณเพื่อแนะนำเกี่ยวกับการตั้งรหัสผ่านใหม่ของคุณ", + "labelSelectCountry": "คุณอาศัยอยู่ที่ไหน?", + "labelChooseCountry": "เลือกประเทศ", + "warnCountryNotAvailable": "ขออภัย Deriv ไม่พร้อมให้บริการในประเทศของคุณ", + "actionNext": "ถัดไป", + "labelEmailIssueHeader": "หากคุณไม่ได้รับอีเมล์จากเราภายในไม่กี่นาที อาจมีบางสิ่งเกิดขึ้น:", + "labelEmailIssueSpam": "อีเมล์นั้นอยู่ในโฟลเดอร์จดหมายขยะของคุณ (บางทีก็มีการพลัดหลงไปที่นั่นได้)", + "labelEmailIssueWrongEmail": "คุณเผอิญให้ที่อยู่อีเมล์อื่นแก่เรา (ปกติมักจะเป็นอีเมล์จากที่ทำงานหรืออีเมล์ส่วนตัวแทนที่จะเป็นอันที่คุณต้องการใช้)", + "labelEmailIssueTypo": "ที่อยู่อีเมล์ที่คุณป้อนมีข้อผิดพลาดหรือคำพิมพ์ผิด (เกิดขึ้นได้กับทุกคน)", + "labelEmailIssueFirewall": "เราไม่สามารถส่งอีเมล์ไปยังที่อยู่นี้ (โดยปกติจะเป็นเพราะไฟร์วอลล์หรือการกรอง)", + "actionReenterEmail": "ป้อนที่อยู่อีเมล์ของคุณแล้วลองอีกครั้ง", + "labelKeepPassword": "รักษาบัญชีของคุณให้ปลอดภัยด้วยรหัสผ่าน", + "labelCreatePassword": "สร้างรหัสผ่าน", + "actionStartTrading": "เริ่มการเทรด", + "actionPrevious": "ก่อนหน้า", + "labelSignUp": "ลงทะเบียนสมัครใช้งาน", + "labelOrSignUpWith": "หรือลงทะเบียนสมัครไปกับ", + "labelReferralInfoTitle": "รหัสอ้างอิงพันธมิตร", + "infoReferralInfoDescription": "รหัสตัวอักษรและตัวเลขที่จัดทำโดยพันธมิตรของ Deriv ซึ่งใช้ได้สำหรับการลงทะเบียนสมัครทางอีเมล์เท่านั้น", + "labelGotReferralCode": "มีรหัสอ้างอิงหรือไม่?", + "labelHaveAccount": "มีบัญชีอยู่แล้วใช่ไหม?", + "actionCreateAccount": "สร้างบัญชีทดลองฟรี", + "informInvalidReferralCode": "รหัสอ้างอิงที่คุณป้อนไม่ถูกต้อง โปรดตรวจสอบและลองอีกครั้ง", + "labelVerifyYourEmail": "ยืนยันอีเมล์ของคุณ", + "labelThanksEmail": "ขอบคุณที่ยืนยันอีเมล์ของคุณ", + "informLetsContinue": "ดำเนินการต่อกันเถอะ", + "actionContinue": "ดำเนินการต่อ", + "labelSearchCountry": "ค้นหาประเทศ", + "informVerificationEmailSent": "เราได้ส่งข้อความไปที่ {email} พร้อมลิงก์เพื่อเปิดใช้งานบัญชีของคุณ", + "@informVerificationEmailSent": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "actionEmailNotReceived": "ไม่ได้รับอีเมล์ของคุณใช่หรือไม่?", + "informPasswordPolicy": "รหัสผ่านของคุณต้องมี:", + "informPasswordPolicyLength": "8-25 อักขระ", + "informPasswordPolicyLowerAndUpper": "ตัวอักษรตัวพิมพ์ใหญ่และตัวพิมพ์เล็ก", + "informPasswordPolicyNumber": "อย่างน้อยหนึ่งหมายเลข", + "warnPasswordContainsSymbol": "ใช้สัญลักษณ์เพื่อให้รหัสผ่านเดาได้ยาก", + "labelReferralCode": "รหัสอ้างอิง", + "actionTryAgain": "ลองอีกครั้ง", + "informInvalid2FACode": "รหัสที่คุณป้อนไม่ถูกต้อง โปรดตรวจสอบแล้วลองอีกครั้ง", + "informFailedAuthentication": "อีเมล์หรือรหัสผ่านของคุณอาจไม่ถูกต้อง คุณได้ลงทะเบียนด้วยบัญชีโซเชียลหรือไม่? โปรดตรวจสอบแล้วลองอีกครั้ง", + "informDeactivatedAccount": "บัญชีของคุณถูกปิดใช้งาน", + "informFailedAuthorization": "การอนุญาตให้สิทธิ์ล้มเหลว", + "informInvalidResidence": "ที่พักอาศัยไม่ถูกต้อง", + "informInvalidCredentials": "ข้อมูลประจำตัวไม่ถูกต้อง", + "informMissingOtp": "รหัสผ่านแบบครั้งเดียวขาดหายไป", + "informSelfClosed": "บัญชีของคุณได้ถูกปิดแล้ว", + "informUnexpectedError": "มีข้อผิดพลาดเกิดขึ้น", + "informUnsupportedCountry": "ไม่รองรับการใช้งานในประเทศของคุณ", + "informExpiredAccount": "บัญชีของคุณหมดอายุ", + "labelCountryConsentBrazil": "ข้าพเจ้าขอยืนยันว่าคำขอเปิดบัญชีกับ Deriv เพื่อทำการซื้อขายผลิตภัณฑ์ OTC ที่ออกและนำเสนอเฉพาะนอกประเทศบราซิลนั้นมาจากความคิดริเริ่มโดยข้าพเจ้าเอง ข้าพเจ้าเข้าใจดีว่า Deriv ไม่ได้ถูกควบคุมดูแลโดย CVM และในการที่ข้าพเจ้าเข้าหา Deriv นั้นข้าพเจ้ามีเจตนาที่จะสร้างความสัมพันธ์กับบริษัทจากต่างประเทศ", + "informConnectionError": "มีข้อผิดพลาดในการเชื่อมต่อ โปรดลองใหม่ในภายหลัง", + "informSwitchAccountError": "มีข้อผิดพลาดในการสลับบัญชี โปรดลองใหม่ในภายหลัง", + "labelDeveloper": "ผู้พัฒนาระบบ", + "labelEndpoint": "อุปกรณ์ปลายทาง", + "semanticEndpoint": "อุปกรณ์ปลายทาง", + "warnInvalidEndpoint": "อุปกรณ์ปลายทางไม่ถูกต้อง", + "labelApplicationID": "รหัสไอดีแอปพลิเคชั่น", + "semanticApplicationID": "รหัสไอดีแอปพลิเคชั่น", + "warnInvalidApplicationID": "รหัสไอดีแอปพลิเคชั่นไม่ถูกต้อง", + "labelLanguage": "ภาษา" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_auth/app_tr.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_tr.arb new file mode 100644 index 000000000..8073bff28 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_tr.arb @@ -0,0 +1,117 @@ +{ + "labelNotAvailable": "Mevcut değil", + "warnNotAvailableCountriesTitle": "{app} ülkenizde mevcut değil", + "@warnNotAvailableCountriesTitle": { + "description": "A message with a single parameter", + "placeholders": { + "app": { + "type": "String", + "example": "Deriv GO" + } + } + }, + "actionOk": "OK", + "warnNotAvailableCountries": "Herhangi bir sorunuz varsa, bizimle şu yolla iletişime geçin ", + "labelLiveChat": "Canlı sohbet", + "actionSignUpForFree": "Ücretsiz Üye Olun", + "actionLogin": "Giriş yap", + "labelTwoFactorAuth": "İki faktörlü kimlik doğrulama", + "informEnterTwoFactorAuthCode": "Telefonunuzdaki kimlik doğrulayıcı uygulamasından 6 haneli kodu girin.", + "labelTwoFactorAuthenticationCode": "2FA kodu", + "actionProceed": "Devam et", + "labelLogIn": "Oturum Aç", + "informLoginOptions": "Veya bununla oturum aç", + "labelEmail": "E-mail", + "labelPassword": "Şifre", + "actionForgotPassword": "Şifreyi mi unuttun?", + "labelDontHaveAnAccountYet": "Henüz bir hesabınız yok mu?", + "actionCreateANewAccount": "Yeni bir hesap oluştur", + "informInvalidEmailFormat": "Geçerli bir e-posta adresi girin", + "warnPasswordLength": "6-25 arası karakter girmelisiniz.", + "labelResetPassword": "Şifreyi sıfırla", + "labelChooseNewPass": "Yeni bir şifre seç", + "labelCreatePass": "Şifre", + "informYourPassHasBeenReset": "Şifreniz sıfırlandı", + "informRedirectLogin": "Yeni şifrenizle giriş yapmanız gerekecek. Bekle, seni yönlendiriyoruz.", + "actionResetPass": "Şifremi sıfırla", + "informInvalidPasswordFormat": "Lütfen geçerli bir şifre formatı girin", + "labelCheckEmail": "E-postanızı kontrol edin", + "informSendResetPasswordEmail": "{email} adresine şifrenizi sıfırlamanız için bir bağlantı içeren bir mesaj gönderdik.", + "@informSendResetPasswordEmail": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "informResetPassByEmail": "Şifrenizi sıfırlamak için size e-posta ile talimatlar göndereceğiz.", + "labelSelectCountry": "Nerede yaşıyorsunuz?", + "labelChooseCountry": "Ülke seçin", + "warnCountryNotAvailable": "Maalesef, Deriv ülkenizde mevcut değil.", + "actionNext": "Sonraki", + "labelEmailIssueHeader": "Birkaç dakika içinde bizden bir e-posta görmezseniz, birkaç şey olmuş olabilir:", + "labelEmailIssueSpam": "E-posta, spam klasörünüzdedir (bazen orada bir şeyler kaybolur).", + "labelEmailIssueWrongEmail": "Yanlışlıkla bize başka bir e-posta adresi verdiniz (Genellikle kastettiğiniz e-posta yerine bir iş veya kişisel bir e-posta).", + "labelEmailIssueTypo": "Girdiğiniz e-posta adresi bir hata veya yazım hatası içerdi (en iyilerimizin bile başına geliyor).", + "labelEmailIssueFirewall": "E-postayı bu adrese teslim edemiyoruz (Genellikle güvenlik duvarları veya filtreleme nedeniyle).", + "actionReenterEmail": "E-postanızı yine girin ve deneyin", + "labelKeepPassword": "Hesabınızı bir şifre ile güvende tutun", + "labelCreatePassword": "Bir şifre oluşturun", + "actionStartTrading": "Ticarete başlayın", + "actionPrevious": "Önceki", + "labelSignUp": "Kayıt ol", + "labelOrSignUpWith": "Veya bununla kaydolun", + "labelReferralInfoTitle": "Affiliate yönlendirme kodu", + "infoReferralInfoDescription": "Bir Deriv affiliate tarafından sağlanan ve yalnızca e-posta kayıtları için geçerli olan alfanümerik bir kod.", + "labelGotReferralCode": "Yönlendirme kodunuz var mı?", + "labelHaveAccount": "Zaten bir hesabınız var?", + "actionCreateAccount": "Ücretsiz demo hesabı oluştur", + "informInvalidReferralCode": "Girdiğiniz yönlendirme kodu geçersiz. Kontrol edin ve tekrar deneyin.", + "labelVerifyYourEmail": "E-postanızı doğrulayın", + "labelThanksEmail": "E-posta doğrulama için teşekkürler", + "informLetsContinue": "Devam edelim.", + "actionContinue": "Devam et", + "labelSearchCountry": "Ülke ara", + "informVerificationEmailSent": "Hesabınızı etkinleştirmek için {email} adresine bir bağlantı içeren bir mesaj gönderdik.", + "@informVerificationEmailSent": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "actionEmailNotReceived": "E-postanızı almadınız mı?", + "informPasswordPolicy": "Şifreniz şunlara sahip olmalıdır:", + "informPasswordPolicyLength": "8-25 karakter", + "informPasswordPolicyLowerAndUpper": "Büyük ve küçük harfler", + "informPasswordPolicyNumber": "En az bir sayı", + "warnPasswordContainsSymbol": "Güçlü şifre için semboller kullanın.", + "labelReferralCode": "Yönlendirme Kodu", + "actionTryAgain": "Tekrar Deneyin", + "informInvalid2FACode": "Girdiğiniz kod geçersiz. Kontrol edin ve tekrar deneyin.", + "informFailedAuthentication": "E-posta adresiniz veya şifreniz yanlış olabilir. Sosyal bir hesapla mı kaydoldunuz? Kontrol edin ve tekrar deneyin.", + "informDeactivatedAccount": "Hesabınız devre dışı bırakıldı.", + "informFailedAuthorization": "Yetkilendirme başarısız oldu.", + "informInvalidResidence": "Geçersiz ikamet.", + "informInvalidCredentials": "Geçersiz kimlik bilgileri.", + "informMissingOtp": "Tek seferlik şifre eksik.", + "informSelfClosed": "Hesabınız kapatıldı.", + "informUnexpectedError": "Beklenmedik bir hata oluştu.", + "informUnsupportedCountry": "Ülkeniz desteklenmiyor.", + "informExpiredAccount": "Hesabınızın süresi doldu", + "labelCountryConsentBrazil": "Sadece Brezilya dışında yayınlanan ve sunulan OTC ürünlerinin ticaretini yapmak için Deriv'de bir hesap açma talebimin tarafımdan başlatıldığını onaylıyorum. Deriv'in CVM tarafından düzenlenmediğini ve Deriv'e başvurarak yabancı bir şirketle ilişki kurmayı amaçladığımı tamamen anlıyorum.", + "informConnectionError": "Bağlantı hatası. Lütfen daha sonra tekrar deneyin.", + "informSwitchAccountError": "Hesap hatası değiştir. Lütfen daha sonra tekrar deneyin.", + "labelDeveloper": "Geliştirici", + "labelEndpoint": "Bitiş noktası", + "semanticEndpoint": "Bitiş noktası", + "warnInvalidEndpoint": "geçersiz bitiş noktası", + "labelApplicationID": "Başvuru ID", + "semanticApplicationID": "Başvuru ID", + "warnInvalidApplicationID": "geçersiz başvuru ID", + "labelLanguage": "Dil" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_auth/app_uz.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_uz.arb new file mode 100644 index 000000000..8ac9be2bf --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_uz.arb @@ -0,0 +1,117 @@ +{ + "labelNotAvailable": "Not available", + "warnNotAvailableCountriesTitle": "{app} isn't available in your country", + "@warnNotAvailableCountriesTitle": { + "description": "A message with a single parameter", + "placeholders": { + "app": { + "type": "String", + "example": "Deriv GO" + } + } + }, + "actionOk": "OK", + "warnNotAvailableCountries": "If you have any questions, contact us via ", + "labelLiveChat": "Live chat", + "actionSignUpForFree": "Sign up for free", + "actionLogin": "Log in", + "labelTwoFactorAuth": "Two-factor authentication", + "informEnterTwoFactorAuthCode": "Enter the 6-digit code from the authenticator app on your phone.", + "labelTwoFactorAuthenticationCode": "2FA code", + "actionProceed": "Proceed", + "labelLogIn": "Log in", + "informLoginOptions": "Or log in with", + "labelEmail": "Email", + "labelPassword": "Password", + "actionForgotPassword": "Forgot password?", + "labelDontHaveAnAccountYet": "Don’t have an account yet?", + "actionCreateANewAccount": "Create a new account", + "informInvalidEmailFormat": "Enter a valid email address", + "warnPasswordLength": "You should enter 6-25 characters.", + "labelResetPassword": "Reset password", + "labelChooseNewPass": "Choose a new password", + "labelCreatePass": "Password", + "informYourPassHasBeenReset": "Your password has been reset", + "informRedirectLogin": "You’ll need to log in with your new password. Hang on, we’re redirecting you.", + "actionResetPass": "Reset my password", + "informInvalidPasswordFormat": "Please enter a valid password format", + "labelCheckEmail": "Check your email", + "informSendResetPasswordEmail": "We’ve sent a message to {email} with a link to reset your password.", + "@informSendResetPasswordEmail": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "informResetPassByEmail": "We'll email you instructions to reset your password.", + "labelSelectCountry": "Where do you live?", + "labelChooseCountry": "Choose country", + "warnCountryNotAvailable": "Unfortunately, Deriv is not available in your country.", + "actionNext": "Next", + "labelEmailIssueHeader": "If you don't see an email from us within a few minutes, a few things could have happened:", + "labelEmailIssueSpam": "The email is in your spam folder (Sometimes things get lost there).", + "labelEmailIssueWrongEmail": "You accidentally gave us another email address (Usually a work or a personal one instead of the one you meant).", + "labelEmailIssueTypo": "The email address you entered had a mistake or typo (happens to the best of us).", + "labelEmailIssueFirewall": "We can't deliver the email to this address (Usually because of firewalls or filtering).", + "actionReenterEmail": "Re-enter your email and try again", + "labelKeepPassword": "Keep your account secure with a password", + "labelCreatePassword": "Create a password", + "actionStartTrading": "Start trading", + "actionPrevious": "Previous", + "labelSignUp": "Sign up", + "labelOrSignUpWith": "Or sign up with", + "labelReferralInfoTitle": "Affiliate referral code", + "infoReferralInfoDescription": "An alphanumeric code provided by a Deriv affiliate, applicable for email sign-ups only.", + "labelGotReferralCode": "Got a referral code?", + "labelHaveAccount": "Already have an account?", + "actionCreateAccount": "Create free demo account", + "informInvalidReferralCode": "The referral code you entered is invalid. Check and try again.", + "labelVerifyYourEmail": "Verify your email", + "labelThanksEmail": "Thanks for verifying your email", + "informLetsContinue": "Let's continue.", + "actionContinue": "Continue", + "labelSearchCountry": "Search country", + "informVerificationEmailSent": "We've sent a message to {email} with a link to activate your account.", + "@informVerificationEmailSent": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "actionEmailNotReceived": "Didn't receive your email?", + "informPasswordPolicy": "Your password must have:", + "informPasswordPolicyLength": "8-25 characters", + "informPasswordPolicyLowerAndUpper": "Upper and lower case letters", + "informPasswordPolicyNumber": "At least one number", + "warnPasswordContainsSymbol": "Use symbols for strong password.", + "labelReferralCode": "Referral Code", + "actionTryAgain": "Try Again", + "informInvalid2FACode": "The code you entered is invalid. Check and try again.", + "informFailedAuthentication": "Your email or password may be incorrect. Did you sign up with a social account? Check and try again.", + "informDeactivatedAccount": "Your account is deactivated.", + "informFailedAuthorization": "Authorization failed.", + "informInvalidResidence": "Invalid residence.", + "informInvalidCredentials": "Invalid credentials.", + "informMissingOtp": "Missing one-time password.", + "informSelfClosed": "Your account has been closed.", + "informUnexpectedError": "An unexpected error occurred.", + "informUnsupportedCountry": "Your country is not supported.", + "informExpiredAccount": "Your account is expired", + "labelCountryConsentBrazil": "I hereby confirm that my request for opening an account with Deriv to trade OTC products issued and offered exclusively outside Brazil was initiated by me. I fully understand that Deriv is not regulated by CVM and by approaching Deriv I intend to set up a relation with a foreign company.", + "informConnectionError": "Connection error. Please try again later.", + "informSwitchAccountError": "Switch account error. Please try again later.", + "labelDeveloper": "Developer", + "labelEndpoint": "Endpoint", + "semanticEndpoint": "Endpoint", + "warnInvalidEndpoint": "invalid endpoint", + "labelApplicationID": "Application ID", + "semanticApplicationID": "Application ID", + "warnInvalidApplicationID": "invalid application ID", + "labelLanguage": "Language" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_auth/app_vi.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_vi.arb new file mode 100644 index 000000000..a30d79678 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_vi.arb @@ -0,0 +1,117 @@ +{ + "labelNotAvailable": "Không tồn tại", + "warnNotAvailableCountriesTitle": "{app} không có sẵn ở quốc gia của bạn", + "@warnNotAvailableCountriesTitle": { + "description": "A message with a single parameter", + "placeholders": { + "app": { + "type": "String", + "example": "Deriv GO" + } + } + }, + "actionOk": "Ok", + "warnNotAvailableCountries": "Nếu bạn có bất kỳ câu hỏi nào, hãy liên hệ với chúng tôi qua ", + "labelLiveChat": "Live chat", + "actionSignUpForFree": "Đăng ký miễn phí", + "actionLogin": "Đăng nhập", + "labelTwoFactorAuth": "Xác thực hai yếu tố", + "informEnterTwoFactorAuthCode": "Nhập mã gồm 6 chữ số từ ứng dụng xác thực trên điện thoại của bạn.", + "labelTwoFactorAuthenticationCode": "mã 2FA", + "actionProceed": "Xử lý", + "labelLogIn": "Đăng nhập", + "informLoginOptions": "Hoặc đăng nhập với", + "labelEmail": "Email", + "labelPassword": "Mật khẩu", + "actionForgotPassword": "Quên mật khẩu?", + "labelDontHaveAnAccountYet": "Chưa có tài khoản?", + "actionCreateANewAccount": "Tạo một tài khoản mới", + "informInvalidEmailFormat": "Nhập địa chỉ email hợp lệ", + "warnPasswordLength": "Bạn nên nhập từ 8-25 ký tự.", + "labelResetPassword": "Đặt lại mật khẩu", + "labelChooseNewPass": "Chọn mật khẩu mới", + "labelCreatePass": "Mật khẩu", + "informYourPassHasBeenReset": "Mật khẩu của bạn đã được đặt lại", + "informRedirectLogin": "Bạn sẽ cần đăng nhập bằng mật khẩu mới của mình. Chờ đã, chúng tôi đang chuyển hướng bạn.", + "actionResetPass": "Đặt lại mật khẩu", + "informInvalidPasswordFormat": "Vui lòng nhập một định dạng mật khẩu hợp lệ", + "labelCheckEmail": "Hãy kiểm tra email của bạn", + "informSendResetPasswordEmail": "Chúng tôi đã gửi tin nhắn {email} với một liên kết để đặt lại mật khẩu của bạn.", + "@informSendResetPasswordEmail": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "informResetPassByEmail": "Chúng tôi sẽ gửi email hướng dẫn đổi mật khẩu cho bạn.", + "labelSelectCountry": "Bạn sống ở đâu?", + "labelChooseCountry": "Chọn quốc gia", + "warnCountryNotAvailable": "Rất tiếc, Deriv không khả dụng ở quốc gia của bạn.", + "actionNext": "Tiếp theo", + "labelEmailIssueHeader": "Nếu sau vài phút bạn không nhận được email từ chúng tôi, một vài điều sau có thể đã xảy ra:", + "labelEmailIssueSpam": "Email có thể đã bị chuyển vào hòm thư spam (Đôi lúc dữ liệu bị lạc mất trong đó).", + "labelEmailIssueWrongEmail": "Bạn đã vô tình đưa cho chúng tôi một tài khoản email khác (Thường là một tài khoản cho công việc hoặc cá nhân thay vì tài khoản mà bạn định sử dụng).", + "labelEmailIssueTypo": "Địa chỉ email bạn nhập bị nhầm hoặc có lỗi chính tả (hay xảy ra với chúng tôi).", + "labelEmailIssueFirewall": "Chúng tôi không thể chuyển email đến địa chỉ này (Thường là do tường lửa hoặc bộ lọc).", + "actionReenterEmail": "Nhập lại email của bạn và thử lại", + "labelKeepPassword": "Bảo vệ tài khoản của bạn an toàn với mật khẩu", + "labelCreatePassword": "Tạo một mật khẩu", + "actionStartTrading": "Bắt đầu giao dịch", + "actionPrevious": "Trước", + "labelSignUp": "Đăng kí", + "labelOrSignUpWith": "Hoặc đang ký với", + "labelReferralInfoTitle": "Mã giới thiệu Affiliate", + "infoReferralInfoDescription": "Mã chữ và số được cung cấp bởi một đơn vị liên kết Deriv, chỉ áp dụng cho đăng ký qua email.", + "labelGotReferralCode": "Có mã giới thiệu?", + "labelHaveAccount": "Đã có tài khoản?", + "actionCreateAccount": "Tạo tài khoản demo miễn phí", + "informInvalidReferralCode": "Mã giới thiệu bạn đã nhập không hợp lệ. Kiểm tra và thử lại.", + "labelVerifyYourEmail": "Xác minh email của bạn", + "labelThanksEmail": "Cảm ơn đã xác thực email của bạn", + "informLetsContinue": "Hãy tiếp tục.", + "actionContinue": "Tiếp tục", + "labelSearchCountry": "Tìm kiếm quốc gia", + "informVerificationEmailSent": "Chúng tôi đã gửi tin nhắn {email} với một liên kết để kích hoạt tài khoản của bạn.", + "@informVerificationEmailSent": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "actionEmailNotReceived": "Không nhận được email bạn gửi?", + "informPasswordPolicy": "Mật khẩu của bạn phải có:", + "informPasswordPolicyLength": "8-25 ký tự", + "informPasswordPolicyLowerAndUpper": "Chữ hoa và chữ thường", + "informPasswordPolicyNumber": "Ít nhất một số", + "warnPasswordContainsSymbol": "Sử dụng các ký hiệu cho mật khẩu mạnh.", + "labelReferralCode": "Mã giới thiệu", + "actionTryAgain": "Thử lại", + "informInvalid2FACode": "Mã bạn nhập không hợp lệ. Kiểm tra và thử lại.", + "informFailedAuthentication": "Email hoặc mật khẩu của bạn có thể không chính xác. Bạn đã đăng ký với một tài khoản xã hội chưa? Kiểm tra và thử lại.", + "informDeactivatedAccount": "Tài khoản của bạn bị vô hiệu hóa.", + "informFailedAuthorization": "Ủy quyền không thành công.", + "informInvalidResidence": "Nơi cư trú không hợp lệ.", + "informInvalidCredentials": "Thông tin đăng nhập không hợp lệ.", + "informMissingOtp": "Thiếu mật khẩu một lần.", + "informSelfClosed": "Tài khoản này của bạn đã bị vô hiệu hóa.", + "informUnexpectedError": "Đã có lỗi bất ngờ xảy ra.", + "informUnsupportedCountry": "Đất nước của bạn không được hỗ trợ.", + "informExpiredAccount": "Tài khoản của bạn đã hết hạn", + "labelCountryConsentBrazil": "Tôi xác nhận rằng yêu cầu mở tài khoản của tôi với Deriv để giao dịch các sản phẩm OTC được phát hành và cung cấp độc quyền bên ngoài Brazil là do tôi thực hiện. Tôi hoàn toàn hiểu rằng Deriv không bị quản lý bởi CVM và thông qua Deriv, tôi dự định thiết lập quan hệ với một công ty nước ngoài.", + "informConnectionError": "Lỗi kết nối. Vui lòng thử lại sau.", + "informSwitchAccountError": "Chuyển đổi lỗi tài khoản. Vui lòng thử lại sau.", + "labelDeveloper": "Nhà phát triển", + "labelEndpoint": "Điểm kết thúc", + "semanticEndpoint": "Điểm kết thúc", + "warnInvalidEndpoint": "điểm kết thúc không hợp lệ", + "labelApplicationID": "Mã đăng ký", + "semanticApplicationID": "Mã đăng ký", + "warnInvalidApplicationID": "mã đăng ký không hợp lệ", + "labelLanguage": "Ngôn ngữ" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_auth/app_zh-CN.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_zh-CN.arb new file mode 100644 index 000000000..95dbac3a6 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_zh-CN.arb @@ -0,0 +1,117 @@ +{ + "labelNotAvailable": "不可用", + "warnNotAvailableCountriesTitle": "您的国家不能使用 {app}", + "@warnNotAvailableCountriesTitle": { + "description": "A message with a single parameter", + "placeholders": { + "app": { + "type": "String", + "example": "Deriv GO" + } + } + }, + "actionOk": "确定", + "warnNotAvailableCountries": "如有任何疑问,请通过以下方式联系我们 ", + "labelLiveChat": "实时聊天", + "actionSignUpForFree": "免费注册", + "actionLogin": "登录", + "labelTwoFactorAuth": "双因素身份验证", + "informEnterTwoFactorAuthCode": "在手机输入身份验证器应用程序的 6 位数代码。", + "labelTwoFactorAuthenticationCode": "2FA 代码", + "actionProceed": "执行", + "labelLogIn": "登录", + "informLoginOptions": "或通过以下登录", + "labelEmail": "电子邮件", + "labelPassword": "密码", + "actionForgotPassword": "忘记密码?", + "labelDontHaveAnAccountYet": "还没有账户?", + "actionCreateANewAccount": "开立新账户", + "informInvalidEmailFormat": "输入有效的电子邮件地址", + "warnPasswordLength": "必须输入 6-25 个字符。", + "labelResetPassword": "重置密码", + "labelChooseNewPass": "选新密码", + "labelCreatePass": "密码", + "informYourPassHasBeenReset": "密码已重置", + "informRedirectLogin": "需要使用新密码登录。请稍等,正在重定向。", + "actionResetPass": "重置密码", + "informInvalidPasswordFormat": "请输入有效的密码格式", + "labelCheckEmail": "查看电子邮件", + "informSendResetPasswordEmail": "已发送邮件至 {email},内含重置密码的链接。", + "@informSendResetPasswordEmail": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "informResetPassByEmail": "会传送内含重设密码的说明的电子邮件。", + "labelSelectCountry": "您的居住地是哪里?", + "labelChooseCountry": "选择国家", + "warnCountryNotAvailable": "对不起,您的所在国不可用 Deriv。", + "actionNext": "下一步", + "labelEmailIssueHeader": "如果数分钟内没有收到我们发送的电子邮件,可能是因为发生了几种情况而引起的:", + "labelEmailIssueSpam": "邮件在垃圾邮箱里(有时一些邮件会误送到那儿)。", + "labelEmailIssueWrongEmail": "您不小心给了我们另一个电子邮件地址(通常非您本意,而是属于工作或个人性质的)。", + "labelEmailIssueTypo": "您输入的电子邮件地址拼写有误(有时这是难免的)。", + "labelEmailIssueFirewall": "无法发送电子邮件到此地址(通常是因为安装了防火墙或筛选器)。", + "actionReenterEmail": "重新输入电子邮件并重试", + "labelKeepPassword": "用密码保障账户安全", + "labelCreatePassword": "创建密码", + "actionStartTrading": "开始交易", + "actionPrevious": "之前", + "labelSignUp": "注册", + "labelOrSignUpWith": "或通过以下注册", + "labelReferralInfoTitle": "联盟推荐码", + "infoReferralInfoDescription": "Deriv 联盟会员提供的字母数字代码,仅用于电子邮件注册。", + "labelGotReferralCode": "有推荐码吗?", + "labelHaveAccount": "已经有账户?", + "actionCreateAccount": "开立免费演示账户", + "informInvalidReferralCode": "输入的推荐码无效。请检查并重试。", + "labelVerifyYourEmail": "电子邮件验证", + "labelThanksEmail": "谢谢您验证了电子邮件", + "informLetsContinue": "让我们继续。", + "actionContinue": "继续", + "labelSearchCountry": "搜索国家", + "informVerificationEmailSent": "已发送邮件至 {email},内含激活账户的链接。", + "@informVerificationEmailSent": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "actionEmailNotReceived": "没收到邮件?", + "informPasswordPolicy": "密码必须有:", + "informPasswordPolicyLength": "8-25 个字符", + "informPasswordPolicyLowerAndUpper": "大写和小写字母", + "informPasswordPolicyNumber": "至少一个数字", + "warnPasswordContainsSymbol": "使用符号加强密码安全。", + "labelReferralCode": "推荐码", + "actionTryAgain": "重试", + "informInvalid2FACode": "输入的代码无效。请检查并重试。", + "informFailedAuthentication": "电子邮件或密码可能不正确。是否用社交账户注册?请检查并重试。", + "informDeactivatedAccount": "账户已停用。", + "informFailedAuthorization": "授权失败。", + "informInvalidResidence": "无效的居住地。", + "informInvalidCredentials": "凭证无效。", + "informMissingOtp": "缺少一次性密码。", + "informSelfClosed": "账户已关闭.", + "informUnexpectedError": "发生不可预测的错误.", + "informUnsupportedCountry": "您的国家不受支持。", + "informExpiredAccount": "账户已过期", + "labelCountryConsentBrazil": "本人特此确认,在 Deriv 开立账户以交易由巴西境外推出和提供的场外交易产品的请求是由本人提出的。本人完全理解 Deriv 不受 CVM 监管,且通过与 Deriv 接触,本人打算与一家外国公司建立关系。", + "informConnectionError": "连接错误。请稍后再试。", + "informSwitchAccountError": "切换账户错误。请稍后再试。", + "labelDeveloper": "开发员", + "labelEndpoint": "终结点", + "semanticEndpoint": "终结点", + "warnInvalidEndpoint": "无效的终结点", + "labelApplicationID": "应用程序 ID", + "semanticApplicationID": "应用程序 ID", + "warnInvalidApplicationID": "无效的应用程序 ID", + "labelLanguage": "语言" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_auth/app_zh-TW.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_zh-TW.arb new file mode 100644 index 000000000..4c589dabe --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_zh-TW.arb @@ -0,0 +1,117 @@ +{ + "labelNotAvailable": "不可用", + "warnNotAvailableCountriesTitle": "您的國家/地區不能使用 {app}", + "@warnNotAvailableCountriesTitle": { + "description": "A message with a single parameter", + "placeholders": { + "app": { + "type": "String", + "example": "Deriv GO" + } + } + }, + "actionOk": "确定", + "warnNotAvailableCountries": "如果有任何疑問,請通過以下方式與我們聯繫 ", + "labelLiveChat": "即時聊天", + "actionSignUpForFree": "免費註冊", + "actionLogin": "登入", + "labelTwoFactorAuth": "雙因素身份驗證", + "informEnterTwoFactorAuthCode": "在手機的驗證器應用程式中輸入 6 位數代碼。", + "labelTwoFactorAuthenticationCode": "2FA 代碼", + "actionProceed": "執行", + "labelLogIn": "登入", + "informLoginOptions": "或通過以下方式登入", + "labelEmail": "電子郵件", + "labelPassword": "密碼", + "actionForgotPassword": "忘記密碼?", + "labelDontHaveAnAccountYet": "還沒有帳戶?", + "actionCreateANewAccount": "開立新帳戶", + "informInvalidEmailFormat": "請輸入有效的電子郵件地址", + "warnPasswordLength": "必須輸入 6-25 個字元。", + "labelResetPassword": "重設密碼", + "labelChooseNewPass": "選新密碼", + "labelCreatePass": "密碼", + "informYourPassHasBeenReset": "密碼已重設", + "informRedirectLogin": "需要使用新密碼登入。等等,正在重導向。", + "actionResetPass": "重設密碼", + "informInvalidPasswordFormat": "請輸入有效的密碼格式", + "labelCheckEmail": "查看電子郵件", + "informSendResetPasswordEmail": "已傳送郵件至{email} ,內含重設密碼的連結。", + "@informSendResetPasswordEmail": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "informResetPassByEmail": "將以電子郵件傳送重設密碼的說明。", + "labelSelectCountry": "您的居住地是哪里?", + "labelChooseCountry": "選擇國家", + "warnCountryNotAvailable": "對不起,您的所在國不可用 Deriv。", + "actionNext": "下一頁", + "labelEmailIssueHeader": "如果數分鐘內沒有收到我們傳送的電子郵件,可能是因為發生了幾種情況而引起的:", + "labelEmailIssueSpam": "郵件在您的垃圾郵箱(有時一些郵件會誤送到那兒)。", + "labelEmailIssueWrongEmail": "您不小心給了我們另一個電子郵件地址(通常非您本意,而是屬於工作或個人性質的)。", + "labelEmailIssueTypo": "您輸入的電子郵件地址拼寫有誤(有時這是難免的)。", + "labelEmailIssueFirewall": "電子郵件無法傳送到此地址(通常是因為安裝了防火牆或篩選器)。", + "actionReenterEmail": "重新輸入電子郵件並重試", + "labelKeepPassword": "用密碼保障帳戶安全", + "labelCreatePassword": "建立密碼", + "actionStartTrading": "開始交易", + "actionPrevious": "上一頁", + "labelSignUp": "註冊", + "labelOrSignUpWith": "或使用以下方式註冊", + "labelReferralInfoTitle": "聯盟推薦代碼", + "infoReferralInfoDescription": "由 Deriv 聯盟會員提供的字母數字代碼,僅適用於電子郵件註冊。", + "labelGotReferralCode": "有推薦代碼嗎?", + "labelHaveAccount": "已經有帳戶?", + "actionCreateAccount": "開立免費示範帳戶", + "informInvalidReferralCode": "輸入的推薦代碼無效。檢查並再試一次。", + "labelVerifyYourEmail": "電子郵件驗證", + "labelThanksEmail": "謝謝您驗證了電子郵件", + "informLetsContinue": "讓我們繼續。", + "actionContinue": "繼續", + "labelSearchCountry": "搜尋國家", + "informVerificationEmailSent": "已傳送郵件至{email} ,內含激活帳戶的連結。", + "@informVerificationEmailSent": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "actionEmailNotReceived": "沒收到郵件?", + "informPasswordPolicy": "密碼必須具有:", + "informPasswordPolicyLength": "8-25 個字元", + "informPasswordPolicyLowerAndUpper": "大小寫英文字母", + "informPasswordPolicyNumber": "至少一個數字", + "warnPasswordContainsSymbol": "使用符號加強密碼。", + "labelReferralCode": "推薦代碼", + "actionTryAgain": "重試", + "informInvalid2FACode": "輸入的推薦代碼無效。檢查並再試一次。", + "informFailedAuthentication": "電子郵件地址或密碼不正確。是否用社交帳戶註冊?檢查並再試一次。", + "informDeactivatedAccount": "帳戶已停用。", + "informFailedAuthorization": "授權失敗。", + "informInvalidResidence": "無效的居住地。", + "informInvalidCredentials": "無效的憑證.", + "informMissingOtp": "缺少一次性密碼。", + "informSelfClosed": "帳戶已關閉.", + "informUnexpectedError": "發生不可預測錯誤.", + "informUnsupportedCountry": "您的國家不受支援。", + "informExpiredAccount": "帳戶已過期", + "labelCountryConsentBrazil": "本人特此確認,在 Deriv 開立帳戶以交易由巴西境外推出和提供的場外交易產品的要求是由本人提出的。本人完全理解 Deriv 不受 CVM 監管,且通過與 Deriv 接觸,本人打算與一家外國公司建立關係。", + "informConnectionError": "連線錯誤。請稍後再試一次。", + "informSwitchAccountError": "切換帳戶錯誤。請稍後再試一次。", + "labelDeveloper": "開發員", + "labelEndpoint": "端點", + "semanticEndpoint": "端點", + "warnInvalidEndpoint": "無效的端點", + "labelApplicationID": "應用程式 ID", + "semanticApplicationID": "應用程式 ID", + "warnInvalidApplicationID": "無效的應用程式 ID", + "labelLanguage": "語言" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_auth/app_zh.arb b/packages/deriv_localizations/lib/l10n/deriv_auth/app_zh.arb new file mode 100644 index 000000000..4c589dabe --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_auth/app_zh.arb @@ -0,0 +1,117 @@ +{ + "labelNotAvailable": "不可用", + "warnNotAvailableCountriesTitle": "您的國家/地區不能使用 {app}", + "@warnNotAvailableCountriesTitle": { + "description": "A message with a single parameter", + "placeholders": { + "app": { + "type": "String", + "example": "Deriv GO" + } + } + }, + "actionOk": "确定", + "warnNotAvailableCountries": "如果有任何疑問,請通過以下方式與我們聯繫 ", + "labelLiveChat": "即時聊天", + "actionSignUpForFree": "免費註冊", + "actionLogin": "登入", + "labelTwoFactorAuth": "雙因素身份驗證", + "informEnterTwoFactorAuthCode": "在手機的驗證器應用程式中輸入 6 位數代碼。", + "labelTwoFactorAuthenticationCode": "2FA 代碼", + "actionProceed": "執行", + "labelLogIn": "登入", + "informLoginOptions": "或通過以下方式登入", + "labelEmail": "電子郵件", + "labelPassword": "密碼", + "actionForgotPassword": "忘記密碼?", + "labelDontHaveAnAccountYet": "還沒有帳戶?", + "actionCreateANewAccount": "開立新帳戶", + "informInvalidEmailFormat": "請輸入有效的電子郵件地址", + "warnPasswordLength": "必須輸入 6-25 個字元。", + "labelResetPassword": "重設密碼", + "labelChooseNewPass": "選新密碼", + "labelCreatePass": "密碼", + "informYourPassHasBeenReset": "密碼已重設", + "informRedirectLogin": "需要使用新密碼登入。等等,正在重導向。", + "actionResetPass": "重設密碼", + "informInvalidPasswordFormat": "請輸入有效的密碼格式", + "labelCheckEmail": "查看電子郵件", + "informSendResetPasswordEmail": "已傳送郵件至{email} ,內含重設密碼的連結。", + "@informSendResetPasswordEmail": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "informResetPassByEmail": "將以電子郵件傳送重設密碼的說明。", + "labelSelectCountry": "您的居住地是哪里?", + "labelChooseCountry": "選擇國家", + "warnCountryNotAvailable": "對不起,您的所在國不可用 Deriv。", + "actionNext": "下一頁", + "labelEmailIssueHeader": "如果數分鐘內沒有收到我們傳送的電子郵件,可能是因為發生了幾種情況而引起的:", + "labelEmailIssueSpam": "郵件在您的垃圾郵箱(有時一些郵件會誤送到那兒)。", + "labelEmailIssueWrongEmail": "您不小心給了我們另一個電子郵件地址(通常非您本意,而是屬於工作或個人性質的)。", + "labelEmailIssueTypo": "您輸入的電子郵件地址拼寫有誤(有時這是難免的)。", + "labelEmailIssueFirewall": "電子郵件無法傳送到此地址(通常是因為安裝了防火牆或篩選器)。", + "actionReenterEmail": "重新輸入電子郵件並重試", + "labelKeepPassword": "用密碼保障帳戶安全", + "labelCreatePassword": "建立密碼", + "actionStartTrading": "開始交易", + "actionPrevious": "上一頁", + "labelSignUp": "註冊", + "labelOrSignUpWith": "或使用以下方式註冊", + "labelReferralInfoTitle": "聯盟推薦代碼", + "infoReferralInfoDescription": "由 Deriv 聯盟會員提供的字母數字代碼,僅適用於電子郵件註冊。", + "labelGotReferralCode": "有推薦代碼嗎?", + "labelHaveAccount": "已經有帳戶?", + "actionCreateAccount": "開立免費示範帳戶", + "informInvalidReferralCode": "輸入的推薦代碼無效。檢查並再試一次。", + "labelVerifyYourEmail": "電子郵件驗證", + "labelThanksEmail": "謝謝您驗證了電子郵件", + "informLetsContinue": "讓我們繼續。", + "actionContinue": "繼續", + "labelSearchCountry": "搜尋國家", + "informVerificationEmailSent": "已傳送郵件至{email} ,內含激活帳戶的連結。", + "@informVerificationEmailSent": { + "description": "A message with a single parameter", + "placeholders": { + "email": { + "type": "String", + "example": "a@a.a" + } + } + }, + "actionEmailNotReceived": "沒收到郵件?", + "informPasswordPolicy": "密碼必須具有:", + "informPasswordPolicyLength": "8-25 個字元", + "informPasswordPolicyLowerAndUpper": "大小寫英文字母", + "informPasswordPolicyNumber": "至少一個數字", + "warnPasswordContainsSymbol": "使用符號加強密碼。", + "labelReferralCode": "推薦代碼", + "actionTryAgain": "重試", + "informInvalid2FACode": "輸入的推薦代碼無效。檢查並再試一次。", + "informFailedAuthentication": "電子郵件地址或密碼不正確。是否用社交帳戶註冊?檢查並再試一次。", + "informDeactivatedAccount": "帳戶已停用。", + "informFailedAuthorization": "授權失敗。", + "informInvalidResidence": "無效的居住地。", + "informInvalidCredentials": "無效的憑證.", + "informMissingOtp": "缺少一次性密碼。", + "informSelfClosed": "帳戶已關閉.", + "informUnexpectedError": "發生不可預測錯誤.", + "informUnsupportedCountry": "您的國家不受支援。", + "informExpiredAccount": "帳戶已過期", + "labelCountryConsentBrazil": "本人特此確認,在 Deriv 開立帳戶以交易由巴西境外推出和提供的場外交易產品的要求是由本人提出的。本人完全理解 Deriv 不受 CVM 監管,且通過與 Deriv 接觸,本人打算與一家外國公司建立關係。", + "informConnectionError": "連線錯誤。請稍後再試一次。", + "informSwitchAccountError": "切換帳戶錯誤。請稍後再試一次。", + "labelDeveloper": "開發員", + "labelEndpoint": "端點", + "semanticEndpoint": "端點", + "warnInvalidEndpoint": "無效的端點", + "labelApplicationID": "應用程式 ID", + "semanticApplicationID": "應用程式 ID", + "warnInvalidApplicationID": "無效的應用程式 ID", + "labelLanguage": "語言" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_ar.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_ar.arb new file mode 100644 index 000000000..afa983792 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_ar.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "المؤشرات", + "labelActive": "نشط", + "labelAll": "الكل", + "labelMomentum": "الزخم", + "labelVolatility": "التقلبات", + "labelMovingAverages": "المتوسطات المتحركة", + "labelMACD": "MACD", + "labelRelativeStrengthIndex": "مؤشر القوة النسبية (RSI)", + "labelRSI": "مؤشر القوة النسبية", + "labelBollingerBands": "البولنجر باند (BB)", + "labelBB": "ب ب", + "labelMovingAverage": "المتوسط المتحرك (MA)", + "labelMA": "ماجستير", + "infoMACD": "مؤشر MACD هو مؤشر تداول يستخدم في التحليل الفني لأسعار الأسهم. ومن المفترض أن يكشف عن التغيرات في قوة واتجاه وزخم ومدة الاتجاه في سعر السهم.", + "infoRSI": "تم نشر مؤشر القوة النسبية (RSI) بواسطة J. Welles Wilder. يتم تطبيع السعر الحالي كنسبة مئوية بين 0 و 100. إن معرف flutter_chart_id لهذا المذبذب مضلل لأنه لا يقارن الأداة بالنسبة إلى أداة أخرى أو مجموعة من الأدوات، بل يمثل السعر الحالي بالنسبة إلى القطع الحديثة الأخرى ضمن طول نافذة الاسترجاع المحددة.", + "infoBB": "يمكن استخدام مؤشر بولينجر باند (BB) لقياس مدى ارتفاع أو انخفاض السعر بالنسبة للصفقات السابقة.", + "infoMA": "يساعد المتوسط المتحرك (MA) على تحديد الاتجاه العام للسوق من خلال تصفية تقلبات الأسعار على المدى القصير. وباستخدام البيانات التاريخية، يحسب المتوسط المتحرك متوسط السعر خلال فترة محددة ويرسم خطًا على الرسم البياني. إذا تحرك خط المتوسط المتحرك المتحرك لأعلى، فهذا مؤشر على وجود اتجاه صعودي، ويكون مؤشرًا على اتجاه هبوطي إذا تحرك لأسفل. وتحدث إشارة الشراء عندما يتحرك السعر فوق خط المتوسط المتحرك.", + "infoMaximumActiveIndicatorsAdded": "لقد أضفت الحد الأقصى لعدد المؤشرات النشطة.", + "infoAddSelectedIndicator": "إضافة {indicator}", + "infoAddIndicator": "إضافة مؤشر", + "labelDeleteAllIndicators": "حذف جميع المؤشرات", + "infoDeleteAllIndicators": "سيؤدي ذلك إلى حذف جميع المؤشرات النشطة.", + "infoResetIndicators": "سيؤدي هذا إلى إعادة تعيين مؤشر {indicator} إلى إعداداته الافتراضية.", + "labelDeleteIndicator": "حذف المؤشر {indicator}", + "labelResetIndicator": "إعادة تعيين مؤشر {indicator}", + "infoDeleteIndicator": "هل أنت متأكد من رغبتك في حذف هذا المؤشر؟", + "labelCancel": "إلغاء", + "labelDelete": "حذف", + "labelDeleteAll": "حذف الكل", + "infoUpto3indicatorsAllowed": "يُسمح بحد أقصى 3 مؤشرات نشطة.", + "infoNoActiveIndicators": "لا توجد مؤشرات نشطة.", + "labelReset": "إعادة تعيين", + "labelApply": "قدم طلبك", + "labelOK": "حسناً", + "labelRSILine": "خط مؤشر القوة النسبية RSI", + "labelPeriod": "الفترة", + "labelMinRange": "الحد الأدنى للنطاق", + "labelMaxRange": "الحد الأقصى للنطاق", + "labelSource": "المصدر", + "labelClose": "إغلاق", + "labelOpen": "افتح", + "labelHigh": "عالية", + "labelLow": "منخفضة", + "labelHl2": "هـ / 2", + "labelHlc3": "Hlc/3", + "labelHlcc4": "Hlcc/4", + "labelOhlc4": "أولك/4", + "labelShowZones": "مناطق العرض", + "labelOverbought": "الإفراط في الشراء", + "labelOversold": "المبالغة في البيع", + "labelMinSize": "الحد الأدنى للحجم", + "labelMaxSize": "الحد الأقصى للحجم", + "labelRange": "النطاق", + "labelOverboughtLine": "خط ذروة الشراء", + "labelOversoldLine": "خط المبيع الزائد", + "labelMACDLine": "خط MACD", + "labelFastMAPeriod": "فترة المتوسط المتحرك السريع", + "labelSlowMAPeriod": "فترة بطء المتوسط المتحرك البطيء", + "labelSignalLine": "خط الإشارة", + "labelSignalPeriod": "فترة الإشارة", + "labelIncreasingBar": "زيادة الشريط", + "labelDecreasingBar": "شريط تنازلي", + "labelBollingerBandsTop": "قمة بولينجر باندز", + "labelBollingerBandsMedian": "متوسط بولينجر باندز", + "labelBollingerBandsBottom": "قاع بولينجر باندز", + "labelChannelFill": "تعبئة القناة", + "labelFillColor": "لون التعبئة", + "labelStandardDeviations": "الانحرافات المعيارية", + "labelMovingAverageType": "نوع المتوسط المتحرك", + "labelMALine": "خط MA", + "labelOffset": "الأوفست", + "labelType": "النوع", + "labelSimple": "بسيطة", + "labelExponential": "الأسي", + "labelWeighted": "مرجح", + "labelHull": "هال", + "labelZeroLag": "التأخر الصفري", + "labelTimeSeries": "السلاسل الزمنية", + "labelWellesWilder": "ويلز وايلدر", + "labelVariable": "متغير", + "labelTriangular": "مثلث الشكل", + "label2Exponential": "2-الأسي", + "label3Exponential": "3-الإكسبونسي", + "warnEnterValueBetweenMinMax": "أدخل قيمة بين {min} و {max}", + "warnRangeMinMax": "النطاق {min} - {max}", + "labelDrawingTools": "أدوات الرسم", + "labelTools": "أدوات", + "labelLine": "خط", + "labelRay": "شعاع", + "informTapToSetFirstPoint": "انقر لتعيين النقطة الأولى", + "informTapToSetFinalPoint": "انقر لتعيين النقطة النهائية", + "informNoActiveDrawingTools": "لا توجد أدوات رسم نشطة.", + "actionAddDrawingTool": "إضافة أداة رسم", + "labelOf": "من", + "labelDeleteAllDrawingTools": "احذف جميع أدوات الرسم", + "informDeleteAllDrawingTools": "سيؤدي هذا إلى حذف جميع أدوات الرسم النشطة." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_bn.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_bn.arb new file mode 100644 index 000000000..bda6fa2e9 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_bn.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "সূচক", + "labelActive": "সক্রিয়", + "labelAll": "সব", + "labelMomentum": "মোমেন্টাম", + "labelVolatility": "অস্থিরতা", + "labelMovingAverages": "মুভিং গড়", + "labelMACD": "ম্যাকডি", + "labelRelativeStrengthIndex": "আপেক্ষিক শক্তি সূচক (আরএসআই)", + "labelRSI": "আরএসআই", + "labelBollingerBands": "বোলিঙ্গার ব্যান্ড (বিবি)", + "labelBB": "বিবি", + "labelMovingAverage": "মুভিং এভারেজ (এমএ)", + "labelMA": "মা", + "infoMACD": "এমএসিডি স্টকের দামের প্রযুক্তিগত বিশ্লেষণে ব্যবহৃত একটি ট্রেডিং সূচক। এটি কোনও স্টকের দামের প্রবণতার শক্তি, দিক, গতি এবং সময়কালের পরিবর্তনগুলি প্রকাশ করবে বলে মনে করা হয়।", + "infoRSI": "আপেক্ষিক শক্তি সূচক (আরএসআই) প্রকাশ করেছিলেন জে ওয়েলস ওয়াইল্ডার। বর্তমান মূল্য 0 থেকে 100 এর মধ্যে শতাংশ হিসাবে স্বাভাবিক করা হয়। এই অসিলেটরের ফ্লুটার_চার্ট_আইডি বিভ্রান্তিকর কারণ এটি অন্য যন্ত্র বা যন্ত্রের সেটের সাথে তুলনা করে না, বরং নির্বাচিত লুকব্যাক উইন্ডো দৈর্ঘ্যের মধ্যে অন্যান্য সাম্প্রতিক টুকরোগুলির তুলনায় বর্তমান দামের প্রতিনিধিত্ব করে।", + "infoBB": "পূর্ববর্তী ট্রেডগুলির তুলনায় মূল্যের উচ্চতা বা নিম্নতা পরিমাপ করতে বোলিঙ্গার ব্যান্ড (বিবি) ব্যবহার করা যেতে পারে।", + "infoMA": "মুভিং এভারেজ (এমএ) স্বল্পমেয়াদী মূল্যের ওঠানামা ফিল্টার করে সামগ্রিক বাজারের প্রবণতা সনাক্ত করতে ঐতিহাসিক ডেটা ব্যবহার করে, এটি একটি নির্দিষ্ট সময়কালে গড় মূল্য গণনা করে এবং চার্টে একটি লাইন প্লট করে। এমএ লাইন যদি উপরের দিকে চলে যায় তবে এটি একটি আপট্রেন্ডের সূচক, নীচের দিকে চলে গেলে একটি ডাউনট্রেন্ড। মূল্য এমএ লাইনের উপরে চলে গেলে একটি ক্রয় সংকেত ঘটে।", + "infoMaximumActiveIndicatorsAdded": "আপনি সর্বাধিক সংখ্যক সক্রিয় সূচক যুক্ত করেছেন।", + "infoAddSelectedIndicator": "যোগ করুন {indicator}", + "infoAddIndicator": "সূচক যোগ করুন", + "labelDeleteAllIndicators": "সমস্ত সূচক মুছুন", + "infoDeleteAllIndicators": "এটি সমস্ত সক্রিয় সূচক মুছে ফেলবে।", + "infoResetIndicators": "এটি {indicator} সূচকটিকে তার ডিফল্ট সেটিংসে পুনরায় সেট করবে।", + "labelDeleteIndicator": "{indicator} সূচক মুছুন", + "labelResetIndicator": "রিসেট {indicator} সূচক", + "infoDeleteIndicator": "আপনি কি নিশ্চিত যে আপনি এই সূচকটি মুছে ফেলতে চান?", + "labelCancel": "বাতিল করুন", + "labelDelete": "মুছে ফেলুন", + "labelDeleteAll": "সব মুছে ফেলুন", + "infoUpto3indicatorsAllowed": "3 টি পর্যন্ত সক্রিয় সূচক অনুমোদিত।", + "infoNoActiveIndicators": "কোন সক্রিয় সূচক নেই।", + "labelReset": "রিসেট করুন", + "labelApply": "আবেদন করুন", + "labelOK": "ঠিক আছে", + "labelRSILine": "আরএসআই লাইন", + "labelPeriod": "সময়কাল", + "labelMinRange": "নিম্ন পরিসীমা", + "labelMaxRange": "সর্বোচ্চ ব্যাপ্তি", + "labelSource": "উৎস", + "labelClose": "বন্ধ করুন", + "labelOpen": "খোলা", + "labelHigh": "উচ্চ", + "labelLow": "নিম্ন", + "labelHl2": "এইচএল/2", + "labelHlc3": "এইচএলসি/3", + "labelHlcc4": "এইচএলসিসি/4", + "labelOhlc4": "ওএইচএলসি/4", + "labelShowZones": "অঞ্চল দেখান", + "labelOverbought": "অতিরিক্ত খরচ", + "labelOversold": "অতিরিক্ত বিক্রি", + "labelMinSize": "ন্যূনতম আকার", + "labelMaxSize": "সর্বোচ্চ আকার", + "labelRange": "পরিসীমা", + "labelOverboughtLine": "ওভারবাউট লাইন", + "labelOversoldLine": "ওভারসোল্ড লাইন", + "labelMACDLine": "এমএসিডি লাইন", + "labelFastMAPeriod": "দ্রুত এমএ পিরিয়ড", + "labelSlowMAPeriod": "ধীর এমএ পিরিয়ড", + "labelSignalLine": "সিগন্যাল লাইন", + "labelSignalPeriod": "সংকেত সময়কাল", + "labelIncreasingBar": "বর্ধমান বার", + "labelDecreasingBar": "হ্রাস করা বার", + "labelBollingerBandsTop": "বোলিংগার ব্যান্ড শীর্ষ", + "labelBollingerBandsMedian": "বোলিঙ্গার ব্যান্ডের মাঝারি", + "labelBollingerBandsBottom": "বোলিঙ্গার ব্যান্ড নীচে", + "labelChannelFill": "চ্যানেল পূরণ", + "labelFillColor": "রঙ পূরণ করুন", + "labelStandardDeviations": "মান বিচ্যুতি", + "labelMovingAverageType": "মুভিং এভারেজ প্রকার", + "labelMALine": "এমএ লাইন", + "labelOffset": "অফসেট", + "labelType": "টাইপ", + "labelSimple": "সহজ", + "labelExponential": "এক্সপোনেন্সিয়াল", + "labelWeighted": "ওজনযুক্ত", + "labelHull": "হাল", + "labelZeroLag": "জিরো ল্যাগ", + "labelTimeSeries": "টাইম সিরিজ", + "labelWellesWilder": "ওয়েলস ওয়াইল্ডার", + "labelVariable": "পরিবর্তনশীল", + "labelTriangular": "ত্রিভুজ", + "label2Exponential": "2-এক্সপোনেন্সিয়াল", + "label3Exponential": "3-এক্সপোনেন্সিয়াল", + "warnEnterValueBetweenMinMax": "{min} এবং {max}এর মধ্যে একটি মান লিখুন", + "warnRangeMinMax": "রেঞ্জ {min} - {max}", + "labelDrawingTools": "অঙ্কন টুলস", + "labelTools": "সরঞ্জাম", + "labelLine": "লাইন", + "labelRay": "রশ্মি", + "informTapToSetFirstPoint": "প্রথম পয়েন্ট সেট করতে আলতো চাপুন", + "informTapToSetFinalPoint": "চূড়ান্ত পয়েন্ট সেট করতে ট্যাপ করুন", + "informNoActiveDrawingTools": "সক্রিয় অঙ্কন সরঞ্জাম নেই।", + "actionAddDrawingTool": "অঙ্কন টুল যোগ করুন", + "labelOf": "এর", + "labelDeleteAllDrawingTools": "সমস্ত অঙ্কন সরঞ্জাম মুছে", + "informDeleteAllDrawingTools": "এটি সমস্ত সক্রিয় অঙ্কন সরঞ্জাম মুছে ফেলবে।" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_de.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_de.arb new file mode 100644 index 000000000..5cf403d82 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_de.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "Indikatoren", + "labelActive": "Aktiv", + "labelAll": "Alle", + "labelMomentum": "Momentum", + "labelVolatility": "Volatilität", + "labelMovingAverages": "Gleitende Durchschnitte", + "labelMACD": "MACD", + "labelRelativeStrengthIndex": "Relative Strength Index (RSI)", + "labelRSI": "RSI", + "labelBollingerBands": "Bollinger Bands (BB)", + "labelBB": "BB", + "labelMovingAverage": "Gleitender Durchschnitt (MA)", + "labelMA": "MA", + "infoMACD": "Der MACD ist ein Handelsindikator, der bei der technischen Analyse von Aktienkursen verwendet wird. Er soll Veränderungen in der Stärke, Richtung, Dynamik und Dauer eines Trends bei einem Aktienkurs aufzeigen.", + "infoRSI": "Der Relative Strength Index (RSI) wurde von J. Welles Wilder veröffentlicht. Der aktuelle Preis wird als Prozentsatz zwischen 0 und 100 normiert. Die flutter_chart_id dieses Oszillators ist irreführend, da sie das Instrument nicht relativ zu einem anderen Instrument oder einer Gruppe von Instrumenten vergleicht, sondern den aktuellen Preis relativ zu anderen kürzlich beobachteten Werten innerhalb des ausgewählten Betrachtungszeitraums darstellt.", + "infoBB": "Bollinger Bänder (BB) können verwendet werden, um die Höhe oder Tiefe des Kurses im Vergleich zu früheren Handel zu messen.", + "infoMA": "Der gleitende Durchschnitt (MA) hilft dabei, den allgemeinen Markttrend zu erkennen, indem er kurzfristige Kursschwankungen herausfiltert. Anhand historischer Daten berechnet er den Durchschnittspreis über einen bestimmten Zeitraum und zeichnet eine Linie in den Chart ein. Bewegt sich die MA-Linie nach oben, ist dies ein Indikator für einen Aufwärtstrend, bewegt sie sich nach unten, ist dies ein Abwärtstrend. Ein Kaufsignal entsteht, wenn sich der Kurs über die MA-Linie bewegt.", + "infoMaximumActiveIndicatorsAdded": "Sie haben die maximale Anzahl von aktiven Indikatoren hinzugefügt.", + "infoAddSelectedIndicator": "{indicator}hinzufügen", + "infoAddIndicator": "Indikator hinzufügen", + "labelDeleteAllIndicators": "Alle Indikatoren löschen", + "infoDeleteAllIndicators": "Dadurch werden alle aktiven Indikatoren gelöscht.", + "infoResetIndicators": "Dadurch wird der Indikator {indicator} auf seine Standardeinstellungen zurückgesetzt.", + "labelDeleteIndicator": "Löschen {indicator} Indikator", + "labelResetIndicator": "Anzeige {indicator} zurücksetzen", + "infoDeleteIndicator": "Sind Sie sicher, dass Sie diesen Indikator löschen möchten?", + "labelCancel": "Abbrechen", + "labelDelete": "Löschen", + "labelDeleteAll": "Alle löschen", + "infoUpto3indicatorsAllowed": "Bis zu 3 aktive Indikatoren erlaubt.", + "infoNoActiveIndicators": "Keine aktiven Indikatoren.", + "labelReset": "Zurücksetzen", + "labelApply": "Anwenden", + "labelOK": "OK", + "labelRSILine": "RSI-Linie", + "labelPeriod": "Zeitraum", + "labelMinRange": "Min. Reichweite", + "labelMaxRange": "Maximale Reichweite", + "labelSource": "Quelle", + "labelClose": "Schließen Sie", + "labelOpen": "Öffnen Sie", + "labelHigh": "High", + "labelLow": "Low", + "labelHl2": "Hl/2", + "labelHlc3": "Hlc/3", + "labelHlcc4": "Hlcc/4", + "labelOhlc4": "Ohlc/4", + "labelShowZones": "Zonen anzeigen", + "labelOverbought": "Überkauft", + "labelOversold": "Überverkauft", + "labelMinSize": "Minimale Größe", + "labelMaxSize": "Maximale Größe", + "labelRange": "Reichweite", + "labelOverboughtLine": "Überkaufte Linie", + "labelOversoldLine": "Überverkaufte Linie", + "labelMACDLine": "MACD-Linie", + "labelFastMAPeriod": "Schnelle MA-Periode", + "labelSlowMAPeriod": "Langsame MA-Periode", + "labelSignalLine": "Signalleitung", + "labelSignalPeriod": "Signalperiode", + "labelIncreasingBar": "Steigende Bar", + "labelDecreasingBar": "Abnehmender Balken", + "labelBollingerBandsTop": "Bollinger Bands oben", + "labelBollingerBandsMedian": "Bollinger Bands Median", + "labelBollingerBandsBottom": "Bollinger Bands unten", + "labelChannelFill": "Kanal füllen", + "labelFillColor": "Farbe ausfüllen", + "labelStandardDeviations": "Standardabweichungen", + "labelMovingAverageType": "Gleitender Durchschnitt Typ", + "labelMALine": "MA-Linie", + "labelOffset": "Versetzt", + "labelType": "Typ", + "labelSimple": "Einfach", + "labelExponential": "Exponential", + "labelWeighted": "Gewichtet", + "labelHull": "Rumpf", + "labelZeroLag": "Null Verzögerung", + "labelTimeSeries": "Zeitreihen", + "labelWellesWilder": "Welles Wilder", + "labelVariable": "Variabel", + "labelTriangular": "Dreieckig", + "label2Exponential": "2-Exponential", + "label3Exponential": "3-Exponential", + "warnEnterValueBetweenMinMax": "Geben Sie einen Wert zwischen {min} und {max}ein.", + "warnRangeMinMax": "Reichweite {min} - {max}", + "labelDrawingTools": "Werkzeuge zum Zeichnen", + "labelTools": "Werkzeuge", + "labelLine": "Linie", + "labelRay": "Ray", + "informTapToSetFirstPoint": "Tippe, um den ersten Punkt zu setzen", + "informTapToSetFinalPoint": "Tippe, um den Endpunkt festzulegen", + "informNoActiveDrawingTools": "Keine aktiven Zeichenwerkzeuge.", + "actionAddDrawingTool": "Zeichentool hinzufügen", + "labelOf": "von", + "labelDeleteAllDrawingTools": "Alle Zeichenwerkzeuge löschen", + "informDeleteAllDrawingTools": "Dadurch werden alle aktiven Zeichenwerkzeuge gelöscht." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_en.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_en.arb new file mode 100644 index 000000000..0923f64c9 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_en.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "Indicators", + "labelActive": "Active", + "labelAll": "All", + "labelMomentum": "Momentum", + "labelVolatility": "Volatility", + "labelMovingAverages": "Moving averages", + "labelMACD": "MACD", + "labelRelativeStrengthIndex": "Relative Strength Index (RSI)", + "labelRSI": "RSI", + "labelBollingerBands": "Bollinger Bands (BB)", + "labelBB": "BB", + "labelMovingAverage": "Moving Average (MA)", + "labelMA": "MA", + "infoMACD": "MACD is a trading indicator used in technical analysis of stock prices. It is supposed to reveal changes in the strength, direction, momentum, and duration of a trend in a stock's price.", + "infoRSI": "The Relative Strength Index (RSI) was published by J. Welles Wilder. The current price is normalized as a percentage between 0 and 100. The flutter_chart_id of this oscillator is misleading because it does not compare the instrument relative to another instrument or set of instruments, but rather represents the current price relative to other recent pieces within the selected lookback window length.", + "infoBB": "Bollinger Bands (BB) can be used to measure the highness or lowness of the price relative to previous trades.", + "infoMA": "The Moving Average (MA) helps to identify the overall market trend by filtering out short-term price fluctuations. Using historical data, it calculates the average price over a specific period and plots a line on the chart. If the MA line moves upwards, it’s an indicator of an uptrend, a downtrend if it moves downwards. A buy signal occurs when the price moves above the MA line.", + "infoMaximumActiveIndicatorsAdded": "You've added the maximum number of active indicators.", + "infoAddSelectedIndicator": "Add {indicator}", + "infoAddIndicator": "Add indicator", + "labelDeleteAllIndicators": "Delete all indicators", + "infoDeleteAllIndicators": "This will delete all active indicators.", + "infoResetIndicators": "This will reset the {indicator} indicator to its default settings.", + "labelDeleteIndicator": "Delete {indicator} indicator", + "labelResetIndicator": "Reset {indicator} indicator", + "infoDeleteIndicator": "Are you sure you want to delete this indicator?", + "labelCancel": "Cancel", + "labelDelete": "Delete", + "labelDeleteAll": "Delete All", + "infoUpto3indicatorsAllowed": "Up to 3 active indicators allowed.", + "infoNoActiveIndicators": "No active indicators.", + "labelReset": "Reset", + "labelApply": "Apply", + "labelOK": "OK", + "labelRSILine": "RSI line", + "labelPeriod": "Period", + "labelMinRange": "Min range", + "labelMaxRange": "Max range", + "labelSource": "Source", + "labelClose": "Close", + "labelOpen": "Open", + "labelHigh": "High", + "labelLow": "Low", + "labelHl2": "Hl/2", + "labelHlc3": "Hlc/3", + "labelHlcc4": "Hlcc/4", + "labelOhlc4": "Ohlc/4", + "labelShowZones": "Show Zones", + "labelOverbought": "Overbought", + "labelOversold": "Oversold", + "labelMinSize": "Min size", + "labelMaxSize": "Max size", + "labelRange": "Range", + "labelOverboughtLine": "Overbought line", + "labelOversoldLine": "Oversold line", + "labelMACDLine": "MACD line", + "labelFastMAPeriod": "Fast MA period", + "labelSlowMAPeriod": "Slow MA period", + "labelSignalLine": "Signal line", + "labelSignalPeriod": "Signal period", + "labelIncreasingBar": "Increasing bar", + "labelDecreasingBar": "Decreasing bar", + "labelBollingerBandsTop": "Bollinger Bands top", + "labelBollingerBandsMedian": "Bollinger Bands median", + "labelBollingerBandsBottom": "Bollinger Bands bottom", + "labelChannelFill": "Channel fill", + "labelFillColor": "Fill color", + "labelStandardDeviations": "Standard deviations", + "labelMovingAverageType": "Moving Average Type", + "labelMALine": "MA line", + "labelOffset": "Offset", + "labelType": "Type", + "labelSimple": "Simple", + "labelExponential": "Exponential", + "labelWeighted": "Weighted", + "labelHull": "Hull", + "labelZeroLag": "Zero Lag", + "labelTimeSeries": "Time Series", + "labelWellesWilder": "Welles Wilder", + "labelVariable": "Variable", + "labelTriangular": "Triangular", + "label2Exponential": "2-Exponential", + "label3Exponential": "3-Exponential", + "warnEnterValueBetweenMinMax": "Enter a value between {min} and {max}", + "warnRangeMinMax": "Range {min} - {max}", + "labelDrawingTools": "Drawing tools", + "labelTools": "Tools", + "labelLine": "Line", + "labelRay": "Ray", + "informTapToSetFirstPoint": "Tap to set first point", + "informTapToSetFinalPoint": "Tap to set final point", + "informNoActiveDrawingTools": "No active drawing tools.", + "actionAddDrawingTool": "Add drawing tool", + "labelOf": "of", + "labelDeleteAllDrawingTools": "Delete all drawing tools", + "informDeleteAllDrawingTools": "This will delete all active drawing tools." +} diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_es.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_es.arb new file mode 100644 index 000000000..e9e92b4c4 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_es.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "Indicadores", + "labelActive": "Activo", + "labelAll": "Todos", + "labelMomentum": "Impulso", + "labelVolatility": "Volatilidad", + "labelMovingAverages": "Medias móviles", + "labelMACD": "MACD", + "labelRelativeStrengthIndex": "Índice de fuerza relativa (RSI)", + "labelRSI": "RSI", + "labelBollingerBands": "Bandas de Bollinger (BB)", + "labelBB": "BB", + "labelMovingAverage": "Media móvil (MA)", + "labelMA": "MA", + "infoMACD": "El MACD es un indicador utilizado en el análisis técnico de las cotizaciones bursátiles. Se supone que revela los cambios en la fuerza, la dirección, el impulso y la duración de una tendencia en el precio de una acción.", + "infoRSI": "El índice de fuerza relativa (RSI) fue publicado por J. Welles Wilder. El precio actual se normaliza como un porcentaje entre 0 y 100. El flutter_chart_id de este oscilador es engañoso porque no compara el instrumento en relación con otro instrumento o conjunto de instrumentos, sino que representa el precio actual en relación con otras piezas recientes dentro de la longitud de la ventana de retroceso seleccionada.", + "infoBB": "Las Bandas de Bollinger (BB) pueden utilizarse para medir la altitud o la caída del precio en relación con operaciones anteriores.", + "infoMA": "La media móvil (MA) ayuda a identificar la tendencia general del mercado filtrando las fluctuaciones de precios a corto plazo. Utilizando datos históricos, calcula el precio medio durante un periodo específico y traza una línea en el gráfico. Si la línea MA se mueve hacia arriba, es un indicador de una tendencia alcista, una tendencia bajista si se mueve hacia abajo. Una señal de compra se produce cuando el precio se mueve por encima de la línea MA.", + "infoMaximumActiveIndicatorsAdded": "Ha añadido el número máximo de indicadores activos.", + "infoAddSelectedIndicator": "Añada {indicator}", + "infoAddIndicator": "Añadir indicador", + "labelDeleteAllIndicators": "Borrar todos los indicadores", + "infoDeleteAllIndicators": "Esto borrará todos los indicadores activos.", + "infoResetIndicators": "Esto restablecerá el indicador {indicator} a su configuración predeterminada.", + "labelDeleteIndicator": "Borrar el indicador {indicator}", + "labelResetIndicator": "Indicador Reset {indicator}", + "infoDeleteIndicator": "¿Está seguro de que desea eliminar este indicador?", + "labelCancel": "Cancelar", + "labelDelete": "Borrar", + "labelDeleteAll": "Borrar todo", + "infoUpto3indicatorsAllowed": "Se permiten hasta 3 indicadores activos.", + "infoNoActiveIndicators": "No hay indicadores activos.", + "labelReset": "Reinicie", + "labelApply": "Aplique", + "labelOK": "OK", + "labelRSILine": "Línea RSI", + "labelPeriod": "Periodo", + "labelMinRange": "Alcance mínimo", + "labelMaxRange": "Alcance máximo", + "labelSource": "Fuente", + "labelClose": "Cerrar", + "labelOpen": "Abra", + "labelHigh": "Alta", + "labelLow": "Bajo", + "labelHl2": "Hl/2", + "labelHlc3": "Hlc/3", + "labelHlcc4": "Hlcc/4", + "labelOhlc4": "Ohlc/4", + "labelShowZones": "Mostrar zonas", + "labelOverbought": "Sobrecompra", + "labelOversold": "Sobreventa", + "labelMinSize": "Tamaño mínimo", + "labelMaxSize": "Tamaño máximo", + "labelRange": "Gama", + "labelOverboughtLine": "Línea de sobrecompra", + "labelOversoldLine": "Línea de sobreventa", + "labelMACDLine": "Línea MACD", + "labelFastMAPeriod": "Periodo MA rápido", + "labelSlowMAPeriod": "Periodo MA lento", + "labelSignalLine": "Línea de señales", + "labelSignalPeriod": "Periodo de señalización", + "labelIncreasingBar": "Barra creciente", + "labelDecreasingBar": "Barra decreciente", + "labelBollingerBandsTop": "Parte superior de las bandas de Bollinger", + "labelBollingerBandsMedian": "Mediana de las bandas de Bollinger", + "labelBollingerBandsBottom": "Fondo de las Bandas de Bollinger", + "labelChannelFill": "Relleno del canal", + "labelFillColor": "Color de relleno", + "labelStandardDeviations": "Desviaciones estándar", + "labelMovingAverageType": "Tipo de media móvil", + "labelMALine": "Línea MA", + "labelOffset": "Desplazamiento", + "labelType": "Tipo", + "labelSimple": "Simple", + "labelExponential": "Exponencial", + "labelWeighted": "Ponderado", + "labelHull": "Casco", + "labelZeroLag": "Retraso cero", + "labelTimeSeries": "Series temporales", + "labelWellesWilder": "Welles Wilder", + "labelVariable": "Variable", + "labelTriangular": "Triangular", + "label2Exponential": "2-Exponencial", + "label3Exponential": "3-Exponencial", + "warnEnterValueBetweenMinMax": "Introduzca un valor entre {min} y {max}", + "warnRangeMinMax": "Gama {min} - {max}", + "labelDrawingTools": "Herramientas de dibujo", + "labelTools": "Herramientas", + "labelLine": "Línea", + "labelRay": "Rayo", + "informTapToSetFirstPoint": "Pulsa para establecer el primer punto", + "informTapToSetFinalPoint": "Pulsa para establecer el punto final", + "informNoActiveDrawingTools": "No hay herramientas de dibujo activas.", + "actionAddDrawingTool": "Añadir herramienta de dibujo", + "labelOf": "de", + "labelDeleteAllDrawingTools": "Eliminar todas las herramientas de dibujo", + "informDeleteAllDrawingTools": "Esto borrará todas las herramientas de dibujo activas." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_fr.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_fr.arb new file mode 100644 index 000000000..f52d9609c --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_fr.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "Indicateurs", + "labelActive": "Actif", + "labelAll": "Tous", + "labelMomentum": "L'élan", + "labelVolatility": "Volatilité", + "labelMovingAverages": "Moyennes mobiles", + "labelMACD": "MACD", + "labelRelativeStrengthIndex": "Indice de force relative (IFR)", + "labelRSI": "RSI", + "labelBollingerBands": "Bandes de Bollinger (BB)", + "labelBB": "BB", + "labelMovingAverage": "Moyenne mobile (MA)", + "labelMA": "MA", + "infoMACD": "Le MACD est un indicateur de trading utilisé dans l'analyse technique des cours boursiers. Il est censé révéler les changements dans la force, la direction, l'élan et la durée d'une tendance dans le prix d'une action.", + "infoRSI": "L'indice de force relative (RSI) a été publié par J. Welles Wilder. Le prix actuel est normalisé en pourcentage entre 0 et 100. Le flutter_chart_id de cet oscillateur est trompeur car il ne compare pas l'instrument par rapport à un autre instrument ou à un ensemble d'instruments, mais représente plutôt le prix actuel par rapport à d'autres pièces récentes dans la longueur de la fenêtre d'observation sélectionnée.", + "infoBB": "Les bandes de Bollinger (BB) peuvent être utilisées pour mesurer la hausse ou la baisse du prix par rapport aux transactions précédentes.", + "infoMA": "La moyenne mobile (MA) permet d'identifier la tendance générale du marché en filtrant les fluctuations de prix à court terme. À l'aide de données historiques, elle calcule le prix moyen sur une période donnée et trace une ligne sur le graphique. Si la ligne de la MA se déplace vers le haut, il s'agit d'un indicateur de tendance haussière, et si elle se déplace vers le bas, il s'agit d'un indicateur de tendance baissière. Un signal d'achat se produit lorsque le prix passe au-dessus de la ligne MA.", + "infoMaximumActiveIndicatorsAdded": "Vous avez ajouté le nombre maximum d'indicateurs actifs.", + "infoAddSelectedIndicator": "Ajoutez {indicator}", + "infoAddIndicator": "Ajouter un indicateur", + "labelDeleteAllIndicators": "Supprimer tous les indicateurs", + "infoDeleteAllIndicators": "Cette opération supprime tous les indicateurs actifs.", + "infoResetIndicators": "Cela réinitialisera les paramètres par défaut de l'indicateur {indicator} .", + "labelDeleteIndicator": "Indicateur de suppression {indicator}", + "labelResetIndicator": "Réinitialiser l'indicateur {indicator}", + "infoDeleteIndicator": "Êtes-vous sûr de vouloir supprimer cet indicateur ?", + "labelCancel": "Annuler", + "labelDelete": "Supprimer", + "labelDeleteAll": "Supprimer tout", + "infoUpto3indicatorsAllowed": "Jusqu'à 3 indicateurs actifs sont autorisés.", + "infoNoActiveIndicators": "Aucun indicateur actif.", + "labelReset": "Remise à zéro", + "labelApply": "Appliquer", + "labelOK": "OK", + "labelRSILine": "Ligne RSI", + "labelPeriod": "Période", + "labelMinRange": "Plage minimale", + "labelMaxRange": "Portée maximale", + "labelSource": "Source", + "labelClose": "Fermer", + "labelOpen": "Ouvrir", + "labelHigh": "Haut", + "labelLow": "Faible", + "labelHl2": "Hl/2", + "labelHlc3": "Hlc/3", + "labelHlcc4": "Hlcc/4", + "labelOhlc4": "Ohlc/4", + "labelShowZones": "Afficher les zones", + "labelOverbought": "Surachat", + "labelOversold": "Survente", + "labelMinSize": "Taille minimale", + "labelMaxSize": "Taille maximale", + "labelRange": "Gamme", + "labelOverboughtLine": "Ligne de surachat", + "labelOversoldLine": "Ligne de survente", + "labelMACDLine": "Ligne MACD", + "labelFastMAPeriod": "Période d'AM rapide", + "labelSlowMAPeriod": "Période d'AM lente", + "labelSignalLine": "Ligne de signal", + "labelSignalPeriod": "Période du signal", + "labelIncreasingBar": "Augmentation de la barre", + "labelDecreasingBar": "Barre décroissante", + "labelBollingerBandsTop": "Sommet des bandes de Bollinger", + "labelBollingerBandsMedian": "Bandes de Bollinger médianes", + "labelBollingerBandsBottom": "Fond des bandes de Bollinger", + "labelChannelFill": "Remplissage du canal", + "labelFillColor": "Couleur de remplissage", + "labelStandardDeviations": "Écarts types", + "labelMovingAverageType": "Type de moyenne mobile", + "labelMALine": "Ligne MA", + "labelOffset": "Décalage", + "labelType": "Type", + "labelSimple": "Simple", + "labelExponential": "Exponentiel", + "labelWeighted": "Pondéré", + "labelHull": "Coque", + "labelZeroLag": "Zéro lag", + "labelTimeSeries": "Séries chronologiques", + "labelWellesWilder": "Welles Wilder", + "labelVariable": "Variable", + "labelTriangular": "Triangulaire", + "label2Exponential": "2-Exponentiel", + "label3Exponential": "3-Exponentiel", + "warnEnterValueBetweenMinMax": "Saisissez une valeur comprise entre {min} et {max}", + "warnRangeMinMax": "Gamme {min} - {max}", + "labelDrawingTools": "Outils de dessin", + "labelTools": "Outils", + "labelLine": "Ligne", + "labelRay": "Ray", + "informTapToSetFirstPoint": "Touchez pour définir le premier point", + "informTapToSetFinalPoint": "Touchez pour définir le point final", + "informNoActiveDrawingTools": "Aucun outil de dessin actif.", + "actionAddDrawingTool": "Ajouter un outil de dessin", + "labelOf": "de", + "labelDeleteAllDrawingTools": "Supprimer tous les outils de dessin", + "informDeleteAllDrawingTools": "Cela supprimera tous les outils de dessin actifs." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_it.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_it.arb new file mode 100644 index 000000000..767bc9fdf --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_it.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "Indicatori", + "labelActive": "Attivo", + "labelAll": "Tutti", + "labelMomentum": "Momento", + "labelVolatility": "Volatilità", + "labelMovingAverages": "Medie mobili", + "labelMACD": "MACD", + "labelRelativeStrengthIndex": "Indice di forza relativa (RSI)", + "labelRSI": "RSI", + "labelBollingerBands": "Bande di Bollinger (BB)", + "labelBB": "BB", + "labelMovingAverage": "Media mobile (MA)", + "labelMA": "MA", + "infoMACD": "Il MACD è un indicatore di trading utilizzato nell'analisi tecnica dei prezzi delle azioni. Si suppone che riveli i cambiamenti nella forza, nella direzione, nello slancio e nella durata di una tendenza nel prezzo di un'azione.", + "infoRSI": "L'Indice di Forza Relativa (RSI) è stato pubblicato da J. Welles Wilder. Il prezzo corrente viene normalizzato come percentuale tra 0 e 100. Il flutter_chart_id di questo oscillatore è fuorviante perché non confronta lo strumento rispetto ad un altro strumento o ad un insieme di strumenti, ma rappresenta piuttosto il prezzo corrente rispetto ad altri pezzi recenti all'interno della lunghezza della finestra di lookback selezionata.", + "infoBB": "Le Bande di Bollinger (BB) possono essere utilizzate per misurare l'altezza o la debolezza del prezzo rispetto alle contrattazioni precedenti.", + "infoMA": "La media mobile (MA) aiuta a identificare la tendenza generale del mercato, filtrando le fluttuazioni di prezzo a breve termine. Utilizzando i dati storici, calcola il prezzo medio in un periodo specifico e traccia una linea sul grafico. Se la linea MA si muove verso l'alto, è un indicatore di una tendenza al rialzo, di una tendenza al ribasso se si muove verso il basso. Un segnale di acquisto si verifica quando il prezzo si muove al di sopra della linea MA.", + "infoMaximumActiveIndicatorsAdded": "Ha aggiunto il numero massimo di indicatori attivi.", + "infoAddSelectedIndicator": "Aggiungi {indicator}", + "infoAddIndicator": "Aggiungi indicatore", + "labelDeleteAllIndicators": "Cancellare tutti gli indicatori", + "infoDeleteAllIndicators": "Questo eliminerà tutti gli indicatori attivi.", + "infoResetIndicators": "Ciò ripristinerà le impostazioni predefinite dell'indicatore {indicator} .", + "labelDeleteIndicator": "Cancellare l'indicatore {indicator}", + "labelResetIndicator": "Indicatore Reset {indicator}", + "infoDeleteIndicator": "È sicuro di voler eliminare questo indicatore?", + "labelCancel": "Annullamento", + "labelDelete": "Cancellare", + "labelDeleteAll": "Cancella tutto", + "infoUpto3indicatorsAllowed": "Sono consentiti fino a 3 indicatori attivi.", + "infoNoActiveIndicators": "Non ci sono indicatori attivi.", + "labelReset": "Azzeramento", + "labelApply": "Applicare", + "labelOK": "OK", + "labelRSILine": "Linea RSI", + "labelPeriod": "Periodo", + "labelMinRange": "Intervallo minimo", + "labelMaxRange": "Gamma massima", + "labelSource": "Fonte", + "labelClose": "Chiudere", + "labelOpen": "Aperto", + "labelHigh": "Alto", + "labelLow": "Basso", + "labelHl2": "Hl/2", + "labelHlc3": "Hlc/3", + "labelHlcc4": "Hlcc/4", + "labelOhlc4": "Ohlc/4", + "labelShowZones": "Mostra le zone", + "labelOverbought": "Sovracomprato", + "labelOversold": "In ipervenduto", + "labelMinSize": "Dimensione minima", + "labelMaxSize": "Dimensione massima", + "labelRange": "Gamma", + "labelOverboughtLine": "Linea di ipercomprato", + "labelOversoldLine": "Linea di ipervenduto", + "labelMACDLine": "Linea MACD", + "labelFastMAPeriod": "Periodo di MA veloce", + "labelSlowMAPeriod": "Periodo di MA lento", + "labelSignalLine": "Linea di segnale", + "labelSignalPeriod": "Periodo del segnale", + "labelIncreasingBar": "Barra di aumento", + "labelDecreasingBar": "Barra decrescente", + "labelBollingerBandsTop": "Bande di Bollinger top", + "labelBollingerBandsMedian": "Bande di Bollinger mediane", + "labelBollingerBandsBottom": "Fondo delle Bande di Bollinger", + "labelChannelFill": "Riempimento del canale", + "labelFillColor": "Colore di riempimento", + "labelStandardDeviations": "Deviazioni standard", + "labelMovingAverageType": "Tipo di media mobile", + "labelMALine": "Linea MA", + "labelOffset": "Offset", + "labelType": "Tipo", + "labelSimple": "Semplice", + "labelExponential": "Esponenziale", + "labelWeighted": "Ponderato", + "labelHull": "Scafo", + "labelZeroLag": "Zero Lag", + "labelTimeSeries": "Serie temporale", + "labelWellesWilder": "Welles Wilder", + "labelVariable": "Variabile", + "labelTriangular": "Triangolare", + "label2Exponential": "2-Esponenziale", + "label3Exponential": "3-Esponenziale", + "warnEnterValueBetweenMinMax": "Inserisca un valore compreso tra {min} e {max}", + "warnRangeMinMax": "Gamma {min} - {max}", + "labelDrawingTools": "Strumenti di disegno", + "labelTools": "Utensili", + "labelLine": "Linea", + "labelRay": "raggio", + "informTapToSetFirstPoint": "Tocca per impostare il primo punto", + "informTapToSetFinalPoint": "Tocca per impostare il punto finale", + "informNoActiveDrawingTools": "Nessuno strumento di disegno attivo.", + "actionAddDrawingTool": "Aggiungi strumento di disegno", + "labelOf": "di", + "labelDeleteAllDrawingTools": "Eliminare tutti gli strumenti di disegno", + "informDeleteAllDrawingTools": "Questo eliminerà tutti gli strumenti di disegno attivi." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_km.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_km.arb new file mode 100644 index 000000000..d794b82d5 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_km.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "សូចនាករ", + "labelActive": "សក្តិសម", + "labelAll": "ទាំងអស់", + "labelMomentum": "សន្ទុះ", + "labelVolatility": "ប្រែប្រួល", + "labelMovingAverages": "មធ្យមភាគចល័ត", + "labelMACD": "MACD", + "labelRelativeStrengthIndex": "សន្ទស្សន៍កម្លាំងទាក់ទង (RSI)", + "labelRSI": "RSI", + "labelBollingerBands": "ក្រុមតន្រ្តី Bollinger (BB)", + "labelBB": "BB", + "labelMovingAverage": "មធ្យមភាគចល័ត (MA)", + "labelMA": "MA", + "infoMACD": "MACD គឺជាសូចនាករជួញដូរដែលត្រូវបានប្រើក្នុងការវិភាគបច្ចេកទេសនៃតម្លៃភាគហ៊ុន។ វាត្រូវបានគេសន្មត់ថាដើម្បីបង្ហាញពីការផ្លាស់ប្តូរកម្លាំង ទិសដៅ សន្ទុះ និងរយៈពេលនៃនិន្នាការក្នុងតម្លៃភាគហ៊ុន។", + "infoRSI": "សន្ទស្សន៍កម្លាំងទាក់ទង (RSI) ត្រូវបានបោះពុម្ពផ្សាយដោយ J. Welles Wilder ។ តម្លៃបច្ចុប្បន្នត្រូវបានធ្វើឱ្យមានលក្ខណៈធម្មតាជាភាគរយរវាង 0 និង 100 ។ flutter_chart_id នៃលំយោលនេះគឺជាការបំភាន់ពីព្រោះវាមិនប្រៀបធៀបឧបករណ៍ទាក់ទងទៅនឹងឧបករណ៍ផ្សេងទៀត ឬសំណុំនៃឧបករណ៍នោះទេ ប៉ុន្តែផ្ទុយទៅវិញតំណាងឱ្យតម្លៃបច្ចុប្បន្នទាក់ទងទៅនឹងបំណែកថ្មីៗផ្សេងទៀតនៅក្នុងប្រវែងបង្អួចមើលត្រឡប់មកវិញដែលបានជ្រើសរើស។", + "infoBB": "ក្រុមតន្រ្តី Bollinger (BB) អាចត្រូវបានប្រើដើម្បីវាស់ស្ទង់កម្ពស់ ឬកម្រិតទាបនៃតម្លៃទាក់ទងទៅនឹងការជួញដូរពីមុន។", + "infoMA": "មធ្យមភាគចល័ត (MA) ជួយកំណត់អត្តសញ្ញាណនិន្នាការទីផ្សារទូទៅដោយការច្រោះការប្រងប្រួលតម្លៃរយៈពេលខ្លី។ ដោយប្រើទិន្នន័យប្រវត្តិសាស្ត្រ វាគណនាតម្លៃជាមធ្យមក្នុងរយៈពេលជាក់លាក់មួយ ហើយគូសបន្ទាត់នៅលើតារាង។ ប្រសិនបើបន្ទាត់ MA ផ្លាស់ទីឡើងលើ វាជាសូចនាករនៃនិន្នាការឡើងលើ និន្នាការធ្លាក់ចុះ ប្រសិនបើវាផ្លាស់ទីចុះក្រោម។ សញ្ញាទិញកើតឡើងនៅពេលដែលតម្លៃផ្លាស់ទីខ្ពស់ជាងបន្ទាត់ MA ។", + "infoMaximumActiveIndicatorsAdded": "អ្នកបានបន្ថែមចំនួនអតិបរមានៃសូចនាករសកម្ម។", + "infoAddSelectedIndicator": "បន្ថែម {indicator}", + "infoAddIndicator": "បន្ថែមសូចនាករ", + "labelDeleteAllIndicators": "លុបសូចនាករទាំងអស់។", + "infoDeleteAllIndicators": "នេះនឹងលុបសូចនាករសកម្មទាំងអស់។", + "infoResetIndicators": "នេះនឹងកំណត់សូចនាករ {indicator} ឡើងវិញទៅការកំណត់លំនាំដើមរបស់វា។", + "labelDeleteIndicator": "លុបសូចនាករ {indicator}", + "labelResetIndicator": "កំណត់សូចនាករ {indicator} ឡើងវិញ", + "infoDeleteIndicator": "តើអ្នកពិតជាចង់លុបសូចនាករនេះមែនទេ?", + "labelCancel": "បោះបង់", + "labelDelete": "លុប", + "labelDeleteAll": "លុបទាំងអស់", + "infoUpto3indicatorsAllowed": "អនុញ្ញាតឱ្យមានសូចនាករសកម្មរហូតដល់ 3 ។", + "infoNoActiveIndicators": "មគ្គុទ្ទេសក៍សកម្មទេ។", + "labelReset": "កំណត់ឡើងវិញ", + "labelApply": "ដាក់ពាក្យ", + "labelOK": "យល់ព្រម", + "labelRSILine": "បន្ទាត់ RSI", + "labelPeriod": "រយៈពេល", + "labelMinRange": "ចន្លោះអប្បបរមា", + "labelMaxRange": "ចន្លោះអតិបរមា", + "labelSource": "ប្រភព", + "labelClose": "បិទ", + "labelOpen": "បើក", + "labelHigh": "ខ្ពស់", + "labelLow": "ទាប", + "labelHl2": "Hl/2", + "labelHlc3": "Hlc/3", + "labelHlcc4": "Hlcc/4", + "labelOhlc4": "Ohlc/4", + "labelShowZones": "បង្ហាញតំបន់", + "labelOverbought": "ទិញលើស", + "labelOversold": "លក់លើស", + "labelMinSize": "ទំហំអប្បបរមា", + "labelMaxSize": "ទំហំអតិបរមា", + "labelRange": "ចន្លោះ", + "labelOverboughtLine": "បន្ទាត់ទិញលើស", + "labelOversoldLine": "បន្ទាត់លក់លើស", + "labelMACDLine": "បន្ទាត់ MACD", + "labelFastMAPeriod": "រយៈពេល MA លឿន", + "labelSlowMAPeriod": "រយៈពេល MA យឺត", + "labelSignalLine": "បន្ទាត់សញ្ញា", + "labelSignalPeriod": "រយៈពេលសញ្ញា", + "labelIncreasingBar": "បារកើនឡើង", + "labelDecreasingBar": "បារថយចុះ", + "labelBollingerBandsTop": "កំពូលក្រុមតន្រ្តី Bollinger", + "labelBollingerBandsMedian": "មធ្យមក្រុមតន្រ្តី Bollinger", + "labelBollingerBandsBottom": "បាតក្រុមតន្រ្តី Bollinger", + "labelChannelFill": "បំពេញឆានែល", + "labelFillColor": "បំពេញពណ៌", + "labelStandardDeviations": "គម្លាតស្តង់ដារ", + "labelMovingAverageType": "ប្រភេទមធ្យមភាគចល័ត", + "labelMALine": "បន្ទាត់ MA", + "labelOffset": "ផ្លាស់ទី", + "labelType": "ប្រភេទ", + "labelSimple": "សាមញ្ញ", + "labelExponential": "អិចស្ប៉ូណង់ស្យែល", + "labelWeighted": "ទម្ងន់", + "labelHull": "ស្រោមសំបុត្រ", + "labelZeroLag": "យឺតសូន្យ", + "labelTimeSeries": "ស៊េរីពេលវេលា", + "labelWellesWilder": "Welles Wilder", + "labelVariable": "អថេរ", + "labelTriangular": "ត្រីកោណ", + "label2Exponential": "អិចស្ប៉ូណង់ស្យែល 2", + "label3Exponential": "អិចស្ប៉ូណង់ស្យែល 3", + "warnEnterValueBetweenMinMax": "បញ្ចូលតម្លៃរវាង {min} និង {max}", + "warnRangeMinMax": "ចន្លោះ {min} - {max}", + "labelDrawingTools": "ឧបករណ៍គូរ", + "labelTools": "ឧបករណ៍", + "labelLine": "បន្ទាត់", + "labelRay": "កាំរស្មី", + "informTapToSetFirstPoint": "ប៉ះដើម្បីកំណត់ចំណុចដំបូង", + "informTapToSetFinalPoint": "ប៉ះដើម្បីកំណត់ចំណុចចុងក្រោយ", + "informNoActiveDrawingTools": "មិនមានឧបករណ៍គូរសកម្មទេ។", + "actionAddDrawingTool": "បន្ថែមឧបករណ៍គូរ", + "labelOf": "នៃ", + "labelDeleteAllDrawingTools": "លុបឧបករណ៍គូរទាំងអស់។", + "informDeleteAllDrawingTools": "នេះនឹងលុបឧបករណ៍គូរសកម្មទាំងអស់។" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_ko.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_ko.arb new file mode 100644 index 000000000..dfb7b2826 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_ko.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "지표", + "labelActive": "활성", + "labelAll": "모두", + "labelMomentum": "모멘텀", + "labelVolatility": "변동성", + "labelMovingAverages": "이동 평균", + "labelMACD": "MACD", + "labelRelativeStrengthIndex": "상대 강도 지수(RSI)", + "labelRSI": "RSI", + "labelBollingerBands": "볼린저 밴드(BB)", + "labelBB": "BB", + "labelMovingAverage": "이동 평균(MA)", + "labelMA": "MA", + "infoMACD": "MACD는 주가의 기술적 분석에 사용되는 트레이딩 지표입니다. 이는 주식 가격 추세의 강도, 방향, 모멘텀, 지속 기간의 변화를 나타내는 지표입니다.", + "infoRSI": "상대강도지수(RSI)는 J. 웰즈 와일더가 발표했습니다. 현재 가격은 0에서 100 사이의 백분율로 정규화됩니다. 이 오실레이터의 flutter_chart_id는 다른 상품 또는 상품 세트와 비교하는 것이 아니라 선택한 룩백 윈도우 길이 내의 다른 최근 상품과 비교한 현재 가격을 나타내므로 오해의 소지가 있습니다.", + "infoBB": "볼린저 밴드(BB)는 이전 거래 대비 가격의 고점 또는 저점을 측정하는 데 사용할 수 있습니다.", + "infoMA": "이동평균(MA)은 단기적인 가격 변동을 걸러내어 전체 시장 추세를 파악하는 데 도움이 됩니다. 과거 데이터를 사용하여 특정 기간 동안의 평균 가격을 계산하고 차트에 선을 표시합니다. MA 선이 위로 이동하면 상승 추세, 아래로 이동하면 하락 추세의 지표입니다. 가격이 MA 선 위로 이동하면 매수 신호가 발생합니다.", + "infoMaximumActiveIndicatorsAdded": "활성 지표의 최대 개수를 추가했습니다.", + "infoAddSelectedIndicator": "{indicator}추가", + "infoAddIndicator": "표시기 추가", + "labelDeleteAllIndicators": "모든 지표 삭제", + "infoDeleteAllIndicators": "이렇게 하면 모든 활성 표시기가 삭제됩니다.", + "infoResetIndicators": "이렇게 하면 {indicator} 표시기가 기본 설정으로 재설정됩니다.", + "labelDeleteIndicator": "{indicator} 표시기 삭제", + "labelResetIndicator": "{indicator} 표시기 재설정", + "infoDeleteIndicator": "이 표시기를 삭제하시겠습니까?", + "labelCancel": "취소", + "labelDelete": "삭제", + "labelDeleteAll": "모두 삭제", + "infoUpto3indicatorsAllowed": "활성 표시기는 최대 3개까지 허용됩니다.", + "infoNoActiveIndicators": "활성 표시기가 없습니다.", + "labelReset": "초기화", + "labelApply": "신청하기", + "labelOK": "확인", + "labelRSILine": "RSI 라인", + "labelPeriod": "기간", + "labelMinRange": "최소 범위", + "labelMaxRange": "최대 범위", + "labelSource": "출처", + "labelClose": "닫기", + "labelOpen": "열기", + "labelHigh": "높음", + "labelLow": "낮음", + "labelHl2": "Hl/2", + "labelHlc3": "Hlc/3", + "labelHlcc4": "Hlcc/4", + "labelOhlc4": "Ohlc/4", + "labelShowZones": "영역 표시", + "labelOverbought": "과매수", + "labelOversold": "과매도", + "labelMinSize": "최소 크기", + "labelMaxSize": "최대 크기", + "labelRange": "범위", + "labelOverboughtLine": "과매수 라인", + "labelOversoldLine": "과매도 라인", + "labelMACDLine": "MACD 라인", + "labelFastMAPeriod": "빠른 MA 기간", + "labelSlowMAPeriod": "느린 MA 기간", + "labelSignalLine": "신호 라인", + "labelSignalPeriod": "신호 기간", + "labelIncreasingBar": "바 증가", + "labelDecreasingBar": "바 감소", + "labelBollingerBandsTop": "볼린저 밴드 상단", + "labelBollingerBandsMedian": "볼린저 밴드 중앙값", + "labelBollingerBandsBottom": "볼린저 밴드 하단", + "labelChannelFill": "채널 채우기", + "labelFillColor": "채우기 색상", + "labelStandardDeviations": "표준 편차", + "labelMovingAverageType": "이동 평균 유형", + "labelMALine": "MA 라인", + "labelOffset": "오프셋", + "labelType": "유형", + "labelSimple": "Simple", + "labelExponential": "지수", + "labelWeighted": "가중치", + "labelHull": "Hull", + "labelZeroLag": "제로 랙", + "labelTimeSeries": "시계열", + "labelWellesWilder": "웰즈 와일더", + "labelVariable": "변수", + "labelTriangular": "삼각형", + "label2Exponential": "2-지수", + "label3Exponential": "3-지수", + "warnEnterValueBetweenMinMax": "{min} ~ {max}사이의 값을 입력합니다.", + "warnRangeMinMax": "범위 {min} - {max}", + "labelDrawingTools": "드로잉 툴", + "labelTools": "도구", + "labelLine": "선", + "labelRay": "광선", + "informTapToSetFirstPoint": "첫 번째 포인트를 설정하려면 누릅니다.", + "informTapToSetFinalPoint": "탭하여 최종 지점을 설정합니다.", + "informNoActiveDrawingTools": "활성 드로잉 도구가 없습니다.", + "actionAddDrawingTool": "드로잉 도구 추가", + "labelOf": "의", + "labelDeleteAllDrawingTools": "모든 그리기 도구 삭제", + "informDeleteAllDrawingTools": "이렇게 하면 활성 드로잉 도구가 모두 삭제됩니다." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_mn.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_mn.arb new file mode 100644 index 000000000..5d9919557 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_mn.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "Indicators", + "labelActive": "Active", + "labelAll": "All", + "labelMomentum": "Momentum", + "labelVolatility": "Volatility", + "labelMovingAverages": "Moving averages", + "labelMACD": "MACD", + "labelRelativeStrengthIndex": "Relative Strength Index (RSI)", + "labelRSI": "RSI", + "labelBollingerBands": "Bollinger Bands (BB)", + "labelBB": "BB", + "labelMovingAverage": "Moving Average (MA)", + "labelMA": "MA", + "infoMACD": "MACD is a trading indicator used in technical analysis of stock prices. It is supposed to reveal changes in the strength, direction, momentum, and duration of a trend in a stock's price.", + "infoRSI": "The Relative Strength Index (RSI) was published by J. Welles Wilder. The current price is normalized as a percentage between 0 and 100. The flutter_chart_id of this oscillator is misleading because it does not compare the instrument relative to another instrument or set of instruments, but rather represents the current price relative to other recent pieces within the selected lookback window length.", + "infoBB": "Bollinger Bands (BB) can be used to measure the highness or lowness of the price relative to previous trades.", + "infoMA": "The Moving Average (MA) helps to identify the overall market trend by filtering out short-term price fluctuations. Using historical data, it calculates the average price over a specific period and plots a line on the chart. If the MA line moves upwards, it’s an indicator of an uptrend, a downtrend if it moves downwards. A buy signal occurs when the price moves above the MA line.", + "infoMaximumActiveIndicatorsAdded": "You've added the maximum number of active indicators.", + "infoAddSelectedIndicator": "Add {indicator}", + "infoAddIndicator": "Add indicator", + "labelDeleteAllIndicators": "Delete all indicators", + "infoDeleteAllIndicators": "This will delete all active indicators.", + "infoResetIndicators": "This will reset the {indicator} indicator to its default settings.", + "labelDeleteIndicator": "Delete {indicator} indicator", + "labelResetIndicator": "Reset {indicator} indicator", + "infoDeleteIndicator": "Are you sure you want to delete this indicator?", + "labelCancel": "Cancel", + "labelDelete": "Delete", + "labelDeleteAll": "Delete All", + "infoUpto3indicatorsAllowed": "Up to 3 active indicators allowed.", + "infoNoActiveIndicators": "No active indicators.", + "labelReset": "Reset", + "labelApply": "Apply", + "labelOK": "OK", + "labelRSILine": "RSI line", + "labelPeriod": "Period", + "labelMinRange": "Min range", + "labelMaxRange": "Max range", + "labelSource": "Source", + "labelClose": "Close", + "labelOpen": "Open", + "labelHigh": "High", + "labelLow": "Low", + "labelHl2": "Hl/2", + "labelHlc3": "Hlc/3", + "labelHlcc4": "Hlcc/4", + "labelOhlc4": "Ohlc/4", + "labelShowZones": "Show Zones", + "labelOverbought": "Overbought", + "labelOversold": "Oversold", + "labelMinSize": "Min size", + "labelMaxSize": "Max size", + "labelRange": "Range", + "labelOverboughtLine": "Overbought line", + "labelOversoldLine": "Oversold line", + "labelMACDLine": "MACD line", + "labelFastMAPeriod": "Fast MA period", + "labelSlowMAPeriod": "Slow MA period", + "labelSignalLine": "Signal line", + "labelSignalPeriod": "Signal period", + "labelIncreasingBar": "Increasing bar", + "labelDecreasingBar": "Decreasing bar", + "labelBollingerBandsTop": "Bollinger Bands top", + "labelBollingerBandsMedian": "Bollinger Bands median", + "labelBollingerBandsBottom": "Bollinger Bands bottom", + "labelChannelFill": "Channel fill", + "labelFillColor": "Fill color", + "labelStandardDeviations": "Standard deviations", + "labelMovingAverageType": "Moving Average Type", + "labelMALine": "MA line", + "labelOffset": "Offset", + "labelType": "Type", + "labelSimple": "Simple", + "labelExponential": "Exponential", + "labelWeighted": "Weighted", + "labelHull": "Hull", + "labelZeroLag": "Zero Lag", + "labelTimeSeries": "Time Series", + "labelWellesWilder": "Welles Wilder", + "labelVariable": "Variable", + "labelTriangular": "Triangular", + "label2Exponential": "2-Exponential", + "label3Exponential": "3-Exponential", + "warnEnterValueBetweenMinMax": "Enter a value between {min} and {max}", + "warnRangeMinMax": "Range {min} - {max}", + "labelDrawingTools": "Drawing tools", + "labelTools": "Tools", + "labelLine": "Line", + "labelRay": "Ray", + "informTapToSetFirstPoint": "Tap to set first point", + "informTapToSetFinalPoint": "Tap to set final point", + "informNoActiveDrawingTools": "No active drawing tools.", + "actionAddDrawingTool": "Add drawing tool", + "labelOf": "-ийн", + "labelDeleteAllDrawingTools": "Бүх зургийн хэрэгслийг устгах", + "informDeleteAllDrawingTools": "Энэ нь бүх идэвхтэй зургийн хэрэгслийг устгах болно." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_pl.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_pl.arb new file mode 100644 index 000000000..52e01514b --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_pl.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "Wskaźniki", + "labelActive": "Aktywny", + "labelAll": "Wszystko", + "labelMomentum": "Pęd", + "labelVolatility": "Zmienność", + "labelMovingAverages": "Średnie kroczące", + "labelMACD": "MACD", + "labelRelativeStrengthIndex": "Względny wskaźnik siły (RSI)", + "labelRSI": "RSI", + "labelBollingerBands": "Zespoły Bollingera (BB)", + "labelBB": "BB", + "labelMovingAverage": "Średnia ruchoma (MA)", + "labelMA": "MA", + "infoMACD": "MACD jest wskaźnikiem handlowym wykorzystywanym w technicznej analizie cen akcji. Ma ujawniać zmiany w sile, kierunku, pędzie i czasie trwania trendu w cenie akcji.", + "infoRSI": "Relative Strength Index (RSI) został opublikowany przez J. Wellesa Wildera. Aktualna cena jest znormalizowana jako procent od 0 do 100. Flutter_chart_id tego oscylatora jest mylący, ponieważ nie porównuje instrumentu względem innego instrumentu lub zestawu instrumentów, ale reprezentuje aktualną cenę w stosunku do innych ostatnich elementów w wybranej długości okna przeglądającego.", + "infoBB": "Pasma Bollingera (BB) można wykorzystać do pomiaru wysokości lub niskiej ceny w stosunku do poprzednich transakcji.", + "infoMA": "Średnia krocząca (MA) pomaga zidentyfikować ogólny trend rynkowy poprzez filtrowanie krótkoterminowych wahań cen. Korzystając z danych historycznych, oblicza średnią cenę w określonym okresie i wykreśla linię na wykresie. Jeśli linia MA porusza się w górę, jest to wskaźnik trendu wzrostowego, trendu spadkowego, jeśli porusza się w dół. Sygnał zakupu pojawia się, gdy cena przesuwa się powyżej linii MA.", + "infoMaximumActiveIndicatorsAdded": "Dodano maksymalną liczbę aktywnych wskaźników.", + "infoAddSelectedIndicator": "Dodaj {indicator}", + "infoAddIndicator": "Dodaj wskaźnik", + "labelDeleteAllIndicators": "Usuń wszystkie wskaźniki", + "infoDeleteAllIndicators": "Spowoduje to usunięcie wszystkich aktywnych wskaźników.", + "infoResetIndicators": "Spowoduje to zresetowanie wskaźnika {indicator} do ustawień domyślnych.", + "labelDeleteIndicator": "Usuń wskaźnik {indicator}", + "labelResetIndicator": "Zresetuj wskaźnik {indicator}", + "infoDeleteIndicator": "Czy na pewno chcesz usunąć ten wskaźnik?", + "labelCancel": "Anuluj", + "labelDelete": "Usuń", + "labelDeleteAll": "Usuń wszystko", + "infoUpto3indicatorsAllowed": "Dozwolone jest do 3 aktywnych wskaźników.", + "infoNoActiveIndicators": "Brak aktywnych wskaźników.", + "labelReset": "Resetuj", + "labelApply": "Zastosuj", + "labelOK": "DOBRZE", + "labelRSILine": "Linia RSI", + "labelPeriod": "Kropka", + "labelMinRange": "Minimalny zasięg", + "labelMaxRange": "Maksymalny zasięg", + "labelSource": "Źródło", + "labelClose": "Zamknij", + "labelOpen": "Otwórz", + "labelHigh": "Wysoki", + "labelLow": "Niski", + "labelHl2": "Hl/2", + "labelHlc3": "HLC/3", + "labelHlcc4": "Hlcc/4", + "labelOhlc4": "Ohlc/4", + "labelShowZones": "Pokaż strefy", + "labelOverbought": "Wykupione", + "labelOversold": "Wyprzedane", + "labelMinSize": "Minimalny rozmiar", + "labelMaxSize": "Maksymalny rozmiar", + "labelRange": "Zasięg", + "labelOverboughtLine": "Linia wykupiona", + "labelOversoldLine": "Linia wyprzedana", + "labelMACDLine": "Linia MACD", + "labelFastMAPeriod": "Szybki okres MA", + "labelSlowMAPeriod": "Powolny okres MA", + "labelSignalLine": "Linia sygnałowa", + "labelSignalPeriod": "Okres sygnału", + "labelIncreasingBar": "Zwiększanie paska", + "labelDecreasingBar": "Malejący pasek", + "labelBollingerBandsTop": "Bollinger Bands Top", + "labelBollingerBandsMedian": "Mediana pasm Bollingera", + "labelBollingerBandsBottom": "Dolne paski Bollingera", + "labelChannelFill": "Wypełnienie kanału", + "labelFillColor": "Kolor wypełnienia", + "labelStandardDeviations": "Odchylenia standardowe", + "labelMovingAverageType": "Typ średniej ruchomej", + "labelMALine": "Linia MA", + "labelOffset": "Przesunięcie", + "labelType": "Typ", + "labelSimple": "Prosty", + "labelExponential": "wykładniczy", + "labelWeighted": "ważony", + "labelHull": "Kadłub", + "labelZeroLag": "Zero opóźnienia", + "labelTimeSeries": "Szereg czasowy", + "labelWellesWilder": "Welles Wilder", + "labelVariable": "Zmienna", + "labelTriangular": "Trójkątne", + "label2Exponential": "2-wykładniczy", + "label3Exponential": "3-wykładniczy", + "warnEnterValueBetweenMinMax": "Wprowadź wartość między {min} i {max}", + "warnRangeMinMax": "Zakres {min} - {max}", + "labelDrawingTools": "Narzędzia do rysowania", + "labelTools": "Narzędzia", + "labelLine": "Linia", + "labelRay": "Ray", + "informTapToSetFirstPoint": "Stuknij, aby ustawić pierwszy punkt", + "informTapToSetFinalPoint": "Stuknij, aby ustawić punkt końcowy", + "informNoActiveDrawingTools": "Brak aktywnych narzędzi do rysowania.", + "actionAddDrawingTool": "Dodaj narzędzie do rysowania", + "labelOf": "z", + "labelDeleteAllDrawingTools": "Usuń wszystkie narzędzia do rysowania", + "informDeleteAllDrawingTools": "Spowoduje to usunięcie wszystkich aktywnych narzędzi do rysowania." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_pt.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_pt.arb new file mode 100644 index 000000000..6bfa55cdd --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_pt.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "Indicadores", + "labelActive": "Ativo", + "labelAll": "Todos", + "labelMomentum": "Momentum", + "labelVolatility": "Volatilidade", + "labelMovingAverages": "Médias móveis", + "labelMACD": "MACD", + "labelRelativeStrengthIndex": "Índice de Força Relativa (RSI)", + "labelRSI": "RSI", + "labelBollingerBands": "Bandas de Bollinger (BB)", + "labelBB": "BB", + "labelMovingAverage": "Média móvel (MA)", + "labelMA": "MA", + "infoMACD": "A Convergência/Divergência das Médias Móveis (MACD) é um indicador de negociação utilizado na análise técnica dos preços das ações. É suposto revelar alterações na força, direção, momentum e duração de uma tendência no preço de uma ação.", + "infoRSI": "O Índice de Força Relativa (RSI) foi publicado por J. Welles Wilder. O preço atual é normalizado como uma percentagem entre 0 e 100. O flutter_chart_id deste oscilador é enganador porque não compara o instrumento com outro instrumento ou conjunto de instrumentos, mas sim representa o preço atual em relação a outras peças recentes dentro do comprimento da janela de lookback selecionada.", + "infoBB": "As Bandas de Bollinger (BB) podem ser usadas para medir se o preço está alto ou baixo em relação a negociações anteriores.", + "infoMA": "A média móvel (MA) ajuda a identificar a tendência geral do mercado ao filtrar as flutuações de preços a curto prazo. Utilizando o histórico de dados, calcula o preço médio durante um período específico e traça uma linha no gráfico. Se a linha MA se mover para cima, indica uma tendência ascendente; se se mover para baixo, indica uma tendência descendente. Um sinal de compra ocorre quando o preço ultrapassa a linha MA.", + "infoMaximumActiveIndicatorsAdded": "Adicionou o número máximo de indicadores ativos.", + "infoAddSelectedIndicator": "Adicionar {indicator}", + "infoAddIndicator": "Adicionar indicador", + "labelDeleteAllIndicators": "Eliminar todos os indicadores", + "infoDeleteAllIndicators": "Esta ação elimina todos os indicadores ativos.", + "infoResetIndicators": "Esta ação irá repor o indicador {indicator} para as suas definições predefinidas.", + "labelDeleteIndicator": "Eliminar o indicador {indicator}", + "labelResetIndicator": "Repor o indicador {indicator}", + "infoDeleteIndicator": "Tem a certeza de que pretende eliminar este indicador?", + "labelCancel": "Cancelar", + "labelDelete": "Eliminar", + "labelDeleteAll": "Eliminar tudo", + "infoUpto3indicatorsAllowed": "São permitidos até 3 indicadores ativos.", + "infoNoActiveIndicators": "Não existem indicadores ativos.", + "labelReset": "Reiniciação", + "labelApply": "Aplicar", + "labelOK": "OK", + "labelRSILine": "Linha do RSI", + "labelPeriod": "Período", + "labelMinRange": "Intervalo mín.", + "labelMaxRange": "Intervalo máx.", + "labelSource": "Fonte", + "labelClose": "Fechado", + "labelOpen": "Aberto", + "labelHigh": "High", + "labelLow": "Low", + "labelHl2": "Hl/2", + "labelHlc3": "Hlc/3", + "labelHlcc4": "Hlcc/4", + "labelOhlc4": "Ohlc/4", + "labelShowZones": "Mostrar zonas", + "labelOverbought": "Sobrecompra", + "labelOversold": "Sobrevenda", + "labelMinSize": "Tamanho mín.", + "labelMaxSize": "Tamanho máx.", + "labelRange": "Intervalo", + "labelOverboughtLine": "Linha de sobrecompra", + "labelOversoldLine": "Linha de sobrevenda", + "labelMACDLine": "Linha do MACD", + "labelFastMAPeriod": "Período da média móvel rápida", + "labelSlowMAPeriod": "Período da média móvel lenta", + "labelSignalLine": "Linha de sinal", + "labelSignalPeriod": "Período de sinal", + "labelIncreasingBar": "Barra crescente", + "labelDecreasingBar": "Barra decrescente", + "labelBollingerBandsTop": "Limite superior das Bandas de Bollinger", + "labelBollingerBandsMedian": "Limite médio das Bandas de Bollinger", + "labelBollingerBandsBottom": "Limite inferior das Bandas de Bollinger", + "labelChannelFill": "Preenchimento do canal", + "labelFillColor": "Cor de preenchimento", + "labelStandardDeviations": "Desvios padrão", + "labelMovingAverageType": "Tipo de média móvel", + "labelMALine": "Linha da MA", + "labelOffset": "Deslocamento", + "labelType": "Tipo", + "labelSimple": "Simples", + "labelExponential": "Exponencial", + "labelWeighted": "Ponderada", + "labelHull": "Hull", + "labelZeroLag": "Zero Lag (sem atraso)", + "labelTimeSeries": "Séries temporais", + "labelWellesWilder": "Welles Wilder", + "labelVariable": "Variável", + "labelTriangular": "Triangular", + "label2Exponential": "2-Exponencial", + "label3Exponential": "3-Exponencial", + "warnEnterValueBetweenMinMax": "Introduza um valor entre {min} e {max}", + "warnRangeMinMax": "Intervalo {min} - {max}", + "labelDrawingTools": "Ferramentas de desenho", + "labelTools": "Ferramentas", + "labelLine": "Linha", + "labelRay": "Linha de raio", + "informTapToSetFirstPoint": "Toque para definir o primeiro ponto", + "informTapToSetFinalPoint": "Toque para definir o ponto final", + "informNoActiveDrawingTools": "Não existem ferramentas de desenho ativas.", + "actionAddDrawingTool": "Adicionar ferramenta de desenho", + "labelOf": "de", + "labelDeleteAllDrawingTools": "Eliminar todas as ferramentas de desenho", + "informDeleteAllDrawingTools": "Isto irá eliminar todas as ferramentas de desenho activas." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_ru.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_ru.arb new file mode 100644 index 000000000..50dba3926 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_ru.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "Индикаторы", + "labelActive": "Активный", + "labelAll": "Все", + "labelMomentum": "Momentum", + "labelVolatility": "Волатильность", + "labelMovingAverages": "Скользящие средние", + "labelMACD": "MACD", + "labelRelativeStrengthIndex": "Индекс относительной силы (RSI)", + "labelRSI": "RSI", + "labelBollingerBands": "Полосы Боллинджера (BB)", + "labelBB": "BB", + "labelMovingAverage": "Скользящая средняя (MA)", + "labelMA": "MA", + "infoMACD": "MACD - это торговый индикатор, используемый в техническом анализе цен на акции. Он призван выявлять изменения в силе, направлении, импульсе и продолжительности тренда в цене акции.", + "infoRSI": "Индекс относительной силы (RSI) был опубликован Дж. Уэллсом Уайлдером. Текущая цена нормируется в процентах от 0 до 100. Flutter_chart_id этого осциллятора вводит в заблуждение, поскольку он не сравнивает инструмент относительно другого инструмента или набора инструментов, а скорее представляет текущую цену относительно других недавних фигур в пределах выбранной длины окна обратного просмотра.", + "infoBB": "Полосы Боллинджера (Bollinger Bands, BB) можно использовать для измерения высоты или низкости цены по отношению к предыдущим сделкам.", + "infoMA": "Скользящая средняя (MA) помогает определить общую тенденцию рынка, отсеивая краткосрочные колебания цен. Используя исторические данные, она рассчитывает среднюю цену за определенный период и наносит линию на график. Если линия MA движется вверх, это индикатор восходящего тренда, если вниз - нисходящего. Сигнал к покупке возникает, когда цена движется выше линии MA.", + "infoMaximumActiveIndicatorsAdded": "Вы добавили максимальное количество активных индикаторов.", + "infoAddSelectedIndicator": "Добавить {indicator}", + "infoAddIndicator": "Добавьте индикатор", + "labelDeleteAllIndicators": "Удалите все индикаторы", + "infoDeleteAllIndicators": "Это удалит все активные индикаторы.", + "infoResetIndicators": "Это вернет настройки индикатора {indicator} по умолчанию.", + "labelDeleteIndicator": "Индикатор удаления {indicator}", + "labelResetIndicator": "Сбросить индикатор {indicator}", + "infoDeleteIndicator": "Вы уверены, что хотите удалить этот индикатор?", + "labelCancel": "Отмена", + "labelDelete": "Удалить", + "labelDeleteAll": "Удалить все", + "infoUpto3indicatorsAllowed": "Допускается до 3 активных индикаторов.", + "infoNoActiveIndicators": "Нет активных индикаторов.", + "labelReset": "Сброс", + "labelApply": "Применить", + "labelOK": "OK", + "labelRSILine": "Линия RSI", + "labelPeriod": "Период", + "labelMinRange": "Минимальный диапазон", + "labelMaxRange": "Максимальный диапазон", + "labelSource": "Источник", + "labelClose": "Закрыть", + "labelOpen": "Открыть", + "labelHigh": "Высокий", + "labelLow": "Низкий", + "labelHl2": "Hl/2", + "labelHlc3": "Hlc/3", + "labelHlcc4": "Hlcc/4", + "labelOhlc4": "Ohlc/4", + "labelShowZones": "Показать зоны", + "labelOverbought": "Перекупленность", + "labelOversold": "Перепроданность", + "labelMinSize": "Минимальный размер", + "labelMaxSize": "Максимальный размер", + "labelRange": "Диапазон", + "labelOverboughtLine": "Линия перекупленности", + "labelOversoldLine": "Линия перепроданности", + "labelMACDLine": "Линия MACD", + "labelFastMAPeriod": "Быстрый период MA", + "labelSlowMAPeriod": "Медленный период MA", + "labelSignalLine": "Сигнальная линия", + "labelSignalPeriod": "Период сигнала", + "labelIncreasingBar": "Увеличивающаяся планка", + "labelDecreasingBar": "Уменьшающаяся планка", + "labelBollingerBandsTop": "Вершина Bollinger Bands", + "labelBollingerBandsMedian": "Медиана полос Боллинджера", + "labelBollingerBandsBottom": "Дно полос Боллинджера", + "labelChannelFill": "Заполнение канала", + "labelFillColor": "Цвет заливки", + "labelStandardDeviations": "Стандартные отклонения", + "labelMovingAverageType": "Тип скользящей средней", + "labelMALine": "Линия MA", + "labelOffset": "Смещение", + "labelType": "Тип", + "labelSimple": "Простой", + "labelExponential": "Экспоненциальный", + "labelWeighted": "Взвешенный", + "labelHull": "Корпус", + "labelZeroLag": "Нулевая задержка", + "labelTimeSeries": "Временные ряды", + "labelWellesWilder": "Уэллс Уайлдер", + "labelVariable": "Переменная", + "labelTriangular": "Треугольный", + "label2Exponential": "2-Экспоненциальный", + "label3Exponential": "3-экспоненциальный", + "warnEnterValueBetweenMinMax": "Введите значение между {min} и {max}.", + "warnRangeMinMax": "Ассортимент {min} - {max}", + "labelDrawingTools": "Инструменты для рисования", + "labelTools": "Инструменты", + "labelLine": "Линия", + "labelRay": "Рэй", + "informTapToSetFirstPoint": "Нажмите, чтобы установить первую точку", + "informTapToSetFinalPoint": "Нажмите, чтобы установить конечную точку", + "informNoActiveDrawingTools": "Нет активных инструментов рисования.", + "actionAddDrawingTool": "Добавить инструмент для рисования", + "labelOf": "из", + "labelDeleteAllDrawingTools": "Удалить все инструменты рисования", + "informDeleteAllDrawingTools": "Это приведет к удалению всех активных инструментов рисования." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_si.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_si.arb new file mode 100644 index 000000000..f8e63dd60 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_si.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "දර්ශක", + "labelActive": "ක්රියාකාරී", + "labelAll": "සියල්ලම", + "labelMomentum": "ගම්යතාව", + "labelVolatility": "අස්ථාවරත්වය", + "labelMovingAverages": "චලනය වන සාමාන්යයන්", + "labelMACD": "මැක්ඩී", + "labelRelativeStrengthIndex": "සාපේක්ෂ ශක්ති දර්ශකය (RSI)", + "labelRSI": "ආර්එස්අයි", + "labelBollingerBands": "බොලින්ගර් බෑන්ඩ් (බීබී)", + "labelBB": "BB", + "labelMovingAverage": "චලනය වන සාමාන්යය (MA)", + "labelMA": "මා", + "infoMACD": "MACD යනු කොටස් මිල ගණන් පිළිබඳ තාක්ෂණික විශ්ලේෂණයේදී භාවිතා කරන වෙළඳ දර්ශකයකි. එය කොටස් මිලෙහි ප්රවණතාවයේ ශක්තිය, දිශාව, ගම්යතාව සහ කාලසීමාවේ වෙනස්කම් හෙළි කිරීමට නියමිතය.", + "infoRSI": "සාපේක්ෂ ශක්ති දර්ශකය (RSI) ප්රකාශයට පත් කරන ලද්දේ ජේ වෙල්ස් වයිල්ඩර් විසිනි. වර්තමාන මිල 0 සහ 100 අතර ප්රතිශතයක් ලෙස සාමාන්යකරණය කර ඇත. මෙම ඔස්කිලේටරයේ flutter_chart_id නොමඟ යන්නේ එය වෙනත් උපකරණයක් හෝ උපකරණ කට්ටලයකට සාපේක්ෂව උපකරණය සංසන්දනය නොකරන නිසා, ඒ වෙනුවට තෝරාගත් lookback කවුළුව දිග තුළ අනෙකුත් මෑත කෑලිවලට සාපේක්ෂව වත්මන් මිල නියෝජනය කරයි.", + "infoBB": "පෙර වෙළඳාම් වලට සාපේක්ෂව මිලෙහි උසස්කම හෝ පහත් බව මැනීම සඳහා බොලින්ගර් බෑන්ඩ් (බීබී) භාවිතා කළ හැකිය.", + "infoMA": "චලනය වන සාමාන්යය (MA) කෙටි කාලීන මිල උච්චාවචනයන් පෙරීම මගින් සමස්ත වෙළඳපල ප්රවණතාව හඳුනා ගැනීමට උපකාරී වේ. ඓතිහාසික දත්ත භාවිතා කරමින්, එය නිශ්චිත කාල පරිච්ඡේදයක් පුරා සාමාන්ය මිල ගණනය කර ප්රස්ථාරයේ රේඛාවක් සකස් කරයි. MA රේඛාව ඉහළට ගමන් කරන්නේ නම් එය uptrend ක දර්ශකයක්, පහළට ගමන් කරන්නේ නම් downtrend එකක්. මිල MA රේඛාවට ඉහළින් ගමන් කරන විට මිලදී ගැනීමේ සංඥාවක් සිදු වේ.", + "infoMaximumActiveIndicatorsAdded": "ඔබ උපරිම ක්රියාකාරී දර්ශක සංඛ්යාව එකතු කර ඇත.", + "infoAddSelectedIndicator": "{indicator}එකතු කරන්න", + "infoAddIndicator": "දර්ශකය එක් කරන්න", + "labelDeleteAllIndicators": "සියලුම දර්ශක මකා දමන්න", + "infoDeleteAllIndicators": "මෙය සියලුම ක්රියාකාරී දර්ශක මකා දමනු ඇත.", + "infoResetIndicators": "මෙය {indicator} දර්ශකය එහි පෙරනිමි සැකසුම් වලට යළි පිහිටුවනු ඇත.", + "labelDeleteIndicator": "{indicator} දර්ශකය මකන්න", + "labelResetIndicator": "{indicator} දර්ශකය නැවත සකසන්න", + "infoDeleteIndicator": "ඔබට මෙම දර්ශකය මකා දැමීමට අවශ්ය බව ඔබට විශ්වාසද?", + "labelCancel": "අවලංගු කරන්න", + "labelDelete": "මකන්න", + "labelDeleteAll": "සියල්ල මකා දමන්න", + "infoUpto3indicatorsAllowed": "ක්රියාකාරී දර්ශක 3 ක් දක්වා අවසර ඇත.", + "infoNoActiveIndicators": "ක්රියාකාරී දර්ශක නොමැත.", + "labelReset": "යළි පිහිටුවන්න", + "labelApply": "අයදුම් කරන්න", + "labelOK": "හරි", + "labelRSILine": "ආර්එස්අයි රේඛාව", + "labelPeriod": "කාල සීමාව", + "labelMinRange": "අවම පරාසය", + "labelMaxRange": "උපරිම පරාසය", + "labelSource": "මූලාශ්රය", + "labelClose": "වසා දමන්න", + "labelOpen": "විවෘත කරන්න", + "labelHigh": "ඉහළ", + "labelLow": "අඩුයි", + "labelHl2": "එච්එල්/2", + "labelHlc3": "එච්එල්සී/3", + "labelHlcc4": "එච්එල්සී/4", + "labelOhlc4": "ඕඑච්එල්සී/4", + "labelShowZones": "කලාප පෙන්වන්න", + "labelOverbought": "අධික ලෙස මිල දී ගනු ලැබේ", + "labelOversold": "අධික ලෙස විකුණා ඇත", + "labelMinSize": "අවම ප්රමාණය", + "labelMaxSize": "උපරිම ප්රමාණය", + "labelRange": "පරාසය", + "labelOverboughtLine": "අධික ලෙස මිලදී ගත් රේඛාව", + "labelOversoldLine": "අධි විකුණුම් රේඛාව", + "labelMACDLine": "MACD රේඛාව", + "labelFastMAPeriod": "වේගවත් MA කාල පරිච්ඡේදය", + "labelSlowMAPeriod": "මන්දගාමී MA කාලය", + "labelSignalLine": "සංඥා රේඛාව", + "labelSignalPeriod": "සංඥා කාලය", + "labelIncreasingBar": "තීරුව වැඩි කිරීම", + "labelDecreasingBar": "බාර් අඩුවීම", + "labelBollingerBandsTop": "බොලින්ගර් බෑන්ඩ් ටොප්", + "labelBollingerBandsMedian": "බොලින්ගර් බෑන්ඩ් මධ්යන්ය", + "labelBollingerBandsBottom": "බොලින්ගර් බෑන්ඩ්ස් පහළ", + "labelChannelFill": "නාලිකා පිරවීම", + "labelFillColor": "වර්ණය පුරවන්න", + "labelStandardDeviations": "සම්මත අපගමනය", + "labelMovingAverageType": "චලනය වන සාමාන්ය වර්ගය", + "labelMALine": "එම්ඒ රේඛාව", + "labelOffset": "ඕෆ්සෙට්", + "labelType": "වර්ගය", + "labelSimple": "සරල", + "labelExponential": "එක්ස්පේන්ෂල්", + "labelWeighted": "බර බර", + "labelHull": "හල්", + "labelZeroLag": "ශුරෝ ලැග්", + "labelTimeSeries": "කාල මාලාව", + "labelWellesWilder": "වෙල්ස් වයිල්ඩර්", + "labelVariable": "විචල්ය", + "labelTriangular": "ත්රිකෝණාකාර", + "label2Exponential": "2-එක්ස්පෙන්ෂනල්", + "label3Exponential": "3-එක්ස්පෙන්ෂනල්", + "warnEnterValueBetweenMinMax": "{min} සහ {max}අතර අගයක් ඇතුළත් කරන්න", + "warnRangeMinMax": "පරාසය {min} - {max}", + "labelDrawingTools": "ඇඳීමේ මෙවලම්", + "labelTools": "මෙවලම්", + "labelLine": "රේඛාව", + "labelRay": "රේ", + "informTapToSetFirstPoint": "පළමු ලක්ෂ්යය සැකසීමට තට්ටු කරන්න", + "informTapToSetFinalPoint": "අවසාන ලක්ෂ්යය සැකසීමට තට්ටු කරන්න", + "informNoActiveDrawingTools": "ක්රියාකාරී ඇඳීමේ මෙවලම් නොමැත.", + "actionAddDrawingTool": "ඇඳීමේ මෙවලම එක් කරන්න", + "labelOf": "හි", + "labelDeleteAllDrawingTools": "සියලුම ඇඳීමේ මෙවලම් මකන්න", + "informDeleteAllDrawingTools": "මෙය සියලුම ක්රියාකාරී ඇඳීමේ මෙවලම් මකා දමනු ඇත." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_sw.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_sw.arb new file mode 100644 index 000000000..692fbd2e4 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_sw.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "Viashiria", + "labelActive": "Inafanya kazi", + "labelAll": "Wote", + "labelMomentum": "Mwendo", + "labelVolatility": "Ubadilishaji", + "labelMovingAverages": "Wastani wa kusonga", + "labelMACD": "MACD", + "labelRelativeStrengthIndex": "Kielelezo cha Nguvu ya Uhusiano (RSI)", + "labelRSI": "RSI", + "labelBollingerBands": "Bendi za Bollinger (BB)", + "labelBB": "BB", + "labelMovingAverage": "Wastani wa kusonga (MA)", + "labelMA": "MA", + "infoMACD": "MACD ni kiashiria cha biashara kinachotumiwa katika uchambuzi wa kiufundi wa bei za hisa. Inapaswa kufichua mabadiliko katika nguvu, mwelekeo, kasi, na muda wa mwenendo wa bei ya hisa.", + "infoRSI": "Fahirisi ya Nguvu ya Relay (RSI) ilichapishwa na J. Welles Wilder. Bei ya sasa imeorodheshwa kama asilimia kati ya 0 na 100. Flutter_chart_id ya oscillator hii inatoa kwa sababu hailinganishi chombo kinachohusiana na chombo kingine au seti ya vyombo, lakini badala yake inawakilisha bei ya sasa ikilinganishwa na vipande vingine vya hivi karibuni ndani ya urefu wa dirisha lililochaguliwa.", + "infoBB": "Bendi za Bollinger (BB) zinaweza kutumika kupima juu au chini wa bei kulinganishwa na biashara zilizopita.", + "infoMA": "Wastani wa Kusonga (MA) husaidia kutambua mwenendo wa jumla wa soko kwa kuchuja mabadiliko ya bei ya muda mfupi. Kutumia data ya kihistoria, inahesabu bei ya wastani kwa kipindi maalum na inapanga mstari kwenye chati. Ikiwa mstari wa MA unasonga juu, ni kiashiria cha kuongezeka, mwenendo wa kushuka ikiwa unasonga chini. Ishara ya ununuzi hutokea wakati bei inapohamia juu ya mstari wa MA.", + "infoMaximumActiveIndicatorsAdded": "Umeongeza idadi kubwa ya viashiria vya kazi.", + "infoAddSelectedIndicator": "Ongeza {indicator}", + "infoAddIndicator": "Ongeza kiashiria", + "labelDeleteAllIndicators": "Futa viashiria vyote", + "infoDeleteAllIndicators": "Hii itafuta viashiria vyote vya kazi.", + "infoResetIndicators": "Hii itaweka upya kiashiria cha {indicator} kwa mipangilio yake ya default.", + "labelDeleteIndicator": "Futa kiashiria cha {indicator}", + "labelResetIndicator": "Weka upya kiashiria cha {indicator}", + "infoDeleteIndicator": "Je! Una hakika unataka kufuta kiashiria hiki?", + "labelCancel": "Ghairi", + "labelDelete": "Futa", + "labelDeleteAll": "Futa Yote", + "infoUpto3indicatorsAllowed": "Hadi viashiria 3 vya kazi zinaruhusiwa.", + "infoNoActiveIndicators": "Hakuna viashiria vya kazi.", + "labelReset": "Weka upya", + "labelApply": "Tumia", + "labelOK": "SAWA", + "labelRSILine": "Mstari wa RSI", + "labelPeriod": "Kipindi", + "labelMinRange": "Kiwango cha chini", + "labelMaxRange": "Kiwango cha juu", + "labelSource": "chanzo", + "labelClose": "Kufunga", + "labelOpen": "Fungua", + "labelHigh": "Juu", + "labelLow": "Ndogo", + "labelHl2": "Hl/2", + "labelHlc3": "HLC/3", + "labelHlcc4": "Hlcc/4", + "labelOhlc4": "Ohlc/4", + "labelShowZones": "Onyesha Maeneo", + "labelOverbought": "Kununua kupita kiasi", + "labelOversold": "Uuzwa kupita kiasi", + "labelMinSize": "Ukubwa wa chini", + "labelMaxSize": "Ukubwa wa juu", + "labelRange": "Mbalimbali", + "labelOverboughtLine": "Mstari wa kununuliwa kupita", + "labelOversoldLine": "Mstari uliouzwa zaidi", + "labelMACDLine": "Mstari wa MACD", + "labelFastMAPeriod": "Kipindi cha haraka cha MA", + "labelSlowMAPeriod": "Kipindi cha polepole cha MA", + "labelSignalLine": "Mstari wa ishara", + "labelSignalPeriod": "Kipindi cha ishara", + "labelIncreasingBar": "Kuongezeka kwa bar", + "labelDecreasingBar": "Baa ya kupungua", + "labelBollingerBandsTop": "Bollinger Bands juu", + "labelBollingerBandsMedian": "Bollinger Bands wastani", + "labelBollingerBandsBottom": "Bandi za Bollinger chini", + "labelChannelFill": "Kujaza kituo", + "labelFillColor": "Jaza rangi", + "labelStandardDeviations": "Upungufu wa kawaida", + "labelMovingAverageType": "Aina ya wastani ya kusonga", + "labelMALine": "Mstari wa MA", + "labelOffset": "Kidhibiti", + "labelType": "Aina", + "labelSimple": "Rahisi", + "labelExponential": "Mwanganyiko", + "labelWeighted": "Uzito", + "labelHull": "Hull", + "labelZeroLag": "Zero Lag", + "labelTimeSeries": "Mfululizo wa Wakati", + "labelWellesWilder": "Welles Wilder", + "labelVariable": "Tofauti", + "labelTriangular": "Pembetatu", + "label2Exponential": "2-Exponential", + "label3Exponential": "3-Mfanyiko", + "warnEnterValueBetweenMinMax": "Ingiza thamani kati ya {min} na {max}", + "warnRangeMinMax": "Kiwango cha {min} - {max}", + "labelDrawingTools": "Zana za kuchora", + "labelTools": "Vyombo", + "labelLine": "Mstari", + "labelRay": "taa", + "informTapToSetFirstPoint": "Gonga ili kuweka hatua ya kwanza", + "informTapToSetFinalPoint": "Gonga ili kuweka hatua ya mwisho", + "informNoActiveDrawingTools": "Hakuna zana zinazofaa za kuchora.", + "actionAddDrawingTool": "Ongeza zana ya kuchora", + "labelOf": "ya", + "labelDeleteAllDrawingTools": "Futa zana zote za kuchora", + "informDeleteAllDrawingTools": "Hii itafuta zana zote za kuchora zinazotumika." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_th.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_th.arb new file mode 100644 index 000000000..b90aa306e --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_th.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "ตัวบ่งชี้", + "labelActive": "ใช้งานอยู่", + "labelAll": "ทั้งหมด", + "labelMomentum": "โมเมนตัม", + "labelVolatility": "ความผันผวน", + "labelMovingAverages": "ค่าเฉลี่ยเคลื่อนที่", + "labelMACD": "MACD", + "labelRelativeStrengthIndex": "ดัชนีความแข็งแรงสัมพัทธ์ (RSI)", + "labelRSI": "RSI", + "labelBollingerBands": "Bollinger Bands (BB)", + "labelBB": "BB", + "labelMovingAverage": "ค่าเฉลี่ยเคลื่อนที่ (MA)", + "labelMA": "MA", + "infoMACD": "MACD เป็นตัวบ่งชี้ที่ใช้ในการวิเคราะห์ทางเทคนิคของราคาหุ้น มีวัตถุประสงค์เพื่อแสดงการเปลี่ยนแปลงในความแข็งแกร่ง ทิศทาง โมเมนตัม และระยะเวลาของแนวโน้มในราคาหุ้น", + "infoRSI": "ดัชนีความแข็งแรงสัมพัทธ์หรือ Relative Strength Index (RSI) ถูกตีพิมพ์โดย J. Welles Wilder ราคาปัจจุบันถูกแปลงเป็นเปอร์เซ็นต์ระหว่าง 0 ถึง 100 ค่า flutter_chart_id ของตัวบ่งชี้นี้อาจทำให้เข้าใจผิดได้ เนื่องจากไม่ได้เปรียบเทียบตราสารอันหนึ่งกับตราสารอื่นหรือกลุ่มสินทรัพย์อื่น แต่จะแสดงราคาปัจจุบันเทียบกับราคาล่าสุดในช่วงระยะเวลาที่เลือกมองย้อนกลับไป", + "infoBB": "Bollinger Bands (BB) สามารถใช้วัดความสูงหรือต่ำของราคาเมื่อเทียบกับการซื้อขายก่อนหน้านี้", + "infoMA": "ค่าเฉลี่ยเคลื่อนที่หรือ Moving Average (MA) ช่วยในการระบุแนวโน้มตลาดโดยรวมโดยการกรองความผันผวนของราคาระยะสั้นออกไป โดยใช้ข้อมูลในอดีตเพื่อคำนวณราคาที่เฉลี่ยในช่วงเวลาที่กำหนดและแสดงเป็นเส้นบนกราฟ หากเส้น MA เคลื่อนที่ขึ้นแสดงถึงแนวโน้มขาขึ้น หากเคลื่อนที่ลงแสดงถึงแนวโน้มขาลง สัญญาณการซื้อเกิดขึ้นเมื่อราคาขยับเหนือเส้น MA", + "infoMaximumActiveIndicatorsAdded": "คุณได้เพิ่มตัวบ่งชี้ที่ใช้งานอยู่จนถึงจำนวนสูงสุดแล้ว", + "infoAddSelectedIndicator": "เพิ่ม {indicator}", + "infoAddIndicator": "เพิ่มตัวบ่งชี้", + "labelDeleteAllIndicators": "ลบตัวบ่งชี้ทั้งหมด", + "infoDeleteAllIndicators": "สิ่งนี้จะลบตัวบ่งชี้ที่ใช้งานอยู่ทั้งหมด", + "infoResetIndicators": "สิ่งนี้จะรีเซ็ตตัวบ่งชี้ {indicator} ไปเป็นตามค่าเริ่มต้นของมัน", + "labelDeleteIndicator": "ลบตัวบ่งชี้ {indicator}", + "labelResetIndicator": "รีเซ็ตตัวบ่งชี้ {indicator}", + "infoDeleteIndicator": "คุณแน่ใจหรือไม่ว่าต้องการลบตัวบ่งชี้นี้?", + "labelCancel": "ยกเลิก", + "labelDelete": "ลบ", + "labelDeleteAll": "ลบทั้งหมด", + "infoUpto3indicatorsAllowed": "อนุญาตให้ใช้ตัวบ่งชี้ที่ใช้งานอยู่สูงสุด 3 ตัว", + "infoNoActiveIndicators": "ไม่มีตัวบ่งชี้ที่ใช้งานอยู่", + "labelReset": "รีเซ็ท", + "labelApply": "ใช้งาน", + "labelOK": "OK", + "labelRSILine": "เส้น RSI", + "labelPeriod": "ระยะเวลา", + "labelMinRange": "ช่วงขั้นต่ำ", + "labelMaxRange": "ช่วงสูงสุด", + "labelSource": "แหล่งที่มา", + "labelClose": "Close", + "labelOpen": "Open", + "labelHigh": "High", + "labelLow": "Low", + "labelHl2": "Hl/2", + "labelHlc3": "Hlc/3", + "labelHlcc4": "Hlcc/4", + "labelOhlc4": "Ohlc/4", + "labelShowZones": "แสดงโซน", + "labelOverbought": "การซื้อมากเกินไป", + "labelOversold": "การขายมากเกินไป", + "labelMinSize": "ขนาดขั้นต่ำ", + "labelMaxSize": "ขนาดสูงสุด", + "labelRange": "ช่วง", + "labelOverboughtLine": "เส้นการซื้อมากเกินไป", + "labelOversoldLine": "เส้นการขายมากเกินไป", + "labelMACDLine": "เส้น MACD", + "labelFastMAPeriod": "ระยะเวลา MA ที่เร็ว", + "labelSlowMAPeriod": "ระยะเวลา MA ที่ช้า", + "labelSignalLine": "เส้นสัญญาณ", + "labelSignalPeriod": "ระยะเวลาสัญญาณ", + "labelIncreasingBar": "แถบที่เพิ่มขึ้น", + "labelDecreasingBar": "แถบที่ลดลง", + "labelBollingerBandsTop": "Bollinger Bands อันบน", + "labelBollingerBandsMedian": "Bollinger Bands อันกลาง", + "labelBollingerBandsBottom": "Bollinger Bands อันล่าง", + "labelChannelFill": "เติม Channel", + "labelFillColor": "เติมสี", + "labelStandardDeviations": "ค่าเบี่ยงเบนมาตรฐาน", + "labelMovingAverageType": "ประเภทค่าเฉลี่ยเคลื่อนที่ (MA)", + "labelMALine": "เส้น MA", + "labelOffset": "ออฟเซ็ท", + "labelType": "ประเภท", + "labelSimple": "Simple", + "labelExponential": "Exponential", + "labelWeighted": "Weighted", + "labelHull": "Hull", + "labelZeroLag": "Zero Lag", + "labelTimeSeries": "ชุดข้อมูลตามลำดับเวลา", + "labelWellesWilder": "Welles Wilder", + "labelVariable": "แบบแปรผัน", + "labelTriangular": "แบบสามเหลี่ยม", + "label2Exponential": "2-เลขชี้กำลัง", + "label3Exponential": "3- เลขชี้กำลัง", + "warnEnterValueBetweenMinMax": "ป้อนค่าระหว่าง {min} และ {max}", + "warnRangeMinMax": "ช่วง {min} - {max}", + "labelDrawingTools": "เครื่องมือวาด", + "labelTools": "เครื่องมือ", + "labelLine": "เส้น", + "labelRay": "รังสี", + "informTapToSetFirstPoint": "แตะเพื่อตั้งค่าจุดแรก", + "informTapToSetFinalPoint": "แตะเพื่อตั้งค่าจุดสุดท้าย", + "informNoActiveDrawingTools": "ไม่มีเครื่องมือวาดภาพที่ใช้งานอยู่", + "actionAddDrawingTool": "เพิ่มเครื่องมือวาดภาพ", + "labelOf": "ของ", + "labelDeleteAllDrawingTools": "ลบเครื่องมือวาดภาพทั้งหมด", + "informDeleteAllDrawingTools": "สิ่งนี้จะลบเครื่องมือวาดภาพที่ใช้งานอยู่ทั้งหมด" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_tr.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_tr.arb new file mode 100644 index 000000000..4ccc21b59 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_tr.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "Göstergeler", + "labelActive": "Aktif", + "labelAll": "Tümü", + "labelMomentum": "Momentum", + "labelVolatility": "Volatilite", + "labelMovingAverages": "Hareketli ortalamalar", + "labelMACD": "MACD", + "labelRelativeStrengthIndex": "Göreceli Güç Endeksi (RSI)", + "labelRSI": "RSI", + "labelBollingerBands": "Bollinger Bantları (BB)", + "labelBB": "BB", + "labelMovingAverage": "Hareketli Ortalama (MA)", + "labelMA": "MA", + "infoMACD": "MACD, hisse senedi fiyatlarının teknik analizinde kullanılan bir alım satım göstergesidir. Bir hisse senedinin fiyatındaki bir trendin gücü, yönü, momentumu ve süresindeki değişiklikleri ortaya çıkarması beklenir.", + "infoRSI": "Göreceli Güç Endeksi (RSI) J. Welles Wilder tarafından yayınlanmıştır. Mevcut fiyat 0 ile 100 arasında bir yüzde olarak normalleştirilir. Bu osilatörün flutter_chart_id'si yanıltıcıdır çünkü enstrümanı başka bir enstrümanla veya enstrüman setiyle karşılaştırmaz, bunun yerine seçilen geriye dönük pencere uzunluğu içindeki diğer son parçalara göre mevcut fiyatı temsil eder.", + "infoBB": "Bollinger Bantları (BB), önceki işlemlere göre fiyatın yüksekliğini veya düşüklüğünü ölçmek için kullanılabilir.", + "infoMA": "Hareketli Ortalama (MA), kısa vadeli fiyat dalgalanmalarını filtreleyerek genel piyasa trendini belirlemeye yardımcı olur. Geçmiş verileri kullanarak belirli bir dönemdeki ortalama fiyatı hesaplar ve grafik üzerinde bir çizgi çizer. MA çizgisi yukarı doğru hareket ederse, bu bir yükseliş trendinin, aşağı doğru hareket ederse bir düşüş trendinin göstergesidir. Fiyat MA çizgisinin üzerine çıktığında bir alım sinyali oluşur.", + "infoMaximumActiveIndicatorsAdded": "Maksimum sayıda aktif gösterge eklediniz.", + "infoAddSelectedIndicator": "{indicator}adresini ekleyin", + "infoAddIndicator": "Gösterge ekleyin", + "labelDeleteAllIndicators": "Tüm göstergeleri sil", + "infoDeleteAllIndicators": "Bu işlem tüm aktif göstergeleri silecektir.", + "infoResetIndicators": "Bu, {indicator} göstergesini varsayılan ayarlarına sıfırlar.", + "labelDeleteIndicator": "Sil {indicator} göstergesi", + "labelResetIndicator": "{indicator} göstergesini sıfırla", + "infoDeleteIndicator": "Bu göstergeyi silmek istediğinizden emin misiniz?", + "labelCancel": "İptal", + "labelDelete": "Silme", + "labelDeleteAll": "Tümünü Sil", + "infoUpto3indicatorsAllowed": "En fazla 3 aktif göstergeye izin verilir.", + "infoNoActiveIndicators": "Aktif gösterge yok.", + "labelReset": "Sıfırla", + "labelApply": "Başvurmak", + "labelOK": "TAMAM.", + "labelRSILine": "RSI çizgisi", + "labelPeriod": "Dönem", + "labelMinRange": "Min aralık", + "labelMaxRange": "Maksimum aralık", + "labelSource": "Kaynak", + "labelClose": "Kapat", + "labelOpen": "Açık", + "labelHigh": "Yüksek", + "labelLow": "Düşük", + "labelHl2": "Hl/2", + "labelHlc3": "Hlc/3", + "labelHlcc4": "Hlcc/4", + "labelOhlc4": "Ohlc/4", + "labelShowZones": "Bölgeleri Göster", + "labelOverbought": "Aşırı alım", + "labelOversold": "Aşırı satım", + "labelMinSize": "Min boyut", + "labelMaxSize": "Maksimum boyut", + "labelRange": "Menzil", + "labelOverboughtLine": "Aşırı alım çizgisi", + "labelOversoldLine": "Aşırı satım çizgisi", + "labelMACDLine": "MACD çizgisi", + "labelFastMAPeriod": "Hızlı MA dönemi", + "labelSlowMAPeriod": "Yavaş MA dönemi", + "labelSignalLine": "Sinyal hattı", + "labelSignalPeriod": "Sinyal süresi", + "labelIncreasingBar": "Artan çubuk", + "labelDecreasingBar": "Azalan çubuk", + "labelBollingerBandsTop": "Bollinger Bantları üst", + "labelBollingerBandsMedian": "Bollinger Bantları medyan", + "labelBollingerBandsBottom": "Bollinger Bantları alt", + "labelChannelFill": "Kanal dolgusu", + "labelFillColor": "Dolgu rengi", + "labelStandardDeviations": "Standart sapmalar", + "labelMovingAverageType": "Hareketli Ortalama Türü", + "labelMALine": "Ana hat", + "labelOffset": "Ofset", + "labelType": "Tip", + "labelSimple": "Basit", + "labelExponential": "Üstel", + "labelWeighted": "Ağırlıklı", + "labelHull": "Gövde", + "labelZeroLag": "Sıfır Gecikme", + "labelTimeSeries": "Zaman Serisi", + "labelWellesWilder": "Welles Wilder", + "labelVariable": "Değişken", + "labelTriangular": "Üçgen", + "label2Exponential": "2-Eksponansiyel", + "label3Exponential": "3-Eksponansiyel", + "warnEnterValueBetweenMinMax": "{min} ile {max}arasında bir değer girin.", + "warnRangeMinMax": "Aralık {min} - {max}", + "labelDrawingTools": "Çizim araçları", + "labelTools": "Araçlar", + "labelLine": "Çizgi", + "labelRay": "ışın", + "informTapToSetFirstPoint": "İlk noktayı ayarlamak için dokunun", + "informTapToSetFinalPoint": "Son noktayı ayarlamak için dokunun", + "informNoActiveDrawingTools": "Etkin çizim aracı yok.", + "actionAddDrawingTool": "Çizim aracı ekle", + "labelOf": "dan", + "labelDeleteAllDrawingTools": "Tüm çizim araçlarını sil", + "informDeleteAllDrawingTools": "Bu, tüm aktif çizim araçlarını silecektir." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_uz.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_uz.arb new file mode 100644 index 000000000..e3de29e5b --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_uz.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "Indicators", + "labelActive": "Active", + "labelAll": "All", + "labelMomentum": "Momentum", + "labelVolatility": "Volatility", + "labelMovingAverages": "Moving averages", + "labelMACD": "MACD", + "labelRelativeStrengthIndex": "Relative Strength Index (RSI)", + "labelRSI": "RSI", + "labelBollingerBands": "Bollinger Bands (BB)", + "labelBB": "BB", + "labelMovingAverage": "Moving Average (MA)", + "labelMA": "MA", + "infoMACD": "MACD is a trading indicator used in technical analysis of stock prices. It is supposed to reveal changes in the strength, direction, momentum, and duration of a trend in a stock's price.", + "infoRSI": "The Relative Strength Index (RSI) was published by J. Welles Wilder. The current price is normalized as a percentage between 0 and 100. The flutter_chart_id of this oscillator is misleading because it does not compare the instrument relative to another instrument or set of instruments, but rather represents the current price relative to other recent pieces within the selected lookback window length.", + "infoBB": "Bollinger Bands (BB) can be used to measure the highness or lowness of the price relative to previous trades.", + "infoMA": "The Moving Average (MA) helps to identify the overall market trend by filtering out short-term price fluctuations. Using historical data, it calculates the average price over a specific period and plots a line on the chart. If the MA line moves upwards, it’s an indicator of an uptrend, a downtrend if it moves downwards. A buy signal occurs when the price moves above the MA line.", + "infoMaximumActiveIndicatorsAdded": "You've added the maximum number of active indicators.", + "infoAddSelectedIndicator": "Add {indicator}", + "infoAddIndicator": "Add indicator", + "labelDeleteAllIndicators": "Delete all indicators", + "infoDeleteAllIndicators": "This will delete all active indicators.", + "infoResetIndicators": "This will reset the {indicator} indicator to its default settings.", + "labelDeleteIndicator": "Delete {indicator} indicator", + "labelResetIndicator": "Reset {indicator} indicator", + "infoDeleteIndicator": "Are you sure you want to delete this indicator?", + "labelCancel": "Cancel", + "labelDelete": "Delete", + "labelDeleteAll": "Delete All", + "infoUpto3indicatorsAllowed": "Up to 3 active indicators allowed.", + "infoNoActiveIndicators": "No active indicators.", + "labelReset": "Reset", + "labelApply": "Apply", + "labelOK": "OK", + "labelRSILine": "RSI line", + "labelPeriod": "Period", + "labelMinRange": "Min range", + "labelMaxRange": "Max range", + "labelSource": "Source", + "labelClose": "Close", + "labelOpen": "Open", + "labelHigh": "High", + "labelLow": "Low", + "labelHl2": "Hl/2", + "labelHlc3": "Hlc/3", + "labelHlcc4": "Hlcc/4", + "labelOhlc4": "Ohlc/4", + "labelShowZones": "Show Zones", + "labelOverbought": "Overbought", + "labelOversold": "Oversold", + "labelMinSize": "Min size", + "labelMaxSize": "Max size", + "labelRange": "Range", + "labelOverboughtLine": "Overbought line", + "labelOversoldLine": "Oversold line", + "labelMACDLine": "MACD line", + "labelFastMAPeriod": "Fast MA period", + "labelSlowMAPeriod": "Slow MA period", + "labelSignalLine": "Signal line", + "labelSignalPeriod": "Signal period", + "labelIncreasingBar": "Increasing bar", + "labelDecreasingBar": "Decreasing bar", + "labelBollingerBandsTop": "Bollinger Bands top", + "labelBollingerBandsMedian": "Bollinger Bands median", + "labelBollingerBandsBottom": "Bollinger Bands bottom", + "labelChannelFill": "Channel fill", + "labelFillColor": "Fill color", + "labelStandardDeviations": "Standard deviations", + "labelMovingAverageType": "Moving Average Type", + "labelMALine": "MA line", + "labelOffset": "Offset", + "labelType": "Type", + "labelSimple": "Simple", + "labelExponential": "Exponential", + "labelWeighted": "Weighted", + "labelHull": "Hull", + "labelZeroLag": "Zero Lag", + "labelTimeSeries": "Time Series", + "labelWellesWilder": "Welles Wilder", + "labelVariable": "Variable", + "labelTriangular": "Triangular", + "label2Exponential": "2-Exponential", + "label3Exponential": "3-Exponential", + "warnEnterValueBetweenMinMax": "Enter a value between {min} and {max}", + "warnRangeMinMax": "Range {min} - {max}", + "labelDrawingTools": "Drawing tools", + "labelTools": "Tools", + "labelLine": "Line", + "labelRay": "Ray", + "informTapToSetFirstPoint": "Tap to set first point", + "informTapToSetFinalPoint": "Tap to set final point", + "informNoActiveDrawingTools": "No active drawing tools.", + "actionAddDrawingTool": "Add drawing tool", + "labelOf": "ning", + "labelDeleteAllDrawingTools": "Barcha chizish vositalarini o'chirish", + "informDeleteAllDrawingTools": "Bu barcha faol chizish vositalarini o'chirib tashlaydi." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_vi.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_vi.arb new file mode 100644 index 000000000..bcad37fd1 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_vi.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "Các chỉ số", + "labelActive": "Hoạt động", + "labelAll": "Tất cả", + "labelMomentum": "Đà", + "labelVolatility": "Có trọng lượng", + "labelMovingAverages": "Đường trung bình động", + "labelMACD": "MACD", + "labelRelativeStrengthIndex": "Chỉ số sức mạnh tương đối (RSI)", + "labelRSI": "RSI", + "labelBollingerBands": "Dải Bollinger (BB)", + "labelBB": "BB", + "labelMovingAverage": "Đường trung bình động (MA)", + "labelMA": "MA", + "infoMACD": "MACD là một chỉ báo giao dịch được sử dụng trong phân tích kỹ thuật giá cổ phiếu. Nó được cho là tiết lộ những thay đổi về sức mạnh, hướng, đà và thời gian của xu hướng trong giá cổ phiếu.", + "infoRSI": "Chỉ số sức mạnh tương đối (RSI) được công bố bởi J. Welles Wilder. Giá hiện tại được chuẩn hóa theo tỷ lệ phần trăm từ 0 đến 100. Flutter_chart_id của bộ dao động này gây hiểu nhầm vì nó không so sánh công cụ so với một công cụ hoặc bộ công cụ khác, mà đại diện cho giá hiện tại so với các phần gần đây khác trong độ dài cửa sổ nhìn lại đã chọn.", + "infoBB": "Dải Bollinger (BB) có thể được sử dụng để đo mức cao hoặc thấp của giá so với các giao dịch trước đó.", + "infoMA": "Đường trung bình động (MA) giúp xác định xu hướng thị trường tổng thể bằng cách lọc ra các biến động giá ngắn hạn. Sử dụng dữ liệu lịch sử, nó tính toán giá trung bình trong một khoảng thời gian cụ thể và vẽ một đường trên biểu đồ. Nếu đường MA di chuyển lên, đó là một chỉ báo của xu hướng tăng, xu hướng giảm nếu nó di chuyển xuống. Tín hiệu mua xảy ra khi giá di chuyển trên đường MA.", + "infoMaximumActiveIndicatorsAdded": "Bạn đã thêm số lượng chỉ báo hoạt động tối đa.", + "infoAddSelectedIndicator": "Thêm {indicator}", + "infoAddIndicator": "Thêm chỉ báo", + "labelDeleteAllIndicators": "Xóa tất cả các chỉ số", + "infoDeleteAllIndicators": "Điều này sẽ xóa tất cả các chỉ số hoạt động.", + "infoResetIndicators": "Thao tác này sẽ đặt lại chỉ báo {indicator} về cài đặt mặc định của nó.", + "labelDeleteIndicator": "Xóa chỉ báo {indicator}", + "labelResetIndicator": "Đặt lại chỉ báo {indicator}", + "infoDeleteIndicator": "Bạn có chắc chắn muốn xóa chỉ báo này không?", + "labelCancel": "Hủy", + "labelDelete": "Xóa", + "labelDeleteAll": "Xóa tất cả", + "infoUpto3indicatorsAllowed": "Cho phép tối đa 3 chỉ số hoạt động.", + "infoNoActiveIndicators": "Không có chỉ số hoạt động.", + "labelReset": "Đặt lại", + "labelApply": "Áp dụng", + "labelOK": "ĐƯỢC", + "labelRSILine": "Dòng RSI", + "labelPeriod": "Thời kỳ", + "labelMinRange": "Phạm vi tối thiểu", + "labelMaxRange": "Phạm vi tối đa", + "labelSource": "Nguồn", + "labelClose": "Đóng", + "labelOpen": "Mở", + "labelHigh": "Cao", + "labelLow": "Thấp", + "labelHl2": "Hl/2", + "labelHlc3": "Hlc/3", + "labelHlcc4": "Hlcc/4", + "labelOhlc4": "Ohlc/4", + "labelShowZones": "Hiển thị khu vực", + "labelOverbought": "Mua quá mức", + "labelOversold": "Bán quá mức", + "labelMinSize": "Kích thước tối thiểu", + "labelMaxSize": "Kích thước tối đa", + "labelRange": "Phạm vi", + "labelOverboughtLine": "Dòng quá mua", + "labelOversoldLine": "Dòng bán quá mức", + "labelMACDLine": "Dòng MACD", + "labelFastMAPeriod": "Thời gian MA nhanh", + "labelSlowMAPeriod": "Thời gian MA chậm", + "labelSignalLine": "Đường tín hiệu", + "labelSignalPeriod": "Thời gian tín hiệu", + "labelIncreasingBar": "Tăng thanh", + "labelDecreasingBar": "Thanh giảm", + "labelBollingerBandsTop": "Bollinger Bands hàng đầu", + "labelBollingerBandsMedian": "Dải Bollinger trung bình", + "labelBollingerBandsBottom": "Dải Bollinger phía dưới", + "labelChannelFill": "Điền kênh", + "labelFillColor": "Tô màu", + "labelStandardDeviations": "Độ lệch chuẩn", + "labelMovingAverageType": "Loại trung bình di chuyển", + "labelMALine": "Dòng MA", + "labelOffset": "Bù đắp", + "labelType": "Kiểu", + "labelSimple": "Đơn giản", + "labelExponential": "Theo cấp số nhân", + "labelWeighted": "Có trọng số", + "labelHull": "Thân tàu", + "labelZeroLag": "Không có độ trễ", + "labelTimeSeries": "Chuỗi thời gian", + "labelWellesWilder": "Welles Wilder", + "labelVariable": "Biến", + "labelTriangular": "Tam giác", + "label2Exponential": "2-cấp số nhân", + "label3Exponential": "3-cấp số nhân", + "warnEnterValueBetweenMinMax": "Nhập giá trị giữa {min} và {max}", + "warnRangeMinMax": "Phạm vi {min} - {max}", + "labelDrawingTools": "Công cụ vẽ", + "labelTools": "Dụng cụ", + "labelLine": "Dòng", + "labelRay": "cá đuối", + "informTapToSetFirstPoint": "Nhấn để đặt điểm đầu tiên", + "informTapToSetFinalPoint": "Nhấn để đặt điểm cuối cùng", + "informNoActiveDrawingTools": "Không có công cụ vẽ hoạt động.", + "actionAddDrawingTool": "Thêm công cụ vẽ", + "labelOf": "của", + "labelDeleteAllDrawingTools": "Xóa tất cả các công cụ vẽ", + "informDeleteAllDrawingTools": "Thao tác này sẽ xóa tất cả các công cụ vẽ đang hoạt động." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_zh-CN.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_zh-CN.arb new file mode 100644 index 000000000..7311d5f49 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_zh-CN.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "指标", + "labelActive": "活跃", + "labelAll": "全部", + "labelMomentum": "动量", + "labelVolatility": "波动率", + "labelMovingAverages": "移动平均线", + "labelMACD": "MACD", + "labelRelativeStrengthIndex": "相对强弱指数 (RSI)", + "labelRSI": "RSI", + "labelBollingerBands": "布林带 (BB)", + "labelBB": "BB", + "labelMovingAverage": "移动平均线 (MA)", + "labelMA": "MA", + "infoMACD": "MACD 是用于股价技术分析的交易指标。可揭示股票价格走势的强度、方向、动量和持续时间的变化。", + "infoRSI": "相对强度指数(RSI)由 J. Welles Wilder 发布。当前价格按介于 0 到 100 之间的百分比进行标准化。该振荡器的 flutter_chart_id 具有误导性,因为它不将该工具与另一种工具或一组工具相比较,而是代表选定回顾窗口长度内相对于其他近期产品的当前价格。", + "infoBB": "布林带(BB)可用于衡量价格相对于先前交易的高点或低点。", + "infoMA": "移动平均线(MA)通过过滤短期价格波动来帮助识别整体市场趋势。它使用历史数据计算特定时期内的平均价格,并在图表上绘线。如果 MA 线向上移动,则表示上升趋势,如果向下移动,则为下降趋势。当价格升至均线上方时,就会出现买入信号。", + "infoMaximumActiveIndicatorsAdded": "已经添加了最大数量的活跃指标。", + "infoAddSelectedIndicator": "添加 {indicator}", + "infoAddIndicator": "添加指标", + "labelDeleteAllIndicators": "删除所有指标", + "infoDeleteAllIndicators": "这将删除所有活跃指标。", + "infoResetIndicators": "这会将{indicator} 指标重置为其默认设置。", + "labelDeleteIndicator": "删除 {indicator} 指标", + "labelResetIndicator": "重置 {indicator} 指标", + "infoDeleteIndicator": "确定要删除此指标吗?", + "labelCancel": "取消", + "labelDelete": "删除", + "labelDeleteAll": "全部删除", + "infoUpto3indicatorsAllowed": "最多允许 3 个活跃指标。", + "infoNoActiveIndicators": "没有活跃指标。", + "labelReset": "重置", + "labelApply": "申请", + "labelOK": "OK", + "labelRSILine": "RSI 线", + "labelPeriod": "周期", + "labelMinRange": "最小范围", + "labelMaxRange": "最大范围", + "labelSource": "来源", + "labelClose": "关闭", + "labelOpen": "打开", + "labelHigh": "High", + "labelLow": "Low", + "labelHl2": "Hl/2", + "labelHlc3": "Hlc/3", + "labelHlcc4": "Hlcc/4", + "labelOhlc4": "Ohlc/4", + "labelShowZones": "显示区域", + "labelOverbought": "超买", + "labelOversold": "超卖", + "labelMinSize": "最小尺寸", + "labelMaxSize": "最大尺寸", + "labelRange": "范围", + "labelOverboughtLine": "超买线", + "labelOversoldLine": "超卖线", + "labelMACDLine": "MACD 线", + "labelFastMAPeriod": "快速均线周期", + "labelSlowMAPeriod": "慢速均线周期", + "labelSignalLine": "信号线", + "labelSignalPeriod": "信号周期", + "labelIncreasingBar": "增加柱线", + "labelDecreasingBar": "减小柱线", + "labelBollingerBandsTop": "布林带顶部", + "labelBollingerBandsMedian": "布林带中位数", + "labelBollingerBandsBottom": "布林带底部", + "labelChannelFill": "频道填充", + "labelFillColor": "填充颜色", + "labelStandardDeviations": "标准偏差", + "labelMovingAverageType": "移动平均线类型", + "labelMALine": "MA 线", + "labelOffset": "抵消", + "labelType": "类型", + "labelSimple": "简单", + "labelExponential": "指数", + "labelWeighted": "加权", + "labelHull": "Hull", + "labelZeroLag": "零延迟", + "labelTimeSeries": "时间序列", + "labelWellesWilder": "Welles Wilder", + "labelVariable": "变量", + "labelTriangular": "三角形", + "label2Exponential": "2-指数", + "label3Exponential": "3-指数", + "warnEnterValueBetweenMinMax": "输入介于 {min} 和 {max} 之间的值", + "warnRangeMinMax": "范围 {min} - {max}", + "labelDrawingTools": "绘图工具", + "labelTools": "工具", + "labelLine": "线", + "labelRay": "射线", + "informTapToSetFirstPoint": "轻触即可设置第一个点", + "informTapToSetFinalPoint": "轻触即可设置终点", + "informNoActiveDrawingTools": "没有有效的绘图工具。", + "actionAddDrawingTool": "添加绘图工具", + "labelOf": "的", + "labelDeleteAllDrawingTools": "删除所有绘图工具", + "informDeleteAllDrawingTools": "这将删除所有活跃的绘图工具。" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_zh-TW.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_zh-TW.arb new file mode 100644 index 000000000..9ee3a010b --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_zh-TW.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "指標", + "labelActive": "活躍", + "labelAll": "全部", + "labelMomentum": "動量", + "labelVolatility": "波動性", + "labelMovingAverages": "移動平均線", + "labelMACD": "MACD", + "labelRelativeStrengthIndex": "相對強度指數(RSI)", + "labelRSI": "RSI", + "labelBollingerBands": "布林格帶(BB)", + "labelBB": "BB", + "labelMovingAverage": "移動平均值 (MA)", + "labelMA": "MA", + "infoMACD": "MACD 是交易指標,用於股票價格的技術分析。可顯示股票價格中趨勢的強度、方向、動量和持續時間的變化。", + "infoRSI": "相對強度指數(RSI)由 J. Welles Wilder 發布。將目前價格以 0 到 100 之間的百分比標準化。此振盪器的 flutter_chart_id 具有誤導性,因為它不與其他工具或一組工具相比較,而是代表所選回顧窗口長度內與其他最近的股票相對的目前價格。", + "infoBB": "布林格帶(BB)可用於測量與之前交易相對於價格的高度或低度。", + "infoMA": "移動平均線(MA)通過過濾短期價格波動來幫助識別整體市場趨勢。它使用歷史資料,計算特定期間的平均價格,並在圖表繪線。如果 MA 線向上移動,則是上升趨勢的指標,如果向下移動則是下降趨勢的指標。當價格走在 MA 線上方時,就會出現買入信號。", + "infoMaximumActiveIndicatorsAdded": "已新增使用中指標數目上限。", + "infoAddSelectedIndicator": "新增 {indicator}", + "infoAddIndicator": "新增指標", + "labelDeleteAllIndicators": "刪除所有指標", + "infoDeleteAllIndicators": "這將刪除所有活躍指標。", + "infoResetIndicators": "這將把 {indicator} 指標重設為其預設設定。", + "labelDeleteIndicator": "刪除 {indicator} 指標", + "labelResetIndicator": "重設 {indicator} 指標", + "infoDeleteIndicator": "確定要刪除此指標嗎?", + "labelCancel": "取消", + "labelDelete": "刪除", + "labelDeleteAll": "刪除全部", + "infoUpto3indicatorsAllowed": "最多允許 3 個活躍指標。", + "infoNoActiveIndicators": "沒有活躍指標。", + "labelReset": "重設", + "labelApply": "申請", + "labelOK": "好", + "labelRSILine": "RSI 線", + "labelPeriod": "週期", + "labelMinRange": "最小範圍", + "labelMaxRange": "最大範圍", + "labelSource": "來源", + "labelClose": "關閉", + "labelOpen": "開啟", + "labelHigh": "High", + "labelLow": "Low", + "labelHl2": "Hl/2", + "labelHlc3": "Hlc/3", + "labelHlcc4": "Hlcc/4", + "labelOhlc4": "Ohlc/4", + "labelShowZones": "顯示區域", + "labelOverbought": "超買", + "labelOversold": "超賣", + "labelMinSize": "最小尺寸", + "labelMaxSize": "最大尺寸", + "labelRange": "範圍", + "labelOverboughtLine": "超買線", + "labelOversoldLine": "超賣線", + "labelMACDLine": "MACD 線", + "labelFastMAPeriod": "快速 MA 週期", + "labelSlowMAPeriod": "慢速 MA 週期", + "labelSignalLine": "信號線", + "labelSignalPeriod": "信號週期", + "labelIncreasingBar": "增加條型線", + "labelDecreasingBar": "減少條型線", + "labelBollingerBandsTop": "布林格帶頂部", + "labelBollingerBandsMedian": "布林格帶中位數", + "labelBollingerBandsBottom": "布林格帶底部", + "labelChannelFill": "通道填充", + "labelFillColor": "填色", + "labelStandardDeviations": "標準偏差", + "labelMovingAverageType": "移動平均線類型", + "labelMALine": "MA 線", + "labelOffset": "偏移", + "labelType": "類型", + "labelSimple": "簡單", + "labelExponential": "指數式", + "labelWeighted": "加權", + "labelHull": "Hull", + "labelZeroLag": "零延遲", + "labelTimeSeries": "時間序列", + "labelWellesWilder": "Welles Wilder", + "labelVariable": "變量", + "labelTriangular": "三角形", + "label2Exponential": "2-指數式", + "label3Exponential": "3-指數式", + "warnEnterValueBetweenMinMax": "輸入介於 {min} 和 {max} 之間的值", + "warnRangeMinMax": "範圍 {min} - {max}", + "labelDrawingTools": "繪圖工具", + "labelTools": "工具", + "labelLine": "線", + "labelRay": "射線", + "informTapToSetFirstPoint": "輕觸以設定第一點", + "informTapToSetFinalPoint": "輕觸以設定最終點", + "informNoActiveDrawingTools": "沒有可用的繪圖工具。", + "actionAddDrawingTool": "新增繪圖工具", + "labelOf": "的", + "labelDeleteAllDrawingTools": "刪除所有繪圖工具", + "informDeleteAllDrawingTools": "這將刪除所有作用中的繪圖工具。" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_zh.arb b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_zh.arb new file mode 100644 index 000000000..9ee3a010b --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_mobile_chart_wrapper/app_zh.arb @@ -0,0 +1,98 @@ +{ + "labelIndicators": "指標", + "labelActive": "活躍", + "labelAll": "全部", + "labelMomentum": "動量", + "labelVolatility": "波動性", + "labelMovingAverages": "移動平均線", + "labelMACD": "MACD", + "labelRelativeStrengthIndex": "相對強度指數(RSI)", + "labelRSI": "RSI", + "labelBollingerBands": "布林格帶(BB)", + "labelBB": "BB", + "labelMovingAverage": "移動平均值 (MA)", + "labelMA": "MA", + "infoMACD": "MACD 是交易指標,用於股票價格的技術分析。可顯示股票價格中趨勢的強度、方向、動量和持續時間的變化。", + "infoRSI": "相對強度指數(RSI)由 J. Welles Wilder 發布。將目前價格以 0 到 100 之間的百分比標準化。此振盪器的 flutter_chart_id 具有誤導性,因為它不與其他工具或一組工具相比較,而是代表所選回顧窗口長度內與其他最近的股票相對的目前價格。", + "infoBB": "布林格帶(BB)可用於測量與之前交易相對於價格的高度或低度。", + "infoMA": "移動平均線(MA)通過過濾短期價格波動來幫助識別整體市場趨勢。它使用歷史資料,計算特定期間的平均價格,並在圖表繪線。如果 MA 線向上移動,則是上升趨勢的指標,如果向下移動則是下降趨勢的指標。當價格走在 MA 線上方時,就會出現買入信號。", + "infoMaximumActiveIndicatorsAdded": "已新增使用中指標數目上限。", + "infoAddSelectedIndicator": "新增 {indicator}", + "infoAddIndicator": "新增指標", + "labelDeleteAllIndicators": "刪除所有指標", + "infoDeleteAllIndicators": "這將刪除所有活躍指標。", + "infoResetIndicators": "這將把 {indicator} 指標重設為其預設設定。", + "labelDeleteIndicator": "刪除 {indicator} 指標", + "labelResetIndicator": "重設 {indicator} 指標", + "infoDeleteIndicator": "確定要刪除此指標嗎?", + "labelCancel": "取消", + "labelDelete": "刪除", + "labelDeleteAll": "刪除全部", + "infoUpto3indicatorsAllowed": "最多允許 3 個活躍指標。", + "infoNoActiveIndicators": "沒有活躍指標。", + "labelReset": "重設", + "labelApply": "申請", + "labelOK": "好", + "labelRSILine": "RSI 線", + "labelPeriod": "週期", + "labelMinRange": "最小範圍", + "labelMaxRange": "最大範圍", + "labelSource": "來源", + "labelClose": "關閉", + "labelOpen": "開啟", + "labelHigh": "High", + "labelLow": "Low", + "labelHl2": "Hl/2", + "labelHlc3": "Hlc/3", + "labelHlcc4": "Hlcc/4", + "labelOhlc4": "Ohlc/4", + "labelShowZones": "顯示區域", + "labelOverbought": "超買", + "labelOversold": "超賣", + "labelMinSize": "最小尺寸", + "labelMaxSize": "最大尺寸", + "labelRange": "範圍", + "labelOverboughtLine": "超買線", + "labelOversoldLine": "超賣線", + "labelMACDLine": "MACD 線", + "labelFastMAPeriod": "快速 MA 週期", + "labelSlowMAPeriod": "慢速 MA 週期", + "labelSignalLine": "信號線", + "labelSignalPeriod": "信號週期", + "labelIncreasingBar": "增加條型線", + "labelDecreasingBar": "減少條型線", + "labelBollingerBandsTop": "布林格帶頂部", + "labelBollingerBandsMedian": "布林格帶中位數", + "labelBollingerBandsBottom": "布林格帶底部", + "labelChannelFill": "通道填充", + "labelFillColor": "填色", + "labelStandardDeviations": "標準偏差", + "labelMovingAverageType": "移動平均線類型", + "labelMALine": "MA 線", + "labelOffset": "偏移", + "labelType": "類型", + "labelSimple": "簡單", + "labelExponential": "指數式", + "labelWeighted": "加權", + "labelHull": "Hull", + "labelZeroLag": "零延遲", + "labelTimeSeries": "時間序列", + "labelWellesWilder": "Welles Wilder", + "labelVariable": "變量", + "labelTriangular": "三角形", + "label2Exponential": "2-指數式", + "label3Exponential": "3-指數式", + "warnEnterValueBetweenMinMax": "輸入介於 {min} 和 {max} 之間的值", + "warnRangeMinMax": "範圍 {min} - {max}", + "labelDrawingTools": "繪圖工具", + "labelTools": "工具", + "labelLine": "線", + "labelRay": "射線", + "informTapToSetFirstPoint": "輕觸以設定第一點", + "informTapToSetFinalPoint": "輕觸以設定最終點", + "informNoActiveDrawingTools": "沒有可用的繪圖工具。", + "actionAddDrawingTool": "新增繪圖工具", + "labelOf": "的", + "labelDeleteAllDrawingTools": "刪除所有繪圖工具", + "informDeleteAllDrawingTools": "這將刪除所有作用中的繪圖工具。" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_ar.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_ar.arb new file mode 100644 index 000000000..a8fcb379d --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_ar.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "النجاح!", + "passkeyCreatedSuccessMessage": "تم تأمين حسابك الآن باستخدام Passkey. قم بإدارة Passkey الخاص بك من خلال إعدادات حساب {platformName} الخاص بك.", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "استمر", + "unexpectedError": "حدث خطأ غير متوقع!", + "unexpectedErrorDescription": "يرجى المحاولة لاحقا.", + "ok": "Ok", + "experienceSaferLogins": "اختبر عمليات تسجيل دخول أكثر أمانًا", + "enhanceSecurity": "الأمان المحسّن على بُعد نقرة واحدة فقط.", + "here": "هنا", + "effortlessLogin": "تسجيل الدخول بسهولة باستخدام Passkeys", + "whatArePasskeys": "ما هي passkeys؟", + "whatArePasskeysDescriptionPoint1": "بديل آمن لكلمات المرور.", + "whatArePasskeysDescriptionPoint2": "قم بإلغاء قفل حسابك مثل هاتفك - باستخدام القياسات الحيوية أو مسح الوجه أو رقم التعريف الشخصي.", + "whyPasskeys": "لماذا Passkey؟", + "whyPasskeysDescription1": "طبقة أمان إضافية.", + "whyPasskeysDescription2": "الحماية من الوصول غير المصرح به والتصيد الاحتيالي.", + "howToCreatePasskey": "كيفية إنشاء passkey؟", + "howToCreatePasskeyDescription1": "انتقل إلى \"إعدادات الحساب\" على المشتقات.", + "howToCreatePasskeyDescription2": "يمكنك إنشاء Passkey واحد لكل جهاز.", + "p2pHowToCreatePasskey": "كيفية إنشاء مفتاح مرور؟", + "p2pHowToCreatePasskeyDescription1": "انتقل إلى «الملف الشخصي» في تطبيق Deriv P2P الخاص بك.", + "p2pHowToCreatePasskeyDescription2": "انقر فوق «مفاتيح المرور» لإنشاء مفتاح المرور الخاص بك.", + "whereArePasskeysSaved": "أين يتم حفظ passkeys؟", + "whereArePasskeysSavedDescriptionAndroid": "أندرويد: مدير كلمات مرور Google.", + "whereArePasskeysSavedDescriptionIOS": "iOS: سلسلة مفاتيح iCloud.", + "whatHappensIfEmailChanged": "ماذا يحدث إذا تم تغيير البريد الإلكتروني لحساب Deriv الخاص بي؟", + "whatHappensIfEmailChangedDescription1": "لا توجد مشكلة! لا يزال مفتاح Passkey بك يعمل.", + "whatHappensIfEmailChangedDescription2": "سجّل الدخول إلى Deriv باستخدام مفتاح Passkey لديك.", + "tips": "نصائح", + "beforeUsingPasskeys": "قبل استخدام Passkey", + "enableScreenLock": "قم بتمكين قفل الشاشة على جهازك.", + "signInGoogleOrIcloud": "قم بتسجيل الدخول إلى حساب Google أو iCloud الخاص بك.", + "enableBluetooth": "قم بتمكين البلوتوث.", + "noPasskeyFound": "لم يتم العثور على passkey!", + "noPasskeyFoundDescription": "يرجى إنشاء passkey لاستخدام هذه الميزة.", + "maybeLater": "ربما في وقت لاحق", + "effortlessLoginWithPasskeys": "تسجيل الدخول بسهولة باستخدام Passkeys", + "learnMoreAboutPasskeys": "تعرف على المزيد حول passkeys", + "noNeedToRememberPassword": "لا حاجة لتذكر كلمة المرور", + "useYourBiometrics": "أمان محسّن باستخدام القياسات الحيوية أو قفل الشاشة", + "syncAcrossDevices": "المزامنة عبر الأجهزة", + "createPasskey": "إنشاء Passkey", + "unsupportedPlatform": "منصة غير مدعومة", + "storedOn": "تم تخزينها على", + "lastUsed": "آخر استخدام", + "rename": "إعادة تسمية", + "revoke": "إلغاء", + "continueTradingButtonText": "استمر في التداول", + "addMorePasskeysButtonText": "أضف المزيد من passkeys", + "unableToSetupPasskey": "تعذر إعداد مفتاح Passkey", + "unableToSetupPasskeyDescription": "لقد واجهنا مشكلة أثناء إعداد Passkey الخاص بك. ربما تمت مقاطعة العملية، أو ربما انتهت مهلة الجلسة. يرجى المحاولة مرة أخرى.", + "passkeysOffErrorTitle": "خدمة Passkeys غير متوفرة", + "never": "أبداً", + "unable_to_process_your_request": "غير قادر على معالجة طلبك", + "unable_to_process_your_request_description": "نحن نواجه مشكلة مؤقتة في معالجة طلبك. يرجى المحاولة مرة أخرى لاحقًا." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_bn.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_bn.arb new file mode 100644 index 000000000..a6c54216d --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_bn.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "সফলতা!", + "passkeyCreatedSuccessMessage": "আপনার অ্যাকাউন্ট এখন একটি পাসকি দিয়ে সুরক্ষিত৷ আপনার {platformName} অ্যাকাউন্ট সেটিংসের মাধ্যমে আপনার পাসকি পরিচালনা করুন।", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "চালিয়ে যান", + "unexpectedError": "একটি অপ্রত্যাশিত ত্রুটি ঘটেছে!", + "unexpectedErrorDescription": "দয়া করে পরে চেষ্টা করুন।", + "ok": "Ok", + "experienceSaferLogins": "নিরাপদ লগইন অভিজ্ঞতা", + "enhanceSecurity": "উন্নত সুরক্ষা মাত্র এক ট্যাপ দূরে।", + "here": "এখানে", + "effortlessLogin": "Passkeys সাথে অনায়াসে লগইন করুন", + "whatArePasskeys": "Passkeys কী কী?", + "whatArePasskeysDescriptionPoint1": "পাসওয়ার্ডের সুরক্ষিত বিকল্প।", + "whatArePasskeysDescriptionPoint2": "আপনার ফোনের মতো আপনার অ্যাকাউন্টটি আনলক করুন - বায়োমেট্রিক্স, ফেস স্ক্যান বা পিন সহ।", + "whyPasskeys": "Passkeys কেন?", + "whyPasskeysDescription1": "অতিরিক্ত সুরক্ষা স্তর।", + "whyPasskeysDescription2": "অননুমোদিত অ্যাক্সেস এবং ফিশিং বিরুদ্ধে শিল্ড।", + "howToCreatePasskey": "Passkey একটি পাসকি তৈরি করবেন?", + "howToCreatePasskeyDescription1": "Deriv 'অ্যাকাউন্ট সেটিংস' এ যান।", + "howToCreatePasskeyDescription2": "Passkey প্রতি ডিভাইসে একটি পাসকি তৈরি করতে পারেন।", + "p2pHowToCreatePasskey": "কিভাবে পাসকি তৈরি করবেন?", + "p2pHowToCreatePasskeyDescription1": "আপনার ডেরিভ পি 2 পি অ্যাপের 'প্রোফাইল' এ যান।", + "p2pHowToCreatePasskeyDescription2": "আপনার পাসকি তৈরি করতে 'পাসকিস' এ আলতো চাপুন।", + "whereArePasskeysSaved": "Passkeys কোথায় সংরক্ষণ করা হয়?", + "whereArePasskeysSavedDescriptionAndroid": "Android: Google পাসওয়ার্ড ম্যানেজার।", + "whereArePasskeysSavedDescriptionIOS": "iOS: iCloud কীচেন।", + "whatHappensIfEmailChanged": "আমার Deriv অ্যাকাউন্ট ইমেইল পরিবর্তন হলে কি হবে?", + "whatHappensIfEmailChangedDescription1": "কোন সমস্যা নেই! আপনার Passkey এখনও কাজ করে।", + "whatHappensIfEmailChangedDescription2": "আপনার বিদ্যমান Passkey দিয়ে Deriv সাইন ইন করুন।", + "tips": "টিপস", + "beforeUsingPasskeys": "Passkeys ব্যবহারের পূর্বে", + "enableScreenLock": "আপনার ডিভাইসে স্ক্রিন লক সক্ষম করুন।", + "signInGoogleOrIcloud": "আপনার Google বা iCloud অ্যাকাউন্টে সাইন ইন করুন।", + "enableBluetooth": "ব্লুটুথ সক্ষম করুন।", + "noPasskeyFound": "কোনো Passkey পাওয়া যায়নি!", + "noPasskeyFoundDescription": "এই বৈশিষ্ট্যটি ব্যবহার করতে অনুগ্রহ করে একটি Passkey তৈরি করুন।", + "maybeLater": "হয়তো পরে", + "effortlessLoginWithPasskeys": "Passkeys সাথে অনায়াসে লগইন করুন", + "learnMoreAboutPasskeys": "Passkeys সম্পর্কে আরও জানুন", + "noNeedToRememberPassword": "পাসওয়ার্ড স্মরণে রাখার দরকার নাই", + "useYourBiometrics": "বায়োমেট্রিক্স বা স্ক্রিন লক সহ উন্নত সুরক্ষা ", + "syncAcrossDevices": "ডিভাইস জুড়ে সিঙ্ক", + "createPasskey": "Passkey তৈরি", + "unsupportedPlatform": "সমর্থিত প্ল্যাটফর্", + "storedOn": "সংরক্ষণ হয়েছে", + "lastUsed": "সর্বশেষ ব্যবহৃত", + "rename": "পুনঃনামকরণ", + "revoke": "প্রত্যাহার করুন", + "continueTradingButtonText": "ট্রেডিং চালিয়ে যান", + "addMorePasskeysButtonText": "আরও Passkeys", + "unableToSetupPasskey": "Passkey সেটআপ করতে অক্ষম", + "unableToSetupPasskeyDescription": "আপনার Passkey সেট আপ করার সময় আমরা একটি সমস্যার মুখোমুখি হয়েছি। প্রক্রিয়াটি বাধা দেওয়া হতে পারে, বা সেশনের সময় শেষ হতে পারে। দয়া করে আবার চেষ্টা করুন।", + "passkeysOffErrorTitle": "Passkeys পরিষেবাটি উপলব্ধ নয়", + "never": "কখনই", + "unable_to_process_your_request": "আপনার অনুরোধ প্রক্রিয়া করতে অক্ষম", + "unable_to_process_your_request_description": "আমরা আপনার অনুরোধ প্রক্রিয়াকরণে একটি অস্থায়ী সমস্যা অনুভব করছি। দয়া করে পরে আবার চেষ্টা করুন।" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_de.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_de.arb new file mode 100644 index 000000000..8531e26ed --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_de.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "Erfolg!", + "passkeyCreatedSuccessMessage": "Ihr Konto ist jetzt mit einem Passkey gesichert. Verwalte deinen Passkey in deinen {platformName} -Kontoeinstellungen.", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "Weiter", + "unexpectedError": "Ein unerwarteter Fehler ist aufgetreten!", + "unexpectedErrorDescription": "Bitte versuchen Sie es später erneut.", + "ok": "Ok", + "experienceSaferLogins": "Erleben Sie sicherere Logins", + "enhanceSecurity": "Verbesserte Sicherheit ist nur einen Fingertipp entfernt.", + "here": "hier", + "effortlessLogin": "Müheloses Einloggen mit Passkeys", + "whatArePasskeys": "Was sind Passkeys? ", + "whatArePasskeysDescriptionPoint1": "Sichere Alternative zu Passwörtern.", + "whatArePasskeysDescriptionPoint2": "Entsperren Sie Ihr Konto wie Ihr Telefon - mit biometrischen Daten, Gesichtsscan oder PIN.", + "whyPasskeys": "Warum Passkeys?", + "whyPasskeysDescription1": "Zusätzliche Sicherheitsebene.", + "whyPasskeysDescription2": "Schützt vor unbefugtem Zugriff und Phishing.", + "howToCreatePasskey": "Wie erstelle ich einen Passkey?", + "howToCreatePasskeyDescription1": "Gehen Sie zu 'Kontoeinstellungen' auf Deriv.", + "howToCreatePasskeyDescription2": "Sie können einen Passkey pro Gerät erstellen.", + "p2pHowToCreatePasskey": "Wie erstelle ich einen Passkey?", + "p2pHowToCreatePasskeyDescription1": "Gehen Sie in Ihrer Deriv P2P-App zu „Profil“.", + "p2pHowToCreatePasskeyDescription2": "Tippen Sie auf „Passkeys“, um Ihren Passkey zu erstellen.", + "whereArePasskeysSaved": "Wo werden die Passkeys gespeichert?", + "whereArePasskeysSavedDescriptionAndroid": "Android: Google Passwort-Manager.", + "whereArePasskeysSavedDescriptionIOS": "iOS: iCloud-Schlüsselbund.", + "whatHappensIfEmailChanged": "Was passiert, wenn die E-Mail-Adresse meines Deriv-Kontos geändert wird?", + "whatHappensIfEmailChangedDescription1": "Kein Problem! Ihr Passkey funktioniert noch.", + "whatHappensIfEmailChangedDescription2": "Melden Sie sich bei Deriv mit Ihrem bestehenden Passkey an.", + "tips": "Tipps", + "beforeUsingPasskeys": "Vor der Benutzung von Passkeys", + "enableScreenLock": "Aktiviere die Bildschirmsperre auf deinem Gerät.", + "signInGoogleOrIcloud": "Melde dich bei deinem Google- oder iCloud-Konto an.", + "enableBluetooth": "Aktiviere Bluetooth.", + "noPasskeyFound": "Kein Passkey gefunden!", + "noPasskeyFoundDescription": "Bitte erstellen Sie einen Passkey, um diese Funktion zu nutzen.", + "maybeLater": "Vielleicht später", + "effortlessLoginWithPasskeys": "Müheloses Einloggen mit Passkeys", + "learnMoreAboutPasskeys": "Erfahren Sie mehr über Passkeys", + "noNeedToRememberPassword": "Sie müssen sich kein Passwort merken", + "useYourBiometrics": "Erhöhte Sicherheit durch Biometrie oder Bildschirmsperre", + "syncAcrossDevices": "Geräteübergreifend synchronisieren", + "createPasskey": "Passkeyerstellen", + "unsupportedPlatform": "Plattform wird nicht unterstützt", + "storedOn": "Gespeichert am", + "lastUsed": "Zuletzt verwendet", + "rename": "Umbenennen", + "revoke": "Widerrufen", + "continueTradingButtonText": "Handel fortsetzen", + "addMorePasskeysButtonText": "Mehr Passkeys", + "unableToSetupPasskey": "Passkey kann nicht eingerichtet werden", + "unableToSetupPasskeyDescription": "Beim Einrichten Ihres Passkey ist ein Problem aufgetreten. Der Vorgang wurde möglicherweise unterbrochen oder die Sitzung wurde unterbrochen. Bitte versuchen Sie es erneut.", + "passkeysOffErrorTitle": "Der Dienst Passkeys ist nicht verfügbar", + "never": "Niemals", + "unable_to_process_your_request": "Ihre Anfrage konnte nicht bearbeitet werden", + "unable_to_process_your_request_description": "Wir haben ein vorübergehendes Problem bei der Bearbeitung Ihrer Anfrage. Bitte versuchen Sie es später erneut." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_en.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_en.arb new file mode 100644 index 000000000..6d5aa4b6c --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_en.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "Success!", + "passkeyCreatedSuccessMessage": "Your account is now secured with a passkey. Manage your passkey through your {platformName} account settings.", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "Continue", + "unexpectedError": "An unexpected error occurred!", + "unexpectedErrorDescription": "Please try again later.", + "ok": "Ok", + "experienceSaferLogins": "Experience safer logins", + "enhanceSecurity": "Enhanced security is just a tap away.", + "here": "here", + "effortlessLogin": "Effortless login with passkeys", + "whatArePasskeys": "What are passkeys?", + "whatArePasskeysDescriptionPoint1": "Secure alternative to passwords.", + "whatArePasskeysDescriptionPoint2": "Unlock your account like your phone - with biometrics, face scan or PIN.", + "whyPasskeys": "Why passkeys?", + "whyPasskeysDescription1": "Extra security layer.", + "whyPasskeysDescription2": "Shields against unauthorised access and phishing.", + "howToCreatePasskey": "How to create a passkey?", + "howToCreatePasskeyDescription1": "Go to ‘Account Settings’ on Deriv.", + "howToCreatePasskeyDescription2": "You can create one passkey per device.", + "p2pHowToCreatePasskey": "How to create passkey?", + "p2pHowToCreatePasskeyDescription1": "Go to ‘Profile‘ in your Deriv P2P app.", + "p2pHowToCreatePasskeyDescription2": "Tap ‘Passkeys‘ to create your passkey.", + "whereArePasskeysSaved": "Where are passkeys saved?", + "whereArePasskeysSavedDescriptionAndroid": "Android: Google password manager.", + "whereArePasskeysSavedDescriptionIOS": "iOS: iCloud keychain.", + "whatHappensIfEmailChanged": "What happens if my Deriv account email is changed?", + "whatHappensIfEmailChangedDescription1": "No problem! Your passkey still works.", + "whatHappensIfEmailChangedDescription2": "Sign in to Deriv with your existing passkey.", + "tips": "Tips", + "beforeUsingPasskeys": "Before using passkeys", + "enableScreenLock": "Enable screen lock on your device.", + "signInGoogleOrIcloud": "Sign in to your Google or iCloud account.", + "enableBluetooth": "Enable Bluetooth.", + "noPasskeyFound": "No passkey found!", + "noPasskeyFoundDescription": "Please create a passkey to use this feature.", + "maybeLater": "Maybe later", + "effortlessLoginWithPasskeys": "Effortless login with passkeys", + "learnMoreAboutPasskeys": "Learn more about passkeys", + "noNeedToRememberPassword": "No need to remember a password", + "useYourBiometrics": "Enhanced security with biometrics or screen lock", + "syncAcrossDevices": "Sync across devices", + "createPasskey": "Create passkey", + "unsupportedPlatform": "Unsupported Platform", + "storedOn": "Stored on", + "lastUsed": "Last used", + "rename": "Rename", + "revoke": "Revoke", + "continueTradingButtonText": "Continue trading", + "addMorePasskeysButtonText": "Add more passkeys", + "unableToSetupPasskey": "Unable to setup passkey", + "unableToSetupPasskeyDescription": "We encountered an issue while setting up your passkey. The process might have been interrupted, or the session timed out. Please try again.", + "passkeysOffErrorTitle": "The Passkeys service is unavailable", + "never": "Never", + "unable_to_process_your_request": "Unable to process your request", + "unable_to_process_your_request_description": "We’re experiencing a temporary issue in processing your request. Please try again later." +} diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_es.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_es.arb new file mode 100644 index 000000000..6e38a39d4 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_es.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "¡Exitoso!", + "passkeyCreatedSuccessMessage": "Su cuenta está ahora protegida con una passkey. Gestione su passkey a través de la configuración de su cuenta {platformName}.", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "Continuar", + "unexpectedError": "Ha ocurrido un error inesperado!", + "unexpectedErrorDescription": "Por favor, inténtelo más tarde.", + "ok": "Ok", + "experienceSaferLogins": "Experimente inicios de sesión más seguros", + "enhanceSecurity": "La seguridad mejorada está a sólo un toque de distancia.", + "here": "aquí", + "effortlessLogin": "Inicio de sesión sin esfuerzo con passkeys", + "whatArePasskeys": "¿Qué son las passkeys?", + "whatArePasskeysDescriptionPoint1": "Alternativa segura a las contraseñas.", + "whatArePasskeysDescriptionPoint2": "Desbloquee su cuenta como si fuera su teléfono: con biometría, escáner facial o PIN.", + "whyPasskeys": "¿Por qué passkeys?", + "whyPasskeysDescription1": "Capa de seguridad adicional.", + "whyPasskeysDescription2": "Protege contra el acceso no autorizado y la suplantación de identidad.", + "howToCreatePasskey": "¿Cómo crear una passkey?", + "howToCreatePasskeyDescription1": "Vaya a \"Configuración de la cuenta\" en Deriv.", + "howToCreatePasskeyDescription2": "Puede crear una passkey por dispositivo.", + "p2pHowToCreatePasskey": "¿Cómo crear una clave de paso?", + "p2pHowToCreatePasskeyDescription1": "Ir a «Perfil» en tu aplicación Deriv P2P.", + "p2pHowToCreatePasskeyDescription2": "Pulsa «Claves de paso» para crear tu clave de paso.", + "whereArePasskeysSaved": "¿Dónde se guardan las passkeys?", + "whereArePasskeysSavedDescriptionAndroid": "Android: Gestor de contraseñas de Google.", + "whereArePasskeysSavedDescriptionIOS": "iOS: Llavero de iCloud.", + "whatHappensIfEmailChanged": "¿Qué ocurre si se cambia el correo electrónico de mi cuenta de Deriv?", + "whatHappensIfEmailChangedDescription1": "No hay problema. Su passkey sigue funcionando.", + "whatHappensIfEmailChangedDescription2": "Inicie sesión en Deriv con su passkey actual.", + "tips": "Consejos", + "beforeUsingPasskeys": "Antes de usar la passkey:", + "enableScreenLock": "Habilite el bloqueo de pantalla en su dispositivo.", + "signInGoogleOrIcloud": "Inicie sesión en su cuenta de Google o iCloud.", + "enableBluetooth": "Activar Bluetooth.", + "noPasskeyFound": "¡No se encontró ninguna passkey!", + "noPasskeyFoundDescription": "Cree una passkey para usar esta función.", + "maybeLater": "Quizás más tarde", + "effortlessLoginWithPasskeys": "Inicio de sesión sin esfuerzo con passkeys", + "learnMoreAboutPasskeys": "Obtenga más información sobre las passkeys", + "noNeedToRememberPassword": "No es necesario recordar una contraseña", + "useYourBiometrics": "Seguridad mejorada con biometría o bloqueo de pantalla", + "syncAcrossDevices": "Sincronización entre dispositivos", + "createPasskey": "Crear passkey", + "unsupportedPlatform": "Plataforma no compatible", + "storedOn": "Almacenado en", + "lastUsed": "Utilizado por última vez", + "rename": "Renombrar", + "revoke": "Revocar", + "continueTradingButtonText": "Continúe operando", + "addMorePasskeysButtonText": "Añadir más Passkeys", + "unableToSetupPasskey": "No se puede configurar la passkey", + "unableToSetupPasskeyDescription": "Hemos encontrado un problema al configurar su passkey. Es posible que el proceso se haya interrumpido o que la sesión haya expirado. Por favor, inténtelo de nuevo.", + "passkeysOffErrorTitle": "El servicio Passkeys no está disponible", + "never": "Nunca", + "unable_to_process_your_request": "No se puede procesar su solicitud", + "unable_to_process_your_request_description": "Estamos teniendo un problema temporal al procesar tu solicitud. Vuelva a intentarlo más tarde." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_fr.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_fr.arb new file mode 100644 index 000000000..8587cdec4 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_fr.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "Effectué !", + "passkeyCreatedSuccessMessage": "Votre compte est désormais sécurisé par une passkey. Gérez votre passkey à partir de vos paramètres de compte {platformName}.", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "Continuer", + "unexpectedError": "Une erreur inattendue s'est produite !", + "unexpectedErrorDescription": "Veuillez réessayer plus tard.", + "ok": "Ok", + "experienceSaferLogins": "Bénéficiez de connexions plus sûres", + "enhanceSecurity": "Une sécurité renforcée est à portée de main.", + "here": "ici", + "effortlessLogin": "Connexion facile à l'aide de passkeys", + "whatArePasskeys": "Que sont les passkeys ?", + "whatArePasskeysDescriptionPoint1": "Alternative sécurisée aux mots de passe.", + "whatArePasskeysDescriptionPoint2": "Déverrouillez votre compte comme votre téléphone - par biométrie, scan du visage ou code PIN.", + "whyPasskeys": "Pourquoi choisir des passkeys ?", + "whyPasskeysDescription1": "Couche de sécurité supplémentaire.", + "whyPasskeysDescription2": "Protège contre les accès non autorisés et le phishing.", + "howToCreatePasskey": "Comment créer une passkey ?", + "howToCreatePasskeyDescription1": "Allez dans \"Paramètres du compte\" sur Deriv.", + "howToCreatePasskeyDescription2": "Vous pouvez créer une Passkey par appareil.", + "p2pHowToCreatePasskey": "Comment créer une clé d'accès ?", + "p2pHowToCreatePasskeyDescription1": "Accéder à « Profil » dans votre application Deriv P2P.", + "p2pHowToCreatePasskeyDescription2": "Appuyez sur « Clés d'accès » pour créer votre clé d'accès.", + "whereArePasskeysSaved": "Où sont enregistrées les passkeys ?", + "whereArePasskeysSavedDescriptionAndroid": "Android : Gestionnaire de mots de passe Google.", + "whereArePasskeysSavedDescriptionIOS": "iOS : trousseau iCloud.", + "whatHappensIfEmailChanged": "Que se passe-t-il si l'adresse e-mail de mon compte Deriv est modifiée ?", + "whatHappensIfEmailChangedDescription1": "Pas de problème ! Votre Passkey fonctionne toujours.", + "whatHappensIfEmailChangedDescription2": "Connectez-vous à Deriv avec votre Passkey existant.", + "tips": "Conseils", + "beforeUsingPasskeys": "Avant d'utiliser la passkey :", + "enableScreenLock": "Activez le verrouillage de l'écran sur votre appareil.", + "signInGoogleOrIcloud": "Connectez-vous à votre compte Google ou iCloud.", + "enableBluetooth": "Activer Bluetooth.", + "noPasskeyFound": "Aucun passkey n'a été trouvé !", + "noPasskeyFoundDescription": "Veuillez créer une passkey pour utiliser cette fonctionnalité.", + "maybeLater": "Peut-être plus tard", + "effortlessLoginWithPasskeys": "Connexion facile à l'aide de passkeys", + "learnMoreAboutPasskeys": "En savoir plus sur les passkeys", + "noNeedToRememberPassword": "Pas besoin de mémoriser un mot de passe", + "useYourBiometrics": "Sécurité renforcée grâce à la biométrie ou au verrouillage de l'écran", + "syncAcrossDevices": "Synchronisation sur tous les appareils", + "createPasskey": "Créer une passkey", + "unsupportedPlatform": "Plateforme non prise en charge", + "storedOn": "Stocké sur ", + "lastUsed": "Dernière utilisation", + "rename": "Renommer", + "revoke": "Révoquer", + "continueTradingButtonText": "Poursuivre les opérations de trading", + "addMorePasskeysButtonText": "Ajouter plus de Passkeys", + "unableToSetupPasskey": "Impossible de configurer la Passkey", + "unableToSetupPasskeyDescription": "Nous avons rencontré un problème lors de l'établissement de votre Passkey. Il se peut que le processus ait été interrompu ou que la session ait expiré. Veuillez réessayer.", + "passkeysOffErrorTitle": "Le service Passkeys est indisponible", + "never": "Jamais", + "unable_to_process_your_request": "Impossible de traiter votre demande", + "unable_to_process_your_request_description": "Nous rencontrons un problème temporaire lors du traitement de votre demande. Veuillez réessayer ultérieurement." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_it.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_it.arb new file mode 100644 index 000000000..fef533b5a --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_it.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "Fatto!", + "passkeyCreatedSuccessMessage": "Il tuo account è ora protetto con una passkey. Gestisci la tua passkey tramite le impostazioni del tuo account {platformName}.", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "Continua", + "unexpectedError": "Si è verificato un errore imprevisto!", + "unexpectedErrorDescription": "Riprova più tardi.", + "ok": "Ok", + "experienceSaferLogins": "Sperimenta accessi più sicuri", + "enhanceSecurity": "Per una maggiore sicurezza basta un tocco.", + "here": "qui", + "effortlessLogin": "Accesso semplice con passkeys", + "whatArePasskeys": "Cosa sono le passkey?", + "whatArePasskeysDescriptionPoint1": "Un'alternativa sicura alle password.", + "whatArePasskeysDescriptionPoint2": "Sblocca il suo conto come il suo telefono - con la biometria, la scansione del volto o il PIN.", + "whyPasskeys": "Perché la passkey?", + "whyPasskeysDescription1": "Un ulteriore livello di sicurezza.", + "whyPasskeysDescription2": "Protegge dagli accessi non autorizzati e dal phishing.", + "howToCreatePasskey": "Come creare una passkey?", + "howToCreatePasskeyDescription1": "Vada a 'Impostazioni del conto' su Deriv.", + "howToCreatePasskeyDescription2": "Può creare una sola Passkey per dispositivo.", + "p2pHowToCreatePasskey": "Come creare una passkey?", + "p2pHowToCreatePasskeyDescription1": "Vai a «Profilo» nella tua app Deriv P2P.", + "p2pHowToCreatePasskeyDescription2": "Tocca «Passkey» per creare la tua passkey.", + "whereArePasskeysSaved": "Dove vengono salvate la passkey?", + "whereArePasskeysSavedDescriptionAndroid": "Android: Gestore di password di Google.", + "whereArePasskeysSavedDescriptionIOS": "iOS: Portachiavi iCloud.", + "whatHappensIfEmailChanged": "Cosa succede se l'email del mio account Deriv viene modificata?", + "whatHappensIfEmailChangedDescription1": "Nessun problema! La sua Passkey funziona ancora.", + "whatHappensIfEmailChangedDescription2": "Acceda a Deriv con la sua Passkey esistente.", + "tips": "Suggerimento", + "beforeUsingPasskeys": "Prima di utilizzare la passkey", + "enableScreenLock": "Abilita il blocco dello schermo sul tuo dispositivo.", + "signInGoogleOrIcloud": "Accedi al tuo account Google o iCloud.", + "enableBluetooth": "Abilita il Bluetooth.", + "noPasskeyFound": "Nessuna passkey trovata!", + "noPasskeyFoundDescription": "Crea una passkey per utilizzare questa funzione.", + "maybeLater": "Forse più tardi", + "effortlessLoginWithPasskeys": "Accesso semplice con passkeys", + "learnMoreAboutPasskeys": "Scopri di più sulle passkey", + "noNeedToRememberPassword": "Non è necessario ricordare una password", + "useYourBiometrics": "Sicurezza avanzata con biometria o blocco dello schermo", + "syncAcrossDevices": "Sincronizzazione su più dispositivi", + "createPasskey": "Crea Passkey", + "unsupportedPlatform": "Piattaforma non supportata", + "storedOn": "Memorizzato su", + "lastUsed": "Ultima volta", + "rename": "Rinomina", + "revoke": "Revoca", + "continueTradingButtonText": "Continua il trading", + "addMorePasskeysButtonText": "Più passkeys", + "unableToSetupPasskey": "Impossibile impostare la Passkey", + "unableToSetupPasskeyDescription": "Abbiamo riscontrato un problema durante la configurazione della sua Passkey. Il processo potrebbe essere stato interrotto, oppure la sessione è scaduta. Provi di nuovo.", + "passkeysOffErrorTitle": "Il servizio Passkeys non è disponibile", + "never": "Mai", + "unable_to_process_your_request": "Impossibile elaborare la tua richiesta", + "unable_to_process_your_request_description": "Stiamo riscontrando un problema temporaneo nell'elaborazione della tua richiesta. Riprova più tardi." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_km.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_km.arb new file mode 100644 index 000000000..ad1372d9c --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_km.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "ជោគជ័យ!", + "passkeyCreatedSuccessMessage": "គណនីរបស់អ្នកឥឡូវនេះត្រូវបានការពារដោយសោចូល។ គ្រប់គ្រងសោចូលរបស់អ្នកតាមរយៈការកំណត់គណនី {platformName} របស់អ្នក។", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "បន្ត", + "unexpectedError": "មានកំហុសដែលមិនបានរំពឹងទុកបានកើតឡើង!", + "unexpectedErrorDescription": "សូមព្យាយាមម្តងទៀតនៅពេលក្រោយ។", + "ok": "យល់ព្រម", + "experienceSaferLogins": "ជួបប្រទះការចូលដែលមានសុវត្ថិភាពជាងមុន", + "enhanceSecurity": "សុវត្ថិភាពកាន់តែប្រសើរគឺគ្រាន់តែចុចមួយដងប៉ុណ្ណោះ។", + "here": "នៅទីនេះ", + "effortlessLogin": "ចូលបានយ៉ាងងាយស្រួលជាមួយសោចូល", + "whatArePasskeys": "តើសោចូលជាអ្វី?", + "whatArePasskeysDescriptionPoint1": "ជម្រើសសុវត្ថិភាពសម្រាប់ពាក្យសម្ងាត់។", + "whatArePasskeysDescriptionPoint2": "ដោះសោគណនីរបស់អ្នកដូចទូរស័ព្ទរបស់អ្នក - ជាមួយជីវមាត្រ ការស្កេនមុខ ឬ PIN។", + "whyPasskeys": "ហេតុអ្វីបានជាសោចូល?", + "whyPasskeysDescription1": "ស្រទាប់សុវត្ថិភាពបន្ថែម។", + "whyPasskeysDescription2": "ការពារប្រឆាំងនឹងការចូលប្រើប្រាស់ដោយគ្មានការអនុញ្ញាត និងការបន្លំ។", + "howToCreatePasskey": "របៀបបង្កើតសោចូល?", + "howToCreatePasskeyDescription1": "ចូលទៅកាន់ 'ការកំណត់គណនី' នៅលើ Deriv។", + "howToCreatePasskeyDescription2": "អ្នកអាចបង្កើតសោចូលមួយសម្រាប់ឧបករណ៍នីមួយៗ។", + "p2pHowToCreatePasskey": "របៀបបង្កើតសោចូល?", + "p2pHowToCreatePasskeyDescription1": "ចូលទៅកាន់ 'ប្រវត្តិរូប' នៅក្នុងកម្មវិធី Deriv P2P របស់អ្នក។", + "p2pHowToCreatePasskeyDescription2": "ចុច 'សោចូល' ដើម្បីបង្កើតសោចូលរបស់អ្នក។", + "whereArePasskeysSaved": "តើសោចូលត្រូវបានរក្សាទុកនៅឯណា?", + "whereArePasskeysSavedDescriptionAndroid": "Android៖ កម្មវិធីគ្រប់គ្រងពាក្យសម្ងាត់ Google។", + "whereArePasskeysSavedDescriptionIOS": "iOS៖ iCloud keychain។", + "whatHappensIfEmailChanged": "តើមានអ្វីកើតឡើងប្រសិនបើអ៊ីមែលគណនី Deriv របស់ខ្ញុំត្រូវបានផ្លាស់ប្តូរ?", + "whatHappensIfEmailChangedDescription1": "គ្មានបញ្ហាទេ! សោចូលរបស់អ្នកនៅតែដំណើរការ។", + "whatHappensIfEmailChangedDescription2": "ចូលទៅក្នុង Deriv ជាមួយសោចូលដែលមានស្រាប់របស់អ្នក។", + "tips": "ការណែនាំ", + "beforeUsingPasskeys": "មុនពេលប្រើប្រាស់សោចូល", + "enableScreenLock": "បើកការចាក់សោអេក្រង់នៅលើឧបករណ៍របស់អ្នក។", + "signInGoogleOrIcloud": "ចូលទៅក្នុងគណនី Google ឬ iCloud របស់អ្នក។", + "enableBluetooth": "បើក Bluetooth។", + "noPasskeyFound": "រកមិនឃើញសោចូលទេ!", + "noPasskeyFoundDescription": "សូមបង្កើតសោចូលដើម្បីប្រើមុខងារនេះ។", + "maybeLater": "ប្រហែលជាពេលក្រោយ", + "effortlessLoginWithPasskeys": "ចូលបានយ៉ាងងាយស្រួលជាមួយសោចូល", + "learnMoreAboutPasskeys": "ស្វែងយល់បន្ថែមអំពីសោចូល", + "noNeedToRememberPassword": "មិនចាំបាច់ចាំពាក្យសម្ងាត់ទេ", + "useYourBiometrics": "សុវត្ថិភាពកាន់តែប្រសើរឡើងជាមួយជីវមាត្រ ឬការចាក់សោអេក្រង់", + "syncAcrossDevices": "ធ្វើសមកាលកម្មនៅលើឧបករណ៍", + "createPasskey": "បង្កើតសោចូល", + "unsupportedPlatform": "វេទិកាមិនត្រូវបានគាំទ្រ", + "storedOn": "រក្សាទុកនៅលើ", + "lastUsed": "ប្រើចុងក្រោយ", + "rename": "ប្តូរឈ្មោះ", + "revoke": "លុបចោល", + "continueTradingButtonText": "បន្តការជួញដូរ", + "addMorePasskeysButtonText": "បន្ថែមសោចូលបន្ថែម", + "unableToSetupPasskey": "មិនអាចដំឡើងសោចូលបានទេ", + "unableToSetupPasskeyDescription": "យើងបានជួបប្រទះបញ្ហាមួយខណៈពេលកំពុងដំឡើងសោចូលរបស់អ្នក។ ដំណើរការនេះអាចត្រូវបានរំខាន ឬវគ្គបានផុតកំណត់។ សូមព្យាយាមម្តងទៀត។", + "passkeysOffErrorTitle": "សេវាកម្មសោចូលមិនមាន", + "never": "មិនដែល", + "unable_to_process_your_request": "មិនអាចដំណើរការសំណើរបស់អ្នកបានទេ", + "unable_to_process_your_request_description": "យើងកំពុងជួបប្រទះបញ្ហាបណ្តោះអាសន្នក្នុងការដំណើរការសំណើរបស់អ្នក។ សូមព្យាយាមម្តងទៀតនៅពេលក្រោយ។" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_ko.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_ko.arb new file mode 100644 index 000000000..632590e26 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_ko.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "성공!", + "passkeyCreatedSuccessMessage": "이제 여러분의 계정은 passkey로 보호됩니다. {platformName} 계정 설정을 통해 passkey를 관리하세요.", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "계속", + "unexpectedError": "예상하지 못한 오류가 발생했습니다!", + "unexpectedErrorDescription": "나중에 다시 시도해 주시기 바랍니다.", + "ok": "확인", + "experienceSaferLogins": "로그인이 더 안전합니다", + "enhanceSecurity": "탭 한 번으로 보안을 강화할 수 있습니다.", + "here": "여기", + "effortlessLogin": "Passkeys로 간편한 로그인", + "whatArePasskeys": "Passkeys란 무엇인가요?", + "whatArePasskeysDescriptionPoint1": "비밀번호를 대체할 수 있는 안전한 비밀번호.", + "whatArePasskeysDescriptionPoint2": "생체 인식, 얼굴 스캔 또는 PIN을 사용하여 휴대폰처럼 계정을 잠금해제하세요.", + "whyPasskeys": "Passkeys를 사용하는 이유는 무엇인가요?", + "whyPasskeysDescription1": "추가 보안 계층.", + "whyPasskeysDescription2": "무단 접근 및 피싱으로부터 보호합니다.", + "howToCreatePasskey": "Passkey는 어떻게 생성할 수 있나요?", + "howToCreatePasskeyDescription1": "Deriv의 '계정 설정'으로 이동하세요.", + "howToCreatePasskeyDescription2": "기기당 하나의 Passkey를 만들 수 있습니다.", + "p2pHowToCreatePasskey": "암호 키는 어떻게 만드나요?", + "p2pHowToCreatePasskeyDescription1": "Deriv P2P 앱에서 '프로필'로 이동합니다.", + "p2pHowToCreatePasskeyDescription2": "'암호'를 눌러 암호를 생성합니다.", + "whereArePasskeysSaved": "Passkeys는 어디에 저장되나요?", + "whereArePasskeysSavedDescriptionAndroid": "Android: Google 비밀번호 관리자.", + "whereArePasskeysSavedDescriptionIOS": "iOS: iCloud 키체인.", + "whatHappensIfEmailChanged": "제 Deriv 계정 이메일이 변경되면 어떻게 되나요?", + "whatHappensIfEmailChangedDescription1": "괜찮습니다! 귀하의 Passkey를 그대로 사용하실 수 있습니다.", + "whatHappensIfEmailChangedDescription2": "기존 Passkey를 사용하여 Deriv에 로그인하세요.", + "tips": "팁", + "beforeUsingPasskeys": "Passkeys를 사용하기 전에", + "enableScreenLock": "기기에서 화면 잠금을 활성화하세요.", + "signInGoogleOrIcloud": "Google 또는 iCloud 계정으로 로그인하세요.", + "enableBluetooth": "블루투스를 활성화하세요.", + "noPasskeyFound": "Passkey를 찾을 수 없습니다!", + "noPasskeyFoundDescription": "이 기능을 사용하기 위해서는 passkey를 생성하시기 바랍니다.", + "maybeLater": "다음으로 미루겠습니다", + "effortlessLoginWithPasskeys": "Passkeys를 통한 간단한 로그인", + "learnMoreAboutPasskeys": "Passkeys에 대해 더 알아보기", + "noNeedToRememberPassword": "비밀번호를 기억하지 않아도 됩니다", + "useYourBiometrics": "생체인식 또는 화면 잠금을 통한 보안 강화", + "syncAcrossDevices": "기기 간 동기화", + "createPasskey": "Passkey 생성", + "unsupportedPlatform": "지원되지 않는 플랫폼입니다", + "storedOn": "저장 위치", + "lastUsed": "최근 사용", + "rename": "이름 변경", + "revoke": "철회", + "continueTradingButtonText": "계속 거래하기", + "addMorePasskeysButtonText": "더 많은 passkeys", + "unableToSetupPasskey": "Passkey를 설정할 수 없습니다", + "unableToSetupPasskeyDescription": "Passkey를 설정하는 동안 문제가 발생했습니다. 프로세스가 중단되었거나 세션 시간이 초과되었을 수 있습니다. 다시 시도해 주시기 바랍니다.", + "passkeysOffErrorTitle": "Passkeys 서비스를 이용할 수 없습니다", + "never": "절대 안 돼", + "unable_to_process_your_request": "요청을 처리할 수 없습니다", + "unable_to_process_your_request_description": "요청을 처리하는 동안 일시적인 문제가 발생했습니다.나중에 다시 시도하세요." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_mn.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_mn.arb new file mode 100644 index 000000000..765b8d2e5 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_mn.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "Success!", + "passkeyCreatedSuccessMessage": "Your account is now secured with a passkey. Manage your passkey through your {platformName} account settings.", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "Continue", + "unexpectedError": "An unexpected error occurred!", + "unexpectedErrorDescription": "Please try again later.", + "ok": "Ok", + "experienceSaferLogins": "Experience safer logins", + "enhanceSecurity": "Enhanced security is just a tap away.", + "here": "here", + "effortlessLogin": "Effortless login with passkeys", + "whatArePasskeys": "What are passkeys?", + "whatArePasskeysDescriptionPoint1": "Secure alternative to passwords.", + "whatArePasskeysDescriptionPoint2": "Unlock your account like your phone - with biometrics, face scan or PIN.", + "whyPasskeys": "Why passkeys?", + "whyPasskeysDescription1": "Extra security layer.", + "whyPasskeysDescription2": "Shields against unauthorised access and phishing.", + "howToCreatePasskey": "How to create a passkey?", + "howToCreatePasskeyDescription1": "Go to ‘Account Settings’ on Deriv.", + "howToCreatePasskeyDescription2": "You can create one passkey per device.", + "p2pHowToCreatePasskey": "How to create passkey?", + "p2pHowToCreatePasskeyDescription1": "Go to ‘Profile‘ in your Deriv P2P app.", + "p2pHowToCreatePasskeyDescription2": "Tap ‘Passkeys‘ to create your passkey.", + "whereArePasskeysSaved": "Where are passkeys saved?", + "whereArePasskeysSavedDescriptionAndroid": "Android: Google password manager.", + "whereArePasskeysSavedDescriptionIOS": "iOS: iCloud keychain.", + "whatHappensIfEmailChanged": "What happens if my Deriv account email is changed?", + "whatHappensIfEmailChangedDescription1": "No problem! Your passkey still works.", + "whatHappensIfEmailChangedDescription2": "Sign in to Deriv with your existing passkey.", + "tips": "Tips", + "beforeUsingPasskeys": "Before using passkeys", + "enableScreenLock": "Enable screen lock on your device.", + "signInGoogleOrIcloud": "Sign in to your Google or iCloud account.", + "enableBluetooth": "Enable Bluetooth.", + "noPasskeyFound": "No passkey found!", + "noPasskeyFoundDescription": "Please create a passkey to use this feature.", + "maybeLater": "Maybe later", + "effortlessLoginWithPasskeys": "Effortless login with passkeys", + "learnMoreAboutPasskeys": "Learn more about passkeys", + "noNeedToRememberPassword": "No need to remember a password", + "useYourBiometrics": "Enhanced security with biometrics or screen lock", + "syncAcrossDevices": "Sync across devices", + "createPasskey": "Create passkey", + "unsupportedPlatform": "Unsupported Platform", + "storedOn": "Stored on", + "lastUsed": "Last used", + "rename": "Rename", + "revoke": "Revoke", + "continueTradingButtonText": "Continue trading", + "addMorePasskeysButtonText": "Add more passkeys", + "unableToSetupPasskey": "Unable to setup passkey", + "unableToSetupPasskeyDescription": "We encountered an issue while setting up your passkey. The process might have been interrupted, or the session timed out. Please try again.", + "passkeysOffErrorTitle": "The Passkeys service is unavailable", + "never": "Never", + "unable_to_process_your_request": "Unable to process your request", + "unable_to_process_your_request_description": "We’re experiencing a temporary issue in processing your request. Please try again later." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_pl.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_pl.arb new file mode 100644 index 000000000..e33a4c2ec --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_pl.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "Udało się!", + "passkeyCreatedSuccessMessage": "Twoje konto jest teraz zabezpieczone passkey. Zarządzaj swoim passkey za pomocą ustawień konta {platformName}.", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "Kontynuuj", + "unexpectedError": "Wystąpił nieoczekiwany błąd!", + "unexpectedErrorDescription": "Spróbuj później.", + "ok": "Ok", + "experienceSaferLogins": "Doświadcz bezpieczniejszych logowań", + "enhanceSecurity": "Zwiększone bezpieczeństwo jest na wyciągnięcie ręki.", + "here": "tutaj", + "effortlessLogin": "Łatwe logowanie za pomocą passkeys", + "whatArePasskeys": "Czym są passkeys?", + "whatArePasskeysDescriptionPoint1": "Bezpieczna alternatywa dla haseł.", + "whatArePasskeysDescriptionPoint2": "Odblokuj swoje konto jak telefon - za pomocą danych biometrycznych, skanowania twarzy lub PIN-u.", + "whyPasskeys": "Dlaczego Passkeys?", + "whyPasskeysDescription1": "Dodatkowa warstwa bezpieczeństwa.", + "whyPasskeysDescription2": "Chroni przed nieautoryzowanym dostępem i phishingiem.", + "howToCreatePasskey": "Jak utworzyć passkey?", + "howToCreatePasskeyDescription1": "Przejdź do ‘Ustawień konta’ na Deriv.", + "howToCreatePasskeyDescription2": "Możesz utworzyć jedno hasło na urządzenie.", + "p2pHowToCreatePasskey": "Jak utworzyć klucz hasłowy?", + "p2pHowToCreatePasskeyDescription1": "Przejdź do „Profil” w aplikacji Deriv P2P.", + "p2pHowToCreatePasskeyDescription2": "Stuknij „Kody haseł”, aby utworzyć klucz hasła.", + "whereArePasskeysSaved": "Gdzie są zapisywane passkeys?", + "whereArePasskeysSavedDescriptionAndroid": "Android: menedżer haseł Google.", + "whereArePasskeysSavedDescriptionIOS": "iOS: pęk kluczy iCloud.", + "whatHappensIfEmailChanged": "Co się stanie, jeśli e-mail mojego konta Deriv zostanie zmieniony?", + "whatHappensIfEmailChangedDescription1": "Bez problemu! Twój Passkey nadal działa.", + "whatHappensIfEmailChangedDescription2": "Zaloguj się do Deriv przy użyciu istniejącego Passkey.", + "tips": "Porady", + "beforeUsingPasskeys": "Przed użyciem passkeys", + "enableScreenLock": "Włącz blokadę ekranu na swoim urządzeniu.", + "signInGoogleOrIcloud": "Zaloguj się na swoje konto Google lub iCloud.", + "enableBluetooth": "Włącz Bluetooth.", + "noPasskeyFound": "Nie znaleziono passkey!", + "noPasskeyFoundDescription": "Aby korzystać z tej funkcji, utwórz passkey.", + "maybeLater": "Może później", + "effortlessLoginWithPasskeys": "Łatwe logowanie za pomocą passkeys", + "learnMoreAboutPasskeys": "Dowiedz się więcej o passkeys", + "noNeedToRememberPassword": "Nie ma potrzeby zapamiętywania hasła", + "useYourBiometrics": "Zwiększone bezpieczeństwo dzięki biometrii lub blokadzie ekranu", + "syncAcrossDevices": "Synchronizacja między urządzeniami", + "createPasskey": "Utwórz passkey", + "unsupportedPlatform": "Nieobsługiwana platforma", + "storedOn": "Przechowywane na", + "lastUsed": "Ostatnio używane", + "rename": "Zmień nazwę", + "revoke": "Odwołać", + "continueTradingButtonText": "Kontynuuj handlowanie", + "addMorePasskeysButtonText": "Więcej passkeys", + "unableToSetupPasskey": "Nie można ustawić passkey", + "unableToSetupPasskeyDescription": "Napotkaliśmy problem podczas konfigurowania Passkey. Proces mógł zostać przerwany lub upłynął limit czasu sesji. Proszę spróbuj ponownie.", + "passkeysOffErrorTitle": "Usługa Passkeys jest niedostępna", + "never": "Nigdy", + "unable_to_process_your_request": "Nie można przetworzyć żądania", + "unable_to_process_your_request_description": "Mamy tymczasowy problem z przetwarzaniem Twojej prośby. Spróbuj ponownie później." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_pt.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_pt.arb new file mode 100644 index 000000000..97df1c81e --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_pt.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "Concluído com sucesso!", + "passkeyCreatedSuccessMessage": "A sua conta está agora protegida com uma chave de acesso. Pode fazer a gestão da sua chave de acesso através das definições da sua conta {platformName}.", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "Continuar", + "unexpectedError": "Ocorreu um erro inesperado!", + "unexpectedErrorDescription": "Por favor, tente novamente mais tarde.", + "ok": "Ok", + "experienceSaferLogins": "Experimente inícios de sessão mais seguros", + "enhanceSecurity": "Segurança reforçada à distância de um clique.", + "here": "aqui", + "effortlessLogin": "Início de sessão simplificado com as chaves de acesso", + "whatArePasskeys": "O que são chaves de acesso?", + "whatArePasskeysDescriptionPoint1": "Alternativa segura às palavras-passe.", + "whatArePasskeysDescriptionPoint2": "Desbloqueie a sua conta com a mesma facilidade que desbloqueia o seu telemóvel - seja através de biometria, reconhecimento facial ou introdução de PIN.", + "whyPasskeys": "Porque é que se utilizam chaves de acesso?", + "whyPasskeysDescription1": "Camada de segurança adicional.", + "whyPasskeysDescription2": "Protege contra o acesso não autorizado e phishing.", + "howToCreatePasskey": "Como criar uma chave de acesso?", + "howToCreatePasskeyDescription1": "Aceda às \"Definições de conta\" na Deriv.", + "howToCreatePasskeyDescription2": "Pode criar uma passkey por dispositivo.", + "p2pHowToCreatePasskey": "Como pode criar uma passkey?", + "p2pHowToCreatePasskeyDescription1": "Aceda à secção \"Perfil\" na sua aplicação Deriv P2P.", + "p2pHowToCreatePasskeyDescription2": "Selecione \"Passkeys\" para criar a sua chave de acesso.", + "whereArePasskeysSaved": "Onde são guardadas as chaves de acesso?", + "whereArePasskeysSavedDescriptionAndroid": "Android: Gestor de palavras-passe do Google.", + "whereArePasskeysSavedDescriptionIOS": "iOS: iCloud keychain.", + "whatHappensIfEmailChanged": "O que acontece se o e-mail da minha conta Deriv for alterado?", + "whatHappensIfEmailChangedDescription1": "Não tem problema! A sua passkey ainda funciona.", + "whatHappensIfEmailChangedDescription2": "Inicie sessão na Deriv com a sua passkey.", + "tips": "Dicas", + "beforeUsingPasskeys": "Antes de utilizar as chaves de acesso", + "enableScreenLock": "Ative o bloqueio de ecrã no seu dispositivo.", + "signInGoogleOrIcloud": "Inicie sessão na sua conta Google ou iCloud.", + "enableBluetooth": "Ativar Bluetooth.", + "noPasskeyFound": "Não foi encontrada nenhuma chave de acesso!", + "noPasskeyFoundDescription": "Por favor, crie uma chave de acesso para utilizar esta funcionalidade.", + "maybeLater": "Talvez mais tarde", + "effortlessLoginWithPasskeys": "Início de sessão simplificado com as chaves de acesso", + "learnMoreAboutPasskeys": "Saiba mais sobre as chaves de acesso", + "noNeedToRememberPassword": "Não memorizar a palavra-passe", + "useYourBiometrics": "Segurança reforçada com dados biométricos ou bloqueio do ecrã", + "syncAcrossDevices": "Sincronizar entre dispositivos", + "createPasskey": "Criar passkey", + "unsupportedPlatform": "Plataforma não suportada", + "storedOn": "Armazenado em", + "lastUsed": "Última utilização", + "rename": "Renomear", + "revoke": "Revogar", + "continueTradingButtonText": "Continuar a negociar", + "addMorePasskeysButtonText": "Adicionar mais passkeys", + "unableToSetupPasskey": "Não é possível configurar a passkey", + "unableToSetupPasskeyDescription": "Houve um problema ao configurar a sua passkey. O processo pode ter sido interrompido ou a sessão expirou. Por favor, tente novamente.", + "passkeysOffErrorTitle": "O serviço de Passkeys não está disponível", + "never": "Nunca", + "unable_to_process_your_request": "Não foi possível completar o processamento do seu pedido", + "unable_to_process_your_request_description": "Surgiu um problema temporário no processamento do seu pedido. Por favor, tente novamente mais tarde." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_ru.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_ru.arb new file mode 100644 index 000000000..2f15a3805 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_ru.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "Готово!", + "passkeyCreatedSuccessMessage": "Теперь ваша учетная запись защищена passkey. Управляйте своим passkey через настройки учетной записи {platformName}.", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "Продолжить", + "unexpectedError": "Произошла неожиданная ошибка!", + "unexpectedErrorDescription": "Попробуйте позже.", + "ok": "Ok", + "experienceSaferLogins": "Испытайте более безопасный вход в систему", + "enhanceSecurity": "Повышенная безопасность — всего лишь одно касание.", + "here": "здесь", + "effortlessLogin": "Легкий вход в систему с помощью passkeys", + "whatArePasskeys": "Что такое passkeys?", + "whatArePasskeysDescriptionPoint1": "Безопасная альтернатива паролям.", + "whatArePasskeysDescriptionPoint2": "Разблокируйте свою учетную запись, как телефон — с помощью биометрии, сканирования лица или PIN-кода.", + "whyPasskeys": "Зачем нужны passkeys?", + "whyPasskeysDescription1": "Дополнительный уровень безопасности.", + "whyPasskeysDescription2": "Защищает от несанкционированного доступа и фишинга.", + "howToCreatePasskey": "Как создать passkey?", + "howToCreatePasskeyDescription1": "Перейдите в ‘Настройки учетной записи’ на Deriv.", + "howToCreatePasskeyDescription2": "Вы можете создать один passkey для каждого устройства.", + "p2pHowToCreatePasskey": "Как создать пароль?", + "p2pHowToCreatePasskeyDescription1": "Перейдите на «Профиль» в приложении Deriv P2P.", + "p2pHowToCreatePasskeyDescription2": "Нажмите «Пароли», чтобы создать свой пароль.", + "whereArePasskeysSaved": "Где хранятся passkeys?", + "whereArePasskeysSavedDescriptionAndroid": "Android: Менеджер паролей Google.", + "whereArePasskeysSavedDescriptionIOS": "iOS: связка ключей iCloud.", + "whatHappensIfEmailChanged": "Что произойдет, если адрес электронной почты моей учетной записи Deriv изменится?", + "whatHappensIfEmailChangedDescription1": "Нет проблем! Ваш passkey по-прежнему работает.", + "whatHappensIfEmailChangedDescription2": "Войдите в Deriv, используя существующий passkey.", + "tips": "Советы", + "beforeUsingPasskeys": "Перед использованием passkey", + "enableScreenLock": "Включите блокировку экрана на своем устройстве.", + "signInGoogleOrIcloud": "Войдите в свою учетную запись Google или iCloud.", + "enableBluetooth": "Включите Bluetooth.", + "noPasskeyFound": "Passkey не найден!", + "noPasskeyFoundDescription": "Чтобы использовать эту функцию, создайте Passkey.", + "maybeLater": "Может быть позже", + "effortlessLoginWithPasskeys": "Легкий вход в систему с помощью passkeys", + "learnMoreAboutPasskeys": "Узнайте больше о passkeys", + "noNeedToRememberPassword": "Нет необходимости запоминать пароль", + "useYourBiometrics": "Повышенная безопасность с помощью биометрии или блокировки экрана", + "syncAcrossDevices": "Синхронизация между устройствами", + "createPasskey": "Создать passkey", + "unsupportedPlatform": "Неподдерживаемая платформа", + "storedOn": "Хранится в", + "lastUsed": "Недавние", + "rename": "Переименовать", + "revoke": "Отменить", + "continueTradingButtonText": "Продолжить торговлю", + "addMorePasskeysButtonText": "Больше passkeys", + "unableToSetupPasskey": "Невозможно настроить passkey", + "unableToSetupPasskeyDescription": "Мы столкнулись с проблемой при настройке вашего passkey. Возможно, процесс был прерван, или сессия завершилась по таймеру. Пожалуйста, попробуйте еще раз.", + "passkeysOffErrorTitle": "Служба Passkeys недоступна", + "never": "Никогда", + "unable_to_process_your_request": "Не удалось обработать ваш запрос", + "unable_to_process_your_request_description": "При обработке вашего запроса возникла временная проблема. Пожалуйста, повторите попытку позже." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_si.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_si.arb new file mode 100644 index 000000000..578e61384 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_si.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "සාර්ථකයි!", + "passkeyCreatedSuccessMessage": "ඔබගේ ගිණුම දැන් Passkey සුරක්ෂිත කර ඇත. ඔබේ {platformName} ගිණු ම් සැකසුම් හරහා ඔබගේ passkey කළමනාකරණය කරන්න.", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "ඉදිරියට යන්න", + "unexpectedError": "අනපේක්ෂිත දෝෂයක් ඇති විය!", + "unexpectedErrorDescription": "කරුණාකර පසුව උත්සාහ කරන්න.", + "ok": "Ok", + "experienceSaferLogins": "ආරක්ෂිත පිවිසුම් අත්විඳින්න", + "enhanceSecurity": "වැඩි දියුණු කළ ආරක්ෂාව එක ටැප් එකක් දුරින්.", + "here": "මෙන්න", + "effortlessLogin": "Passkeys සමඟ වෙහෙස නොබලා පිවිසීම", + "whatArePasskeys": "Passkeys යනු කුමක්ද?", + "whatArePasskeysDescriptionPoint1": "මුරපද සඳහා ආරක්ෂිත විකල්පයක්.", + "whatArePasskeysDescriptionPoint2": "ජෛවමිතික, මුහුණු ස්කෑන් කිරීම හෝ PIN සමඟ ඔබේ දුරකථනයයේ අකාරයටම ඔබේ ගිණුම අගුළු හරින්න.", + "whyPasskeys": "Passkeys අවශ්‍ය වන්නේ ඇයි?", + "whyPasskeysDescription1": "අමතර ආරක්ෂක ස්ථරය.", + "whyPasskeysDescription2": "අනවසර ප්‍රවේශයෙන් සහ තතුබෑම්වලින් ආරක්ෂා කරයි.", + "howToCreatePasskey": "නිර්මාණය Passkey කෙසේද?", + "howToCreatePasskeyDescription1": "Deriv හි 'ගිණුම් සැකසීම්' වෙත යන්න.", + "howToCreatePasskeyDescription2": "ඔබට එක් උපාංගයකට එක් passkey එකක් සෑදිය හැක.", + "p2pHowToCreatePasskey": "මුරපද නිර්මාණය කරන්නේ කෙසේද?", + "p2pHowToCreatePasskeyDescription1": "ඔබේ ඩෙරිව් පී 2 පී යෙදුමේ 'පැතිකඩ' වෙත යන්න.", + "p2pHowToCreatePasskeyDescription2": "ඔබගේ මුරපද නිර්මාණය කිරීම සඳහා 'මුරපද' තට්ටු කරන්න.", + "whereArePasskeysSaved": "Passkey සුරකින්නේ කොහේද?", + "whereArePasskeysSavedDescriptionAndroid": "Android: Google මුරපද කළමනාකරු.", + "whereArePasskeysSavedDescriptionIOS": "iOS: iCloud යතුරු දාමය.", + "whatHappensIfEmailChanged": "මගේ Deriv ගිණුමේ විද්යුත් තැපෑල වෙනස් කළහොත් කුමක් සිදුවේද?", + "whatHappensIfEmailChangedDescription1": "ප්‍රශ්නයක් නැත! ඔබේ passkey එක තවමත් ක්‍රියා කරයි.", + "whatHappensIfEmailChangedDescription2": "ඔබේ පවතින passkey සමඟින් Deriv වෙත පුරන්න.", + "tips": "ඉඟි", + "beforeUsingPasskeys": "Passkey භාවිතා කිරීමට පෙර", + "enableScreenLock": "ඔබේ උපාංගයේ තිර අගුල සක්රීය කරන්න.", + "signInGoogleOrIcloud": "ඔබේ Google හෝ iCloud ගිණුමට පිවිසෙන්න.", + "enableBluetooth": "බ්ලූටූත් සක්රීය කරන්න.", + "noPasskeyFound": "Passkey සොයාගත නොමැත!", + "noPasskeyFoundDescription": "කරුණාකර මෙම විශේෂාංගය භාවිතා කිරීම සඳහා passkey යන්නක් සාදන්න.", + "maybeLater": "සමහර විට පසුව", + "effortlessLoginWithPasskeys": "Passkeys සමඟ වෙහෙස නොබලා පිවිසීම", + "learnMoreAboutPasskeys": "Passkeys ගැන වැඩි විස්තර දැනගන්න", + "noNeedToRememberPassword": "මුරපදයක් මතක තබා ගැනීමට අවශ්ය නැත", + "useYourBiometrics": "ජෛවමිතික හෝ තිර අගුල සමඟ වැඩි දියුණු කළ ආරක්ෂාව", + "syncAcrossDevices": "උපාංග හරහා සමමුහූර්ත කරන්න", + "createPasskey": "Passkey සාදන්න", + "unsupportedPlatform": "සහාය නොදක්වන වේදිකාව", + "storedOn": "ගබඩා කර ඇත", + "lastUsed": "අවසන් වරට භාවිත කළේ", + "rename": "නැවත නම් කරන්න", + "revoke": "අවලංගු කරන්න", + "continueTradingButtonText": "දිගටම ගනුදෙනු කරන්න", + "addMorePasskeysButtonText": "තවත් passkeys එක් කරන්න", + "unableToSetupPasskey": "Passkey සැකසීමට නොහැකි විය", + "unableToSetupPasskeyDescription": "ඔබගේ passkey සැකසීමේදී අපට ගැටලුවක් ඇති විය. ක්රියාවලියට බාධා ඇති විය හැකිය, නැතහොත් සැසිය කාලය අවසන් විය හැකිය. කරුණාකර නැවත උත්සාහ කරන්න.", + "passkeysOffErrorTitle": "Passkeys සේවාව ලබා ගත නොහැක", + "never": "කවදාවත්", + "unable_to_process_your_request": "ඔබගේ ඉල්ලීම සැකසීමට නොහැකි විය", + "unable_to_process_your_request_description": "ඔබගේ ඉල්ලීම සැකසීමේදී අපට තාවකාලික ගැටළුවක් අත්විඳිනවා. කරුණාකර පසුව නැවත උත්සාහ කරන්න." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_sw.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_sw.arb new file mode 100644 index 000000000..048f310de --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_sw.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "Mafanikio!", + "passkeyCreatedSuccessMessage": "Akaunti yako sasa imehifadhiwa na passkey. Dhibiti passkey yako kupi tia mipangilio yako ya akaunti ya {platformName}.", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "Endelea", + "unexpectedError": "Hitilafu yasiyotarajiwa!", + "unexpectedErrorDescription": "Tafadhali jaribu tena.", + "ok": "Ok", + "experienceSaferLogins": "Pata kuingia salama", + "enhanceSecurity": "Usalama ulioimarishwa uko karibu sana, unahitaji kubofya tu.", + "here": "hapa", + "effortlessLogin": "Kuingia bila juhudi kwa passkeys", + "whatArePasskeys": "Nini passkeys?", + "whatArePasskeysDescriptionPoint1": "Njia mbadala ya kufanya nenosiri kuwa salama.", + "whatArePasskeysDescriptionPoint2": "Fungua akaunti yako kama vile simu yako - kwa biyometriki, kuskani uso au PIN.", + "whyPasskeys": "Kwa nini passkeys?", + "whyPasskeysDescription1": "Safu ya ziada ya usalama.", + "whyPasskeysDescription2": "Kinga dhidi ya ufikiaji ambao haujaidhinishwa na hadaa.", + "howToCreatePasskey": "Jinsi ya kuunda passkey?", + "howToCreatePasskeyDescription1": "Nenda katika 'Mipangilio ya Akaunti' kwenye Deriv.", + "howToCreatePasskeyDescription2": "Unaweza kuunda passkey moja kwa kila kifaa.", + "p2pHowToCreatePasskey": "Jinsi ya kuunda passkey?", + "p2pHowToCreatePasskeyDescription1": "Nenda kwenye 'Profaili' katika programu yako ya Deriv P2P.", + "p2pHowToCreatePasskeyDescription2": "Gonga 'Passkeys' ili kuunda nenosiri lako.", + "whereArePasskeysSaved": "Passkeys zimehifadhiwa wapi?", + "whereArePasskeysSavedDescriptionAndroid": "Android: Google password manager.", + "whereArePasskeysSavedDescriptionIOS": "iOS: iCloud keychain.", + "whatHappensIfEmailChanged": "Nini kinatokea ikiwa barua pepe yangu ya akaunti ya Deriv imebadil?", + "whatHappensIfEmailChangedDescription1": "Hakuna tatizo! Passkey yako bado inafanya kazi.", + "whatHappensIfEmailChangedDescription2": "Ingia kwenye Deriv na passkey yako iliyopo.", + "tips": "Vidokezo", + "beforeUsingPasskeys": "Kabla ya kutumia passkeys", + "enableScreenLock": "Wezesha kufunga skrini kwenye kifaa chako.", + "signInGoogleOrIcloud": "Ingia kwenye akaunti yako ya Google au iCloud.", + "enableBluetooth": "Wezesha Bluetooth.", + "noPasskeyFound": "Hakuna passkey iliyopatikana!", + "noPasskeyFoundDescription": "Tafadhali unda passkey ili kutumia kipengele hiki.", + "maybeLater": "Labda baadaye", + "effortlessLoginWithPasskeys": "Kuingia bila juhudi kwa passkeys", + "learnMoreAboutPasskeys": "Jifunze zaidi kuhusu passkeys", + "noNeedToRememberPassword": "Hakuna haja ya kukumbuka nenosiri", + "useYourBiometrics": "Usalama ulioboreshwa na biometriki au kufunga skrini", + "syncAcrossDevices": "Sawazisha katika vifaa", + "createPasskey": "Unda passkey", + "unsupportedPlatform": "Jukwaa isiyosaidiwa", + "storedOn": "Imehifadhiwa kwenye", + "lastUsed": "Imetumika mwisho", + "rename": "Badilisha jina", + "revoke": "Kufuta", + "continueTradingButtonText": "Endelea kufanya biashara", + "addMorePasskeysButtonText": "Ongeza passkeys nyingine", + "unableToSetupPasskey": "Haiwezi kuanzisha kifungu cha passkey", + "unableToSetupPasskeyDescription": "Tulikutana na shida wakati wa kuanzisha passkey lako. Utaratibu huo unaweza kuingiliwa, au kikao kimekamilika. Tafadhali jaribu tena.", + "passkeysOffErrorTitle": "Huduma ya Passkeys haipatikani", + "never": "Kamwe", + "unable_to_process_your_request": "Haiwezi kushughulikia ombi lako", + "unable_to_process_your_request_description": "Tunapata shida ya muda katika kusindika ombi lako. Tafadhali jaribu tena baadaye." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_th.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_th.arb new file mode 100644 index 000000000..60425dc8b --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_th.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "สำเร็จแล้ว!", + "passkeyCreatedSuccessMessage": "ตอนนี้บัญชีของคุณได้รับการรักษาความปลอดภัยด้วย Passkey โดยสามารถจัดการ Passkey นี้ได้ผ่านการตั้งค่าบัญชี {platformName} ของคุณ", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "ดำเนินการต่อ", + "unexpectedError": "มีข้อผิดพลาดเกิดขึ้น!", + "unexpectedErrorDescription": "โปรดลองอีกครั้งในภายหลัง", + "ok": "Ok", + "experienceSaferLogins": "สัมผัสประสบการณ์การเข้าสู่ระบบที่ปลอดภัยขึ้น", + "enhanceSecurity": "การเสริมความปลอดภัยที่อยู่แค่เพียงคุณแตะปุ่มเดียว", + "here": "ที่นี่", + "effortlessLogin": "เข้าสู่ระบบได้อย่างง่ายดายด้วย Passkey", + "whatArePasskeys": "Passkey คืออะไร?", + "whatArePasskeysDescriptionPoint1": "ทางเลือกที่ปลอดภัยนอกเหนือไปจากรหัสผ่าน", + "whatArePasskeysDescriptionPoint2": "ปลดล็อคบัญชีคุณได้เหมือนที่ทำในโทรศัพท์ - ผ่านไบโอเมตริกซ์ การสแกนใบหน้า หรือหมายเลข PIN", + "whyPasskeys": "ทำไมต้องใช้ Passkey?", + "whyPasskeysDescription1": "เพิ่มอีกชั้นความปลอดภัยพิเศษ", + "whyPasskeysDescription2": "กันการเข้าถึงที่ไม่ได้อนุญาตและการโจมตีแบบฟิชชิ่ง", + "howToCreatePasskey": "จะสร้าง Passkey ได้อย่างไร?", + "howToCreatePasskeyDescription1": "ไปที่ 'การตั้งค่าบัญชี' บน Deriv", + "howToCreatePasskeyDescription2": "คุณสามารถสร้างหนึ่ง Passkey ต่ออุปกรณ์", + "p2pHowToCreatePasskey": "จะสร้าง Passkey ได้อย่างไร?", + "p2pHowToCreatePasskeyDescription1": "ไปที่ 'โปรไฟล์' ในแอป Deriv P2P ของคุณ", + "p2pHowToCreatePasskeyDescription2": "แตะ 'Passkeys' เพื่อสร้าง Passkey ของคุณ", + "whereArePasskeysSaved": "Passkey จะถูกบันทึกไว้ที่ไหน?", + "whereArePasskeysSavedDescriptionAndroid": "Android: ตัวจัดการรหัสผ่าน Google", + "whereArePasskeysSavedDescriptionIOS": "iOS: พวงกุญแจ iCloud", + "whatHappensIfEmailChanged": "จะเกิดอะไรขึ้นหากอีเมล์บัญชี Deriv ของฉันเปลี่ยนไป?", + "whatHappensIfEmailChangedDescription1": "ไม่มีปัญหา! Passkey ของคุณยังใช้งานได้อยู่", + "whatHappensIfEmailChangedDescription2": "ลงชื่อเข้าใช้ Deriv ด้วย Passkey ที่มีอยู่ของคุณ", + "tips": "เคล็ดลับ", + "beforeUsingPasskeys": "ก่อนที่จะใช้ Passkey", + "enableScreenLock": "เปิดใช้งานการล็อคหน้าจอบนอุปกรณ์ของคุณ", + "signInGoogleOrIcloud": "ลงชื่อเข้าใช้บัญชี Google หรือ iCloud ของคุณ", + "enableBluetooth": "เปิดใช้งานบลูทูธ", + "noPasskeyFound": "ไม่พบ Passkey!", + "noPasskeyFoundDescription": "กรุณาสร้าง Passkey เพื่อใช้ฟีเจอร์นี้", + "maybeLater": "ไว้ทีหลัง", + "effortlessLoginWithPasskeys": "เข้าสู่ระบบได้อย่างง่ายดายด้วย Passkey", + "learnMoreAboutPasskeys": "เรียนรู้เพิ่มเติมเกี่ยวกับ Passkey", + "noNeedToRememberPassword": "ไม่จำเป็นต้องจดจำรหัสผ่าน", + "useYourBiometrics": "เพิ่มความปลอดภัยด้วยไบโอเมตริกซ์หรือการล็อคหน้าจอ", + "syncAcrossDevices": "ซิงค์ได้ระหว่างอุปกรณ์ต่างๆ", + "createPasskey": "สร้าง Passkey", + "unsupportedPlatform": "แพลตฟอร์มที่ไม่รองรับ", + "storedOn": "เก็บไว้ใน", + "lastUsed": "ใช้ครั้งล่าสุด", + "rename": "เปลี่ยนชื่อ", + "revoke": "เพิกถอน", + "continueTradingButtonText": "ดำเนินการเทรดต่อ", + "addMorePasskeysButtonText": "เพิ่มจำนวน Passkey", + "unableToSetupPasskey": "ไม่สามารถตั้งค่า Passkey ได้", + "unableToSetupPasskeyDescription": "เราพบปัญหาขณะตั้งค่า Passkey ของคุณ กระบวนการอาจถูกขัดจังหวะหรือเซสชั่นหมดเวลา โปรดลองอีกครั้ง", + "passkeysOffErrorTitle": "บริการ Passkey ไม่พร้อมใช้งาน", + "never": "ไม่เคย", + "unable_to_process_your_request": "ไม่สามารถประมวลผลคำขอของคุณได้", + "unable_to_process_your_request_description": "เรากำลังประสบปัญหาชั่วคราวในการประมวลผลคำขอของคุณ โปรดลองอีกครั้งในภายหลัง" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_tr.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_tr.arb new file mode 100644 index 000000000..d6b3ed6b1 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_tr.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "Başarı!", + "passkeyCreatedSuccessMessage": "Hesabınız artık bir şifre ile güvence altına alınmıştır. {platformName} hesap ayarlarınızdan passkey'inizi yönetin.", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "Devam et", + "unexpectedError": "Beklenmedik bir hata oluştu!", + "unexpectedErrorDescription": "Lütfen daha sonra deneyin.", + "ok": "Tamam", + "experienceSaferLogins": "Daha güvenli oturum açma deneyimini yaşayın", + "enhanceSecurity": "Gelişmiş güvenlik sadece bir dokunuş uzağınızda.", + "here": "burada", + "effortlessLogin": "Passkey tuşları ile zahmetsiz giriş", + "whatArePasskeys": "Passkeys nedir?", + "whatArePasskeysDescriptionPoint1": "Şifrelere güvenli alternatif.", + "whatArePasskeysDescriptionPoint2": "Hesabınızın kilidini telefonunuz gibi açın - biyometri, yüz tarama veya PIN ile.", + "whyPasskeys": "Neden Passkey?", + "whyPasskeysDescription1": "Ekstra güvenlik katmanı.", + "whyPasskeysDescription2": "Yetkisiz erişime ve kimlik avına karşı kalkanlar.", + "howToCreatePasskey": "Passkey nasıl oluşturulur?", + "howToCreatePasskeyDescription1": "Deriv'de 'Hesap Ayarları'na gidin.", + "howToCreatePasskeyDescription2": "Cihaz başına bir passkey oluşturabilirsiniz.", + "p2pHowToCreatePasskey": "Passkey nasıl oluşturulur?", + "p2pHowToCreatePasskeyDescription1": "Deriv P2P uygulamanızdaki 'Profil' bölümüne gidin.", + "p2pHowToCreatePasskeyDescription2": "Passkey'inizi oluşturmak için \"Passkeys\" ögesine dokunun.", + "whereArePasskeysSaved": "Passkeys nereye kaydedilir?", + "whereArePasskeysSavedDescriptionAndroid": "Android: Google şifre yöneticisi.", + "whereArePasskeysSavedDescriptionIOS": "iOS: iCloud anahtar zinciri.", + "whatHappensIfEmailChanged": "Deriv hesabım e-posta adresim değiştirilirse ne olur?", + "whatHappensIfEmailChangedDescription1": "Sorun yok! Passkey hâlâ çalışıyor.", + "whatHappensIfEmailChangedDescription2": "Mevcut passkey ile Deriv'de oturum açın.", + "tips": "İpuçları", + "beforeUsingPasskeys": "Passkey kullanmadan önce", + "enableScreenLock": "Cihazınızda ekran kilidini etkinleştirin.", + "signInGoogleOrIcloud": "Google veya iCloud hesabınızda oturum açın.", + "enableBluetooth": "Bluetooth'u etkinleştir.", + "noPasskeyFound": "Passkey bulunamadı!", + "noPasskeyFoundDescription": "Bu özelliği kullanmak için lütfen bir passkey oluşturun.", + "maybeLater": "Belki daha sonra", + "effortlessLoginWithPasskeys": "Passkey tuşları ile zahmetsiz giriş", + "learnMoreAboutPasskeys": "Passkeys hakkında daha fazla bilgi edinin", + "noNeedToRememberPassword": "Şifreyi hatırlamanıza gerek yok", + "useYourBiometrics": "Biyometri veya ekran kilidi ile gelişmiş güvenlik", + "syncAcrossDevices": "Cihazlar arasında senkronizasyon", + "createPasskey": "Passkey oluştur", + "unsupportedPlatform": "Desteklenmeyen Platform", + "storedOn": "Şurada saklanır", + "lastUsed": "Son kullanılan", + "rename": "Yeniden Adlandır", + "revoke": "İptal", + "continueTradingButtonText": "Alım satıma devam", + "addMorePasskeysButtonText": "Daha fazla passkeys ekle", + "unableToSetupPasskey": "Passkey ayarlanamıyor", + "unableToSetupPasskeyDescription": "Passkey ayarlarken bir sorunla karşılaştık. İşlem yarıda kesilmiş veya oturum zaman aşımına uğramış olabilir. Lütfen tekrar deneyin.", + "passkeysOffErrorTitle": "Passkeys hizmeti kullanılamıyor", + "never": "Asla", + "unable_to_process_your_request": "Talebiniz işleme alınamıyor", + "unable_to_process_your_request_description": "Talebinizin işlenmesinde geçici bir sorun yaşıyoruz. Lütfen daha sonra tekrar deneyin." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_uz.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_uz.arb new file mode 100644 index 000000000..765b8d2e5 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_uz.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "Success!", + "passkeyCreatedSuccessMessage": "Your account is now secured with a passkey. Manage your passkey through your {platformName} account settings.", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "Continue", + "unexpectedError": "An unexpected error occurred!", + "unexpectedErrorDescription": "Please try again later.", + "ok": "Ok", + "experienceSaferLogins": "Experience safer logins", + "enhanceSecurity": "Enhanced security is just a tap away.", + "here": "here", + "effortlessLogin": "Effortless login with passkeys", + "whatArePasskeys": "What are passkeys?", + "whatArePasskeysDescriptionPoint1": "Secure alternative to passwords.", + "whatArePasskeysDescriptionPoint2": "Unlock your account like your phone - with biometrics, face scan or PIN.", + "whyPasskeys": "Why passkeys?", + "whyPasskeysDescription1": "Extra security layer.", + "whyPasskeysDescription2": "Shields against unauthorised access and phishing.", + "howToCreatePasskey": "How to create a passkey?", + "howToCreatePasskeyDescription1": "Go to ‘Account Settings’ on Deriv.", + "howToCreatePasskeyDescription2": "You can create one passkey per device.", + "p2pHowToCreatePasskey": "How to create passkey?", + "p2pHowToCreatePasskeyDescription1": "Go to ‘Profile‘ in your Deriv P2P app.", + "p2pHowToCreatePasskeyDescription2": "Tap ‘Passkeys‘ to create your passkey.", + "whereArePasskeysSaved": "Where are passkeys saved?", + "whereArePasskeysSavedDescriptionAndroid": "Android: Google password manager.", + "whereArePasskeysSavedDescriptionIOS": "iOS: iCloud keychain.", + "whatHappensIfEmailChanged": "What happens if my Deriv account email is changed?", + "whatHappensIfEmailChangedDescription1": "No problem! Your passkey still works.", + "whatHappensIfEmailChangedDescription2": "Sign in to Deriv with your existing passkey.", + "tips": "Tips", + "beforeUsingPasskeys": "Before using passkeys", + "enableScreenLock": "Enable screen lock on your device.", + "signInGoogleOrIcloud": "Sign in to your Google or iCloud account.", + "enableBluetooth": "Enable Bluetooth.", + "noPasskeyFound": "No passkey found!", + "noPasskeyFoundDescription": "Please create a passkey to use this feature.", + "maybeLater": "Maybe later", + "effortlessLoginWithPasskeys": "Effortless login with passkeys", + "learnMoreAboutPasskeys": "Learn more about passkeys", + "noNeedToRememberPassword": "No need to remember a password", + "useYourBiometrics": "Enhanced security with biometrics or screen lock", + "syncAcrossDevices": "Sync across devices", + "createPasskey": "Create passkey", + "unsupportedPlatform": "Unsupported Platform", + "storedOn": "Stored on", + "lastUsed": "Last used", + "rename": "Rename", + "revoke": "Revoke", + "continueTradingButtonText": "Continue trading", + "addMorePasskeysButtonText": "Add more passkeys", + "unableToSetupPasskey": "Unable to setup passkey", + "unableToSetupPasskeyDescription": "We encountered an issue while setting up your passkey. The process might have been interrupted, or the session timed out. Please try again.", + "passkeysOffErrorTitle": "The Passkeys service is unavailable", + "never": "Never", + "unable_to_process_your_request": "Unable to process your request", + "unable_to_process_your_request_description": "We’re experiencing a temporary issue in processing your request. Please try again later." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_vi.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_vi.arb new file mode 100644 index 000000000..328e50aaa --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_vi.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "Thành công!", + "passkeyCreatedSuccessMessage": "Tài khoản của bạn hiện được bảo mật bằng passkey. Quản lý passkey của bạn thông qua cài đặt tài khoản {platformName} của bạn.", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "Tiếp tục", + "unexpectedError": "Đã có lỗi bất ngờ xảy ra!", + "unexpectedErrorDescription": "Vui lòng thử lại sau.", + "ok": "Ok", + "experienceSaferLogins": "Trải nghiệm đăng nhập an toàn hơn", + "enhanceSecurity": "Bảo mật nâng cao chỉ cách một cú chạm.", + "here": "ở đây", + "effortlessLogin": "Đăng nhập dễ dàng với các passkeys", + "whatArePasskeys": "Passkeys là gì?", + "whatArePasskeysDescriptionPoint1": "Thay thế an toàn cho mật khẩu.", + "whatArePasskeysDescriptionPoint2": "Mở khóa tài khoản của bạn như điện thoại của bạn - với sinh trắc học, quét khuôn mặt hoặc mã PIN.", + "whyPasskeys": "Tại sao passkeys?", + "whyPasskeysDescription1": "Lớp bảo mật bổ sung.", + "whyPasskeysDescription2": "Bảo vệ chống truy cập trái phép và lừa đảo.", + "howToCreatePasskey": "Làm thế nào để tạo passkey?", + "howToCreatePasskeyDescription1": "Chuyển đến 'Cài đặt tài khoản' trên Deriv.", + "howToCreatePasskeyDescription2": "Bạn có thể tạo một passkey cho mỗi thiết bị.", + "p2pHowToCreatePasskey": "Làm thế nào để tạo mật khẩu?", + "p2pHowToCreatePasskeyDescription1": "Chuyển đến 'Hồ sơ' trong ứng dụng Deriv P2P của bạn.", + "p2pHowToCreatePasskeyDescription2": "Nhấn vào 'Phím mật khẩu' để tạo mật khẩu của bạn.", + "whereArePasskeysSaved": "Passkeys được lưu ở đâu?", + "whereArePasskeysSavedDescriptionAndroid": "Android: Trình quản lý mật khẩu Google.", + "whereArePasskeysSavedDescriptionIOS": "iOS: Móc khóa iCloud.", + "whatHappensIfEmailChanged": "Điều gì xảy ra nếu email tài khoản Deriv của tôi bị thay đổi?", + "whatHappensIfEmailChangedDescription1": "Không có vấn đề gì! Khóa passkey của bạn vẫn hoạt động.", + "whatHappensIfEmailChangedDescription2": "Đăng nhập vào Deriv bằng passkey hiện có của bạn.", + "tips": "Mẹo", + "beforeUsingPasskeys": "Trước khi sử dụng passkeys", + "enableScreenLock": "Bật khóa màn hình trên thiết bị của bạn.", + "signInGoogleOrIcloud": "Đăng nhập vào tài khoản Google hoặc iCloud của bạn.", + "enableBluetooth": "Bật Bluetooth.", + "noPasskeyFound": "Không tìm thấy passkey!", + "noPasskeyFoundDescription": "Vui lòng tạo passkey để sử dụng tính năng này.", + "maybeLater": "Để sau", + "effortlessLoginWithPasskeys": "Đăng nhập dễ dàng với các passkeys", + "learnMoreAboutPasskeys": "Tìm hiểu thêm về passkeys", + "noNeedToRememberPassword": "Không cần nhớ mật khẩu", + "useYourBiometrics": "Tăng cường bảo mật với sinh trắc học hoặc khóa màn hình", + "syncAcrossDevices": "Đồng bộ hóa trên các thiết bị", + "createPasskey": "Tạo passkey", + "unsupportedPlatform": "Nền tảng không được hỗ trợ", + "storedOn": "Được lưu trữ trên", + "lastUsed": "Lần sử dụng cuối", + "rename": "Đổi tên", + "revoke": "Thu hồi", + "continueTradingButtonText": "Tiếp tục giao dịch", + "addMorePasskeysButtonText": "Thêm passkeys", + "unableToSetupPasskey": "Không thể thiết lập passkey", + "unableToSetupPasskeyDescription": "Chúng tôi gặp sự cố khi thiết lập passkey của bạn. Quá trình có thể đã bị gián đoạn hoặc phiên đã hết thời gian. Vui lòng thử lại.", + "passkeysOffErrorTitle": "Dịch vụ Passkeys không khả dụng", + "never": "Không bao giờ", + "unable_to_process_your_request": "Không thể xử lý yêu cầu của bạn", + "unable_to_process_your_request_description": "Chúng tôi đang gặp sự cố tạm thời trong quá trình xử lý yêu cầu của bạn. Vui lòng thử lại sau." +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_zh-CN.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_zh-CN.arb new file mode 100644 index 000000000..8c249e5d8 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_zh-CN.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "成功!", + "passkeyCreatedSuccessMessage": "账户现已使用 passkey 保护。通过<0/> {platformName} 账户设置<0/>管理 passkey 。", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "继续", + "unexpectedError": "发生不可预测的错误!", + "unexpectedErrorDescription": "请稍后重试。", + "ok": "确定", + "experienceSaferLogins": "体验更安全的登录", + "enhanceSecurity": "只需轻点一下即可增强安全性。", + "here": "这里", + "effortlessLogin": "使用 passkey 轻松登录", + "whatArePasskeys": "什么是 passkey ?", + "whatArePasskeysDescriptionPoint1": "密码的安全替代方案。", + "whatArePasskeysDescriptionPoint2": "使用生物识别、面部扫描或 PIN 码像解锁手机一样解锁账户-。", + "whyPasskeys": "为什么使用 passkey ?", + "whyPasskeysDescription1": "额外的安全层。", + "whyPasskeysDescription2": "防范未经授权的访问和网络钓鱼。", + "howToCreatePasskey": "如何创建 passkey ?", + "howToCreatePasskeyDescription1": "转到 Deriv 的‘账户设置‘。", + "howToCreatePasskeyDescription2": "每个设备只能创建一个 passkey 。", + "p2pHowToCreatePasskey": "如何创建 passkey ?", + "p2pHowToCreatePasskeyDescription1": "转到 Deriv P2P 应用程序中的 ‘个人资料‘。", + "p2pHowToCreatePasskeyDescription2": "点击 ‘ passkey ‘ 以创建 passkey 。", + "whereArePasskeysSaved": " passkey 保存在哪里?", + "whereArePasskeysSavedDescriptionAndroid": "Android: Google 密码管理器。", + "whereArePasskeysSavedDescriptionIOS": "iOS:iCloud 钥匙串。", + "whatHappensIfEmailChanged": "如果 Deriv 账户电子邮件地址更改会怎样?", + "whatHappensIfEmailChangedDescription1": "没问题! passkey 仍然有效。", + "whatHappensIfEmailChangedDescription2": "使用现有 passkey 登录 Deriv。", + "tips": "提示", + "beforeUsingPasskeys": "使用 passkey 之前", + "enableScreenLock": "在设备启用屏幕锁定。", + "signInGoogleOrIcloud": "登录到 Google 或 iCloud 账户。", + "enableBluetooth": "启用蓝牙。", + "noPasskeyFound": "找不到 passkey !", + "noPasskeyFoundDescription": "请创建 passkey 以使用此功能。", + "maybeLater": "以后再说", + "effortlessLoginWithPasskeys": "使用 passkey 轻松登录", + "learnMoreAboutPasskeys": "了解有关 passkey 的更多信息", + "noNeedToRememberPassword": "无需记住密码", + "useYourBiometrics": "通过生物识别或屏幕锁增强安全性", + "syncAcrossDevices": "跨设备同步", + "createPasskey": "创建 passkey ", + "unsupportedPlatform": "不支持的平台", + "storedOn": "存储在", + "lastUsed": "上一次使用", + "rename": "重命名", + "revoke": "撤销", + "continueTradingButtonText": "继续交易", + "addMorePasskeysButtonText": "添加更多 passkey ", + "unableToSetupPasskey": "无法设置 passkey ", + "unableToSetupPasskeyDescription": "设置 passkey 时遇到了问题。该过程可能已中断,或者会话超时。请再试一次。", + "passkeysOffErrorTitle": " passkey 服务不可用", + "never": "从未", + "unable_to_process_your_request": "无法处理请求", + "unable_to_process_your_request_description": "处理请求时遇到了临时问题。请稍后再试。" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_zh-TW.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_zh-TW.arb new file mode 100644 index 000000000..88aeb4199 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_zh-TW.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "成功!", + "passkeyCreatedSuccessMessage": "帳戶現在已使用 passkey 保護。請透過 <0/>{platformName} 帳戶設定<0/>管理 passkey 。", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "繼續", + "unexpectedError": "發生不可預測錯誤!", + "unexpectedErrorDescription": "請稍後重試。", + "ok": "確定", + "experienceSaferLogins": "體驗更安全的登入", + "enhanceSecurity": "只需輕按一下即可增強安全性。", + "here": "這裡", + "effortlessLogin": "使用 passkey 輕鬆登入", + "whatArePasskeys": "什麼是 passkey ?", + "whatArePasskeysDescriptionPoint1": "密碼之外的安全替代品。", + "whatArePasskeysDescriptionPoint2": "使用生物特徵、臉部掃描或 PIN 像手機一樣解鎖帳戶。", + "whyPasskeys": "為什麼要使用 passkey ?", + "whyPasskeysDescription1": "額外的安全層。", + "whyPasskeysDescription2": "防止未經授權的存取和網絡釣魚。", + "howToCreatePasskey": "如何建立 passkey ?", + "howToCreatePasskeyDescription1": "轉到 Deriv 的‘帳戶設定‘。", + "howToCreatePasskeyDescription2": "可以為每個裝置建立一個 passkey 。", + "p2pHowToCreatePasskey": "如何建立 passkey ?", + "p2pHowToCreatePasskeyDescription1": "在 Deriv P2P 應用程式中轉到‘個人資料‘。", + "p2pHowToCreatePasskeyDescription2": "點選‘密鑰‘以建立密鑰。", + "whereArePasskeysSaved": " passkey 儲存在哪裡?", + "whereArePasskeysSavedDescriptionAndroid": "Android: Google 密碼管理器。", + "whereArePasskeysSavedDescriptionIOS": "iOS:iCloud 鑰匙圈。", + "whatHappensIfEmailChanged": "如果 Deriv 帳戶電子郵件更改,會怎麼樣?", + "whatHappensIfEmailChangedDescription1": "沒問題! passkey 仍然有效。", + "whatHappensIfEmailChangedDescription2": "使用現有的 passkey 登入 Deriv。", + "tips": "提示", + "beforeUsingPasskeys": "使用 passkey 之前", + "enableScreenLock": "啟用裝置螢幕鎖定。", + "signInGoogleOrIcloud": "登入 Google 或 iCloud 帳戶。", + "enableBluetooth": "啟用藍牙。", + "noPasskeyFound": "找不到 passkey !", + "noPasskeyFoundDescription": "請建立 passkey 以使用此功能。", + "maybeLater": "以後再說", + "effortlessLoginWithPasskeys": "使用 passkey 輕鬆登入", + "learnMoreAboutPasskeys": "進一步了解 passkey ", + "noNeedToRememberPassword": "無需記住密碼", + "useYourBiometrics": "通過生物特徵或螢幕鎖定增強安全性", + "syncAcrossDevices": "跨裝置同步", + "createPasskey": "建立 passkey ", + "unsupportedPlatform": "不支援的平台", + "storedOn": "儲存在", + "lastUsed": "上一次使用", + "rename": "重命名", + "revoke": "撤銷", + "continueTradingButtonText": "繼續交易", + "addMorePasskeysButtonText": "新增更多 passkey ", + "unableToSetupPasskey": "無法設定 passkey ", + "unableToSetupPasskeyDescription": "設定 passkey 時遇到問題。程序可能已中斷,或工作階段逾時。請再試一次。", + "passkeysOffErrorTitle": " passkey 服務無法使用", + "never": "從未", + "unable_to_process_your_request": "無法處理請求", + "unable_to_process_your_request_description": "處理請求時遇到臨時問題。請稍後再試。" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_zh.arb b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_zh.arb new file mode 100644 index 000000000..88aeb4199 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/deriv_passkeys/app_zh.arb @@ -0,0 +1,65 @@ +{ + "passkeyCreatedSuccessTitle": "成功!", + "passkeyCreatedSuccessMessage": "帳戶現在已使用 passkey 保護。請透過 <0/>{platformName} 帳戶設定<0/>管理 passkey 。", + "@passkeyCreatedSuccessMessage": { + "placeholders": { + "platformName": { + "required": true, + "type": "String" + } + } + }, + "continueButtonText": "繼續", + "unexpectedError": "發生不可預測錯誤!", + "unexpectedErrorDescription": "請稍後重試。", + "ok": "確定", + "experienceSaferLogins": "體驗更安全的登入", + "enhanceSecurity": "只需輕按一下即可增強安全性。", + "here": "這裡", + "effortlessLogin": "使用 passkey 輕鬆登入", + "whatArePasskeys": "什麼是 passkey ?", + "whatArePasskeysDescriptionPoint1": "密碼之外的安全替代品。", + "whatArePasskeysDescriptionPoint2": "使用生物特徵、臉部掃描或 PIN 像手機一樣解鎖帳戶。", + "whyPasskeys": "為什麼要使用 passkey ?", + "whyPasskeysDescription1": "額外的安全層。", + "whyPasskeysDescription2": "防止未經授權的存取和網絡釣魚。", + "howToCreatePasskey": "如何建立 passkey ?", + "howToCreatePasskeyDescription1": "轉到 Deriv 的‘帳戶設定‘。", + "howToCreatePasskeyDescription2": "可以為每個裝置建立一個 passkey 。", + "p2pHowToCreatePasskey": "如何建立 passkey ?", + "p2pHowToCreatePasskeyDescription1": "在 Deriv P2P 應用程式中轉到‘個人資料‘。", + "p2pHowToCreatePasskeyDescription2": "點選‘密鑰‘以建立密鑰。", + "whereArePasskeysSaved": " passkey 儲存在哪裡?", + "whereArePasskeysSavedDescriptionAndroid": "Android: Google 密碼管理器。", + "whereArePasskeysSavedDescriptionIOS": "iOS:iCloud 鑰匙圈。", + "whatHappensIfEmailChanged": "如果 Deriv 帳戶電子郵件更改,會怎麼樣?", + "whatHappensIfEmailChangedDescription1": "沒問題! passkey 仍然有效。", + "whatHappensIfEmailChangedDescription2": "使用現有的 passkey 登入 Deriv。", + "tips": "提示", + "beforeUsingPasskeys": "使用 passkey 之前", + "enableScreenLock": "啟用裝置螢幕鎖定。", + "signInGoogleOrIcloud": "登入 Google 或 iCloud 帳戶。", + "enableBluetooth": "啟用藍牙。", + "noPasskeyFound": "找不到 passkey !", + "noPasskeyFoundDescription": "請建立 passkey 以使用此功能。", + "maybeLater": "以後再說", + "effortlessLoginWithPasskeys": "使用 passkey 輕鬆登入", + "learnMoreAboutPasskeys": "進一步了解 passkey ", + "noNeedToRememberPassword": "無需記住密碼", + "useYourBiometrics": "通過生物特徵或螢幕鎖定增強安全性", + "syncAcrossDevices": "跨裝置同步", + "createPasskey": "建立 passkey ", + "unsupportedPlatform": "不支援的平台", + "storedOn": "儲存在", + "lastUsed": "上一次使用", + "rename": "重命名", + "revoke": "撤銷", + "continueTradingButtonText": "繼續交易", + "addMorePasskeysButtonText": "新增更多 passkey ", + "unableToSetupPasskey": "無法設定 passkey ", + "unableToSetupPasskeyDescription": "設定 passkey 時遇到問題。程序可能已中斷,或工作階段逾時。請再試一次。", + "passkeysOffErrorTitle": " passkey 服務無法使用", + "never": "從未", + "unable_to_process_your_request": "無法處理請求", + "unable_to_process_your_request_description": "處理請求時遇到臨時問題。請稍後再試。" +} \ No newline at end of file diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations.dart new file mode 100644 index 000000000..e0c9b9851 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations.dart @@ -0,0 +1,721 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'deriv_auth_localizations_ar.dart'; +import 'deriv_auth_localizations_bn.dart'; +import 'deriv_auth_localizations_de.dart'; +import 'deriv_auth_localizations_en.dart'; +import 'deriv_auth_localizations_es.dart'; +import 'deriv_auth_localizations_fr.dart'; +import 'deriv_auth_localizations_it.dart'; +import 'deriv_auth_localizations_km.dart'; +import 'deriv_auth_localizations_ko.dart'; +import 'deriv_auth_localizations_mn.dart'; +import 'deriv_auth_localizations_pl.dart'; +import 'deriv_auth_localizations_pt.dart'; +import 'deriv_auth_localizations_ru.dart'; +import 'deriv_auth_localizations_si.dart'; +import 'deriv_auth_localizations_sw.dart'; +import 'deriv_auth_localizations_th.dart'; +import 'deriv_auth_localizations_tr.dart'; +import 'deriv_auth_localizations_uz.dart'; +import 'deriv_auth_localizations_vi.dart'; +import 'deriv_auth_localizations_zh.dart'; + +/// Callers can lookup localized strings with an instance of DerivAuthLocalizations +/// returned by `DerivAuthLocalizations.of(context)`. +/// +/// Applications need to include `DerivAuthLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'deriv_auth/deriv_auth_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: DerivAuthLocalizations.localizationsDelegates, +/// supportedLocales: DerivAuthLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the DerivAuthLocalizations.supportedLocales +/// property. +abstract class DerivAuthLocalizations { + DerivAuthLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static DerivAuthLocalizations of(BuildContext context) { + return Localizations.of(context, DerivAuthLocalizations)!; + } + + static const LocalizationsDelegate delegate = _DerivAuthLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [ + Locale('ar'), + Locale('bn'), + Locale('de'), + Locale('en'), + Locale('es'), + Locale('fr'), + Locale('it'), + Locale('km'), + Locale('ko'), + Locale('mn'), + Locale('pl'), + Locale('pt'), + Locale('ru'), + Locale('si'), + Locale('sw'), + Locale('th'), + Locale('tr'), + Locale('uz'), + Locale('vi'), + Locale('zh', 'CN'), + Locale('zh', 'TW'), + Locale('zh') + ]; + + /// No description provided for @labelNotAvailable. + /// + /// In en, this message translates to: + /// **'Not available'** + String get labelNotAvailable; + + /// A message with a single parameter + /// + /// In en, this message translates to: + /// **'{app} isn\'t available in your country'** + String warnNotAvailableCountriesTitle(String app); + + /// No description provided for @actionOk. + /// + /// In en, this message translates to: + /// **'OK'** + String get actionOk; + + /// No description provided for @warnNotAvailableCountries. + /// + /// In en, this message translates to: + /// **'If you have any questions, contact us via '** + String get warnNotAvailableCountries; + + /// No description provided for @labelLiveChat. + /// + /// In en, this message translates to: + /// **'Live chat'** + String get labelLiveChat; + + /// No description provided for @actionSignUpForFree. + /// + /// In en, this message translates to: + /// **'Sign up for free'** + String get actionSignUpForFree; + + /// No description provided for @actionLogin. + /// + /// In en, this message translates to: + /// **'Log in'** + String get actionLogin; + + /// No description provided for @labelTwoFactorAuth. + /// + /// In en, this message translates to: + /// **'Two-factor authentication'** + String get labelTwoFactorAuth; + + /// No description provided for @informEnterTwoFactorAuthCode. + /// + /// In en, this message translates to: + /// **'Enter the 6-digit code from the authenticator app on your phone.'** + String get informEnterTwoFactorAuthCode; + + /// No description provided for @labelTwoFactorAuthenticationCode. + /// + /// In en, this message translates to: + /// **'2FA code'** + String get labelTwoFactorAuthenticationCode; + + /// No description provided for @actionProceed. + /// + /// In en, this message translates to: + /// **'Proceed'** + String get actionProceed; + + /// No description provided for @labelLogIn. + /// + /// In en, this message translates to: + /// **'Log in'** + String get labelLogIn; + + /// No description provided for @informLoginOptions. + /// + /// In en, this message translates to: + /// **'Or log in with'** + String get informLoginOptions; + + /// No description provided for @labelEmail. + /// + /// In en, this message translates to: + /// **'Email'** + String get labelEmail; + + /// No description provided for @labelPassword. + /// + /// In en, this message translates to: + /// **'Password'** + String get labelPassword; + + /// No description provided for @actionForgotPassword. + /// + /// In en, this message translates to: + /// **'Forgot password?'** + String get actionForgotPassword; + + /// No description provided for @labelDontHaveAnAccountYet. + /// + /// In en, this message translates to: + /// **'Don’t have an account yet?'** + String get labelDontHaveAnAccountYet; + + /// No description provided for @actionCreateANewAccount. + /// + /// In en, this message translates to: + /// **'Create a new account'** + String get actionCreateANewAccount; + + /// No description provided for @informInvalidEmailFormat. + /// + /// In en, this message translates to: + /// **'Enter a valid email address'** + String get informInvalidEmailFormat; + + /// No description provided for @warnPasswordLength. + /// + /// In en, this message translates to: + /// **'You should enter 6-25 characters.'** + String get warnPasswordLength; + + /// No description provided for @labelResetPassword. + /// + /// In en, this message translates to: + /// **'Reset password'** + String get labelResetPassword; + + /// No description provided for @labelChooseNewPass. + /// + /// In en, this message translates to: + /// **'Choose a new password'** + String get labelChooseNewPass; + + /// No description provided for @labelCreatePass. + /// + /// In en, this message translates to: + /// **'Password'** + String get labelCreatePass; + + /// No description provided for @informYourPassHasBeenReset. + /// + /// In en, this message translates to: + /// **'Your password has been reset'** + String get informYourPassHasBeenReset; + + /// No description provided for @informRedirectLogin. + /// + /// In en, this message translates to: + /// **'You’ll need to log in with your new password. Hang on, we’re redirecting you.'** + String get informRedirectLogin; + + /// No description provided for @actionResetPass. + /// + /// In en, this message translates to: + /// **'Reset my password'** + String get actionResetPass; + + /// No description provided for @informInvalidPasswordFormat. + /// + /// In en, this message translates to: + /// **'Please enter a valid password format'** + String get informInvalidPasswordFormat; + + /// No description provided for @labelCheckEmail. + /// + /// In en, this message translates to: + /// **'Check your email'** + String get labelCheckEmail; + + /// A message with a single parameter + /// + /// In en, this message translates to: + /// **'We’ve sent a message to {email} with a link to reset your password.'** + String informSendResetPasswordEmail(String email); + + /// No description provided for @informResetPassByEmail. + /// + /// In en, this message translates to: + /// **'We\'ll email you instructions to reset your password.'** + String get informResetPassByEmail; + + /// No description provided for @labelSelectCountry. + /// + /// In en, this message translates to: + /// **'Where do you live?'** + String get labelSelectCountry; + + /// No description provided for @labelChooseCountry. + /// + /// In en, this message translates to: + /// **'Choose country'** + String get labelChooseCountry; + + /// No description provided for @warnCountryNotAvailable. + /// + /// In en, this message translates to: + /// **'Unfortunately, Deriv is not available in your country.'** + String get warnCountryNotAvailable; + + /// No description provided for @actionNext. + /// + /// In en, this message translates to: + /// **'Next'** + String get actionNext; + + /// No description provided for @labelEmailIssueHeader. + /// + /// In en, this message translates to: + /// **'If you don\'t see an email from us within a few minutes, a few things could have happened:'** + String get labelEmailIssueHeader; + + /// No description provided for @labelEmailIssueSpam. + /// + /// In en, this message translates to: + /// **'The email is in your spam folder (Sometimes things get lost there).'** + String get labelEmailIssueSpam; + + /// No description provided for @labelEmailIssueWrongEmail. + /// + /// In en, this message translates to: + /// **'You accidentally gave us another email address (Usually a work or a personal one instead of the one you meant).'** + String get labelEmailIssueWrongEmail; + + /// No description provided for @labelEmailIssueTypo. + /// + /// In en, this message translates to: + /// **'The email address you entered had a mistake or typo (happens to the best of us).'** + String get labelEmailIssueTypo; + + /// No description provided for @labelEmailIssueFirewall. + /// + /// In en, this message translates to: + /// **'We can\'t deliver the email to this address (Usually because of firewalls or filtering).'** + String get labelEmailIssueFirewall; + + /// No description provided for @actionReenterEmail. + /// + /// In en, this message translates to: + /// **'Re-enter your email and try again'** + String get actionReenterEmail; + + /// No description provided for @labelKeepPassword. + /// + /// In en, this message translates to: + /// **'Keep your account secure with a password'** + String get labelKeepPassword; + + /// No description provided for @labelCreatePassword. + /// + /// In en, this message translates to: + /// **'Create a password'** + String get labelCreatePassword; + + /// No description provided for @actionStartTrading. + /// + /// In en, this message translates to: + /// **'Start trading'** + String get actionStartTrading; + + /// No description provided for @actionPrevious. + /// + /// In en, this message translates to: + /// **'Previous'** + String get actionPrevious; + + /// No description provided for @labelSignUp. + /// + /// In en, this message translates to: + /// **'Sign up'** + String get labelSignUp; + + /// No description provided for @labelOrSignUpWith. + /// + /// In en, this message translates to: + /// **'Or sign up with'** + String get labelOrSignUpWith; + + /// No description provided for @labelReferralInfoTitle. + /// + /// In en, this message translates to: + /// **'Affiliate referral code'** + String get labelReferralInfoTitle; + + /// No description provided for @infoReferralInfoDescription. + /// + /// In en, this message translates to: + /// **'An alphanumeric code provided by a Deriv affiliate, applicable for email sign-ups only.'** + String get infoReferralInfoDescription; + + /// No description provided for @labelGotReferralCode. + /// + /// In en, this message translates to: + /// **'Got a referral code?'** + String get labelGotReferralCode; + + /// No description provided for @labelHaveAccount. + /// + /// In en, this message translates to: + /// **'Already have an account?'** + String get labelHaveAccount; + + /// No description provided for @actionCreateAccount. + /// + /// In en, this message translates to: + /// **'Create free demo account'** + String get actionCreateAccount; + + /// No description provided for @informInvalidReferralCode. + /// + /// In en, this message translates to: + /// **'The referral code you entered is invalid. Check and try again.'** + String get informInvalidReferralCode; + + /// No description provided for @labelVerifyYourEmail. + /// + /// In en, this message translates to: + /// **'Verify your email'** + String get labelVerifyYourEmail; + + /// No description provided for @labelThanksEmail. + /// + /// In en, this message translates to: + /// **'Thanks for verifying your email'** + String get labelThanksEmail; + + /// No description provided for @informLetsContinue. + /// + /// In en, this message translates to: + /// **'Let\'s continue.'** + String get informLetsContinue; + + /// No description provided for @actionContinue. + /// + /// In en, this message translates to: + /// **'Continue'** + String get actionContinue; + + /// No description provided for @labelSearchCountry. + /// + /// In en, this message translates to: + /// **'Search country'** + String get labelSearchCountry; + + /// A message with a single parameter + /// + /// In en, this message translates to: + /// **'We\'ve sent a message to {email} with a link to activate your account.'** + String informVerificationEmailSent(String email); + + /// No description provided for @actionEmailNotReceived. + /// + /// In en, this message translates to: + /// **'Didn\'t receive your email?'** + String get actionEmailNotReceived; + + /// No description provided for @informPasswordPolicy. + /// + /// In en, this message translates to: + /// **'Your password must have:'** + String get informPasswordPolicy; + + /// No description provided for @informPasswordPolicyLength. + /// + /// In en, this message translates to: + /// **'8-25 characters'** + String get informPasswordPolicyLength; + + /// No description provided for @informPasswordPolicyLowerAndUpper. + /// + /// In en, this message translates to: + /// **'Upper and lower case letters'** + String get informPasswordPolicyLowerAndUpper; + + /// No description provided for @informPasswordPolicyNumber. + /// + /// In en, this message translates to: + /// **'At least one number'** + String get informPasswordPolicyNumber; + + /// No description provided for @warnPasswordContainsSymbol. + /// + /// In en, this message translates to: + /// **'Use symbols for strong password.'** + String get warnPasswordContainsSymbol; + + /// No description provided for @labelReferralCode. + /// + /// In en, this message translates to: + /// **'Referral Code'** + String get labelReferralCode; + + /// No description provided for @actionTryAgain. + /// + /// In en, this message translates to: + /// **'Try Again'** + String get actionTryAgain; + + /// No description provided for @informInvalid2FACode. + /// + /// In en, this message translates to: + /// **'The code you entered is invalid. Check and try again.'** + String get informInvalid2FACode; + + /// No description provided for @informFailedAuthentication. + /// + /// In en, this message translates to: + /// **'Your email or password may be incorrect. Did you sign up with a social account? Check and try again.'** + String get informFailedAuthentication; + + /// No description provided for @informDeactivatedAccount. + /// + /// In en, this message translates to: + /// **'Your account is deactivated.'** + String get informDeactivatedAccount; + + /// No description provided for @informFailedAuthorization. + /// + /// In en, this message translates to: + /// **'Authorization failed.'** + String get informFailedAuthorization; + + /// No description provided for @informInvalidResidence. + /// + /// In en, this message translates to: + /// **'Invalid residence.'** + String get informInvalidResidence; + + /// No description provided for @informInvalidCredentials. + /// + /// In en, this message translates to: + /// **'Invalid credentials.'** + String get informInvalidCredentials; + + /// No description provided for @informMissingOtp. + /// + /// In en, this message translates to: + /// **'Missing one-time password.'** + String get informMissingOtp; + + /// No description provided for @informSelfClosed. + /// + /// In en, this message translates to: + /// **'Your account has been closed.'** + String get informSelfClosed; + + /// No description provided for @informUnexpectedError. + /// + /// In en, this message translates to: + /// **'An unexpected error occurred.'** + String get informUnexpectedError; + + /// No description provided for @informUnsupportedCountry. + /// + /// In en, this message translates to: + /// **'Your country is not supported.'** + String get informUnsupportedCountry; + + /// No description provided for @informExpiredAccount. + /// + /// In en, this message translates to: + /// **'Your account is expired'** + String get informExpiredAccount; + + /// No description provided for @labelCountryConsentBrazil. + /// + /// In en, this message translates to: + /// **'I hereby confirm that my request for opening an account with Deriv to trade OTC products issued and offered exclusively outside Brazil was initiated by me. I fully understand that Deriv is not regulated by CVM and by approaching Deriv I intend to set up a relation with a foreign company.'** + String get labelCountryConsentBrazil; + + /// No description provided for @informConnectionError. + /// + /// In en, this message translates to: + /// **'Connection error. Please try again later.'** + String get informConnectionError; + + /// No description provided for @informSwitchAccountError. + /// + /// In en, this message translates to: + /// **'Switch account error. Please try again later.'** + String get informSwitchAccountError; + + /// No description provided for @labelDeveloper. + /// + /// In en, this message translates to: + /// **'Developer'** + String get labelDeveloper; + + /// No description provided for @labelEndpoint. + /// + /// In en, this message translates to: + /// **'Endpoint'** + String get labelEndpoint; + + /// No description provided for @semanticEndpoint. + /// + /// In en, this message translates to: + /// **'Endpoint'** + String get semanticEndpoint; + + /// No description provided for @warnInvalidEndpoint. + /// + /// In en, this message translates to: + /// **'invalid endpoint'** + String get warnInvalidEndpoint; + + /// No description provided for @labelApplicationID. + /// + /// In en, this message translates to: + /// **'Application ID'** + String get labelApplicationID; + + /// No description provided for @semanticApplicationID. + /// + /// In en, this message translates to: + /// **'Application ID'** + String get semanticApplicationID; + + /// No description provided for @warnInvalidApplicationID. + /// + /// In en, this message translates to: + /// **'invalid application ID'** + String get warnInvalidApplicationID; + + /// No description provided for @labelLanguage. + /// + /// In en, this message translates to: + /// **'Language'** + String get labelLanguage; +} + +class _DerivAuthLocalizationsDelegate extends LocalizationsDelegate { + const _DerivAuthLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupDerivAuthLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => ['ar', 'bn', 'de', 'en', 'es', 'fr', 'it', 'km', 'ko', 'mn', 'pl', 'pt', 'ru', 'si', 'sw', 'th', 'tr', 'uz', 'vi', 'zh'].contains(locale.languageCode); + + @override + bool shouldReload(_DerivAuthLocalizationsDelegate old) => false; +} + +DerivAuthLocalizations lookupDerivAuthLocalizations(Locale locale) { + + // Lookup logic when language+country codes are specified. + switch (locale.languageCode) { + case 'zh': { + switch (locale.countryCode) { + case 'CN': return DerivAuthLocalizationsZhCn(); +case 'TW': return DerivAuthLocalizationsZhTw(); + } + break; + } + } + + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'ar': return DerivAuthLocalizationsAr(); + case 'bn': return DerivAuthLocalizationsBn(); + case 'de': return DerivAuthLocalizationsDe(); + case 'en': return DerivAuthLocalizationsEn(); + case 'es': return DerivAuthLocalizationsEs(); + case 'fr': return DerivAuthLocalizationsFr(); + case 'it': return DerivAuthLocalizationsIt(); + case 'km': return DerivAuthLocalizationsKm(); + case 'ko': return DerivAuthLocalizationsKo(); + case 'mn': return DerivAuthLocalizationsMn(); + case 'pl': return DerivAuthLocalizationsPl(); + case 'pt': return DerivAuthLocalizationsPt(); + case 'ru': return DerivAuthLocalizationsRu(); + case 'si': return DerivAuthLocalizationsSi(); + case 'sw': return DerivAuthLocalizationsSw(); + case 'th': return DerivAuthLocalizationsTh(); + case 'tr': return DerivAuthLocalizationsTr(); + case 'uz': return DerivAuthLocalizationsUz(); + case 'vi': return DerivAuthLocalizationsVi(); + case 'zh': return DerivAuthLocalizationsZh(); + } + + throw FlutterError( + 'DerivAuthLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.' + ); +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_ar.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_ar.dart new file mode 100644 index 000000000..158f2adef --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_ar.dart @@ -0,0 +1,276 @@ +import 'deriv_auth_localizations.dart'; + +/// The translations for Arabic (`ar`). +class DerivAuthLocalizationsAr extends DerivAuthLocalizations { + DerivAuthLocalizationsAr([String locale = 'ar']) : super(locale); + + @override + String get labelNotAvailable => 'غير متاح'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return '$app غير متاح في بلدك'; + } + + @override + String get actionOk => 'حسنا'; + + @override + String get warnNotAvailableCountries => 'إذا كانت لديك أي أسئلة، فاتصل بنا عبر '; + + @override + String get labelLiveChat => 'دردشة مباشرة'; + + @override + String get actionSignUpForFree => 'قم بالتسجيل مجانًا'; + + @override + String get actionLogin => 'تسجيل دخول'; + + @override + String get labelTwoFactorAuth => 'المصادقة الثنائية (2FA)'; + + @override + String get informEnterTwoFactorAuthCode => 'أدخل الرمز المكون من 6 أرقام من تطبيق المصادقة على هاتفك.'; + + @override + String get labelTwoFactorAuthenticationCode => 'كود 2FA'; + + @override + String get actionProceed => 'تقدم'; + + @override + String get labelLogIn => 'تسجيل دخول'; + + @override + String get informLoginOptions => 'أو قم بتسجيل الدخول باستخدام'; + + @override + String get labelEmail => 'البريد الإلكتروني'; + + @override + String get labelPassword => 'كلمة المرور'; + + @override + String get actionForgotPassword => 'هل نسيت كلمة المرور؟'; + + @override + String get labelDontHaveAnAccountYet => 'ليس لديك حساب حتى الآن؟'; + + @override + String get actionCreateANewAccount => 'إنشاء حساب جديد'; + + @override + String get informInvalidEmailFormat => 'أدخل عنوان بريد إلكتروني صالح'; + + @override + String get warnPasswordLength => 'يجب إدخال 8-25 حرفًا.'; + + @override + String get labelResetPassword => 'إعادة تعيين كلمة المرور'; + + @override + String get labelChooseNewPass => 'اختر كلمة مرور جديدة'; + + @override + String get labelCreatePass => 'كلمة المرور'; + + @override + String get informYourPassHasBeenReset => 'تم إعادة تعيين كلمة المرور الخاصة بك'; + + @override + String get informRedirectLogin => 'ستحتاج إلى تسجيل الدخول باستخدام كلمة المرور الجديدة. انتظر، سنعيد توجيهك.'; + + @override + String get actionResetPass => 'إعادة تعيين كلمة المرور الخاصة بي'; + + @override + String get informInvalidPasswordFormat => 'يرجى إدخال صيغة كلمة مرور صالحة'; + + @override + String get labelCheckEmail => 'تحقق من بريدك الإلكتروني'; + + @override + String informSendResetPasswordEmail(String email) { + return 'لقد أرسلنا رسالة إلى $email مع رابط لإعادة تعيين كلمة المرور الخاصة بك.'; + } + + @override + String get informResetPassByEmail => 'سنرسل لك تعليمات عبر البريد الإلكتروني لإعادة تعيين كلمة المرور الخاصة بك.'; + + @override + String get labelSelectCountry => 'أين تعيش؟'; + + @override + String get labelChooseCountry => 'اختر البلد'; + + @override + String get warnCountryNotAvailable => 'للأسف، Deriv غير متوفر في بلدك.'; + + @override + String get actionNext => 'التالي'; + + @override + String get labelEmailIssueHeader => 'إذا لم تشاهد بريدًا إلكترونيًا منا في غضون بضع دقائق، فقد تحدث بعض الأشياء:'; + + @override + String get labelEmailIssueSpam => 'البريد الإلكتروني موجود في مجلد الرسائل غير المرغوب فيها (أحيانًا تضيع الأشياء هناك).'; + + @override + String get labelEmailIssueWrongEmail => 'لقد أعطيتنا عن طريق الخطأ عنوان بريد إلكتروني آخر (عادةً ما يكون عنوان عمل أو عنوان شخصي بدلاً من العنوان الذي قصدته).'; + + @override + String get labelEmailIssueTypo => 'عنوان البريد الإلكتروني الذي أدخلته به خطأ أو خطأ مطبعي (يحدث لأفضل منا).'; + + @override + String get labelEmailIssueFirewall => 'لا يمكننا تسليم البريد الإلكتروني إلى هذا العنوان (عادةً بسبب جدران الحماية أو التصفية).'; + + @override + String get actionReenterEmail => 'أعد إدخال بريدك الإلكتروني وحاول مرة أخرى'; + + @override + String get labelKeepPassword => 'حافظ على أمان حسابك باستخدام كلمة مرور'; + + @override + String get labelCreatePassword => 'قم بإنشاء كلمة مرور'; + + @override + String get actionStartTrading => 'ابدأ التداول'; + + @override + String get actionPrevious => 'السابق'; + + @override + String get labelSignUp => 'قم بالتسجيل'; + + @override + String get labelOrSignUpWith => 'أو قم بالتسجيل باستخدام'; + + @override + String get labelReferralInfoTitle => 'رمز الإحالة التابع'; + + @override + String get infoReferralInfoDescription => 'رمز أبجدي رقمي مقدم من شركة تابعة لـ Deriv، ينطبق على عمليات الاشتراك في البريد الإلكتروني فقط.'; + + @override + String get labelGotReferralCode => 'هل لديك رمز إحالة؟'; + + @override + String get labelHaveAccount => 'لديك حساب بالفعل؟'; + + @override + String get actionCreateAccount => 'إنشاء حساب تجريبي مجاني'; + + @override + String get informInvalidReferralCode => 'رمز الإحالة الذي أدخلته غير صالح. تحقق وحاول مرة أخرى.'; + + @override + String get labelVerifyYourEmail => 'تحقق من بريدك الإلكتروني'; + + @override + String get labelThanksEmail => 'شكرًا لك على التحقق من بريدك الإلكتروني'; + + @override + String get informLetsContinue => 'دعونا نواصل.'; + + @override + String get actionContinue => 'استمر'; + + @override + String get labelSearchCountry => 'ابحث عن البلد'; + + @override + String informVerificationEmailSent(String email) { + return 'لقد أرسلنا رسالة إلى $email رابط لتفعيل حسابك.'; + } + + @override + String get actionEmailNotReceived => 'لم تستلم بريدك الإلكتروني؟'; + + @override + String get informPasswordPolicy => 'يجب أن تحتوي كلمة المرور الخاصة بك على:'; + + @override + String get informPasswordPolicyLength => '8-25 حرفًا'; + + @override + String get informPasswordPolicyLowerAndUpper => 'الأحرف الكبيرة والصغيرة'; + + @override + String get informPasswordPolicyNumber => 'رقم واحد على الأقل'; + + @override + String get warnPasswordContainsSymbol => 'استخدم الرموز لكلمة مرور قوية.'; + + @override + String get labelReferralCode => 'رمز الإحالة'; + + @override + String get actionTryAgain => 'حاول مرة أخرى'; + + @override + String get informInvalid2FACode => 'الرمز الذي أدخلته غير صالح. تحقق وحاول مرة أخرى.'; + + @override + String get informFailedAuthentication => 'قد يكون بريدك الإلكتروني أو كلمة المرور غير صحيحة. هل قمت بالتسجيل باستخدام حساب اجتماعي؟ تحقق وحاول مرة أخرى.'; + + @override + String get informDeactivatedAccount => 'تم إلغاء تنشيط حسابك.'; + + @override + String get informFailedAuthorization => 'فشلت عملية التخويل.'; + + @override + String get informInvalidResidence => 'إقامة غير صالحة.'; + + @override + String get informInvalidCredentials => 'بيانات اعتماد غير صالحة.'; + + @override + String get informMissingOtp => 'كلمة مرور مفقودة لمرة واحدة.'; + + @override + String get informSelfClosed => 'تم إغلاق الحساب الخاص بك.'; + + @override + String get informUnexpectedError => 'حدث خطأ غير متوقع.'; + + @override + String get informUnsupportedCountry => 'بلدك غير مدعوم.'; + + @override + String get informExpiredAccount => 'انتهت صلاحية حسابك'; + + @override + String get labelCountryConsentBrazil => 'أؤكد بموجب هذا أن طلبي لفتح حساب مع Deriv لتداول منتجات OTC الصادرة والمعروضة حصريًا خارج البرازيل قد بادرت به. أفهم تمامًا أن Deriv لا تخضع للتنظيم من قبل CVM ومن خلال الاتصال بـ Deriv أعتزم إقامة علاقة مع شركة أجنبية.'; + + @override + String get informConnectionError => 'خطأ اتصال. يرجى المحاولة مرة أخرى لاحقًا.'; + + @override + String get informSwitchAccountError => 'خطأ في تبديل الحساب. يرجى المحاولة مرة أخرى لاحقًا.'; + + @override + String get labelDeveloper => 'المطوّر'; + + @override + String get labelEndpoint => 'نقطة النهاية'; + + @override + String get semanticEndpoint => 'نقطة النهاية'; + + @override + String get warnInvalidEndpoint => 'نقطة نهاية غير صالحة'; + + @override + String get labelApplicationID => 'معرف التطبيق'; + + @override + String get semanticApplicationID => 'معرف التطبيق'; + + @override + String get warnInvalidApplicationID => 'معرف تطبيق غير صالح'; + + @override + String get labelLanguage => 'اللغة'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_bn.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_bn.dart new file mode 100644 index 000000000..992d068f2 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_bn.dart @@ -0,0 +1,276 @@ +import 'deriv_auth_localizations.dart'; + +/// The translations for Bengali Bangla (`bn`). +class DerivAuthLocalizationsBn extends DerivAuthLocalizations { + DerivAuthLocalizationsBn([String locale = 'bn']) : super(locale); + + @override + String get labelNotAvailable => 'উপলব্ধ নেই'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return '$app আপনার দেশে উপলব্ধ নেই'; + } + + @override + String get actionOk => 'ওকে'; + + @override + String get warnNotAvailableCountries => 'আপনার যদি কোনও প্রশ্ন থাকে তবে আমাদের মাধ্যমে যোগাযোগ করুন '; + + @override + String get labelLiveChat => 'লাইভ চ্যাট'; + + @override + String get actionSignUpForFree => 'বিনামূল্যে সাইন আপ করুন'; + + @override + String get actionLogin => 'লগ ইন'; + + @override + String get labelTwoFactorAuth => 'দুই ফ্যাক্টর প্রমাণীকরণ'; + + @override + String get informEnterTwoFactorAuthCode => 'আপনার ফোনে প্রমাণীকরণকারী অ্যাপ থেকে 6-অঙ্কের কোড লিখুন।'; + + @override + String get labelTwoFactorAuthenticationCode => '2FA কোড'; + + @override + String get actionProceed => 'এগিয়ে যাও'; + + @override + String get labelLogIn => 'লগ ইন'; + + @override + String get informLoginOptions => 'অথবা লগ ইন করুন'; + + @override + String get labelEmail => 'ই-মেইল'; + + @override + String get labelPassword => 'পাসওয়ার্ড'; + + @override + String get actionForgotPassword => 'পাসওয়ার্ড ভুলে গেছেন?'; + + @override + String get labelDontHaveAnAccountYet => 'এখনও অ্যাকাউন্ট নেই?'; + + @override + String get actionCreateANewAccount => 'একটি নতুন অ্যাকাউন্ট তৈরি করুন'; + + @override + String get informInvalidEmailFormat => 'একটি বৈধ ইমেল ঠিকানা লিখুন'; + + @override + String get warnPasswordLength => 'আপনার 8-25 অক্ষর লিখতে হবে।'; + + @override + String get labelResetPassword => 'পাসওয়ার্ড রিসেট'; + + @override + String get labelChooseNewPass => 'একটি নতুন পাসওয়ার্ড বেছে নিন'; + + @override + String get labelCreatePass => 'পাসওয়ার্ড'; + + @override + String get informYourPassHasBeenReset => 'আপনার পাসওয়ার্ড পুনরায় সেট করা হয়েছে'; + + @override + String get informRedirectLogin => 'আপনাকে আপনার নতুন পাসওয়ার্ড দিয়ে লগ ইন করতে হবে। ধরে রাখুন, আমরা আপনাকে পুনর্নির্দেশ করছি।'; + + @override + String get actionResetPass => 'আমার পাসওয়ার্ড রিসেট'; + + @override + String get informInvalidPasswordFormat => 'অনুগ্রহ করে একটি বৈধ পাসওয়ার্ড ফর্ম্যাট লিখুন'; + + @override + String get labelCheckEmail => 'আপনার ইমেইল চেক করুন'; + + @override + String informSendResetPasswordEmail(String email) { + return 'আমরা আপনার পাসওয়ার্ড পুনরায় সেট $email করার জন্য একটি লিঙ্ক সহ একটি বার্তা পাঠিয়েছি।'; + } + + @override + String get informResetPassByEmail => 'আপনার পাসওয়ার্ড পুনরায় সেট করার জন্য আমরা আপনাকে নির্দেশাবলী ইমেইল করব।'; + + @override + String get labelSelectCountry => 'আপনি কোথায় থাকেন?'; + + @override + String get labelChooseCountry => 'দেশ নির্বাচন করুন'; + + @override + String get warnCountryNotAvailable => 'দুর্ভাগ্যবশত, Deriv আপনার দেশে পাওয়া যায় না।'; + + @override + String get actionNext => 'পরবর্তী'; + + @override + String get labelEmailIssueHeader => 'যদি আপনি কয়েক মিনিটের মধ্যে আমাদের কাছ থেকে কোন ইমেল না দেখেন, তবে কিছু ঘটনা ঘটতে পারত:'; + + @override + String get labelEmailIssueSpam => 'ইমেলটি আপনার স্প্যাম ফোল্ডারে রয়েছে (কখনও কখনও কিছু সেখানে হারিয়ে যায়)।'; + + @override + String get labelEmailIssueWrongEmail => 'আপনি ঘটনাক্রমে আমাদের অন্য একটি ইমেল ঠিকানা দিয়েছেন (সাধারণত একটি কাজ বা আপনি বোঝানো এক পরিবর্তে একটি ব্যক্তিগত এক)।'; + + @override + String get labelEmailIssueTypo => 'আপনার প্রবেশ করা ইমেল ঠিকানাটি একটি ভুল বা টাইপো ছিল (আমাদের সেরা ক্ষেত্রে ঘটে)।'; + + @override + String get labelEmailIssueFirewall => 'আমরা এই ঠিকানায় ইমেলটি সরবরাহ করতে পারি না (সাধারণত ফায়ারওয়াল বা ফিল্টারিংয়ের কারণে)।'; + + @override + String get actionReenterEmail => 'আপনার ইমেইল পুনরায় প্রবেশ করুন এবং পুনরায় চেষ্টা করুন'; + + @override + String get labelKeepPassword => 'পাসওয়ার্ড দিয়ে আপনার অ্যাকাউন্টকে নিরাপদ রাখুন'; + + @override + String get labelCreatePassword => 'পাসওয়ার্ড তৈরি করুন'; + + @override + String get actionStartTrading => 'ট্রেডিং শুরু করুন'; + + @override + String get actionPrevious => 'পূর্ববর্তী'; + + @override + String get labelSignUp => 'সাইন আপ'; + + @override + String get labelOrSignUpWith => 'অথবা সাইন আপ করুন'; + + @override + String get labelReferralInfoTitle => 'অ্যাফিলিয়েট রেফারেল'; + + @override + String get infoReferralInfoDescription => 'একটি Deriv অ্যাফিলিয়েট দ্বারা প্রদত্ত একটি আলফানিমেরিক কোড, শুধুমাত্র ইমেল সাইন আপের জন্য প্রযোজ্য।'; + + @override + String get labelGotReferralCode => 'একটি রেফারেল কোড পেয়েছেন?'; + + @override + String get labelHaveAccount => 'ইতিমধ্যে একটি অ্যাকাউন্ট আছে কি?'; + + @override + String get actionCreateAccount => 'ফ্রি ডেমো অ্যাকাউন্ট তৈরি করুন'; + + @override + String get informInvalidReferralCode => 'আপনি যে রেফারেল কোড লিখেছেন তা অবৈধ। চেক করুন এবং আবার চেষ্টা করুন।'; + + @override + String get labelVerifyYourEmail => 'আমার ইমেইল যাচাই'; + + @override + String get labelThanksEmail => 'আপনার ইমেইল যাচাই করার জন্য ধন্যবাদ'; + + @override + String get informLetsContinue => 'চলুন চালিয়ে যাক।'; + + @override + String get actionContinue => 'চালিয়ে যান'; + + @override + String get labelSearchCountry => 'দেশ অনুসন্ধান করুন'; + + @override + String informVerificationEmailSent(String email) { + return 'আমরা আপনার অ্যাকাউন্ট সক্রিয় করার জন্য $email একটি লিঙ্ক সহ একটি বার্তা পাঠিয়েছি।'; + } + + @override + String get actionEmailNotReceived => 'আপনার ইমেইল কি পাওয়া যায়নি?'; + + @override + String get informPasswordPolicy => 'আপনার পাসওয়ার্ড অবশ্যই থাকতে হবে:'; + + @override + String get informPasswordPolicyLength => '8-25 অক্ষর'; + + @override + String get informPasswordPolicyLowerAndUpper => 'উচ্চ এবং নিম্ন কেস অক্ষর'; + + @override + String get informPasswordPolicyNumber => 'অন্তত একটি সংখ্যা'; + + @override + String get warnPasswordContainsSymbol => 'শক্তিশালী পাসওয়ার্ড জন্য প্রতীক ব্যবহার'; + + @override + String get labelReferralCode => 'রেফারেল কোড'; + + @override + String get actionTryAgain => 'পুনরায় চেষ্টা করুন'; + + @override + String get informInvalid2FACode => 'আপনি যে কোডটি প্রবেশ করেছেন তা অবৈধ। চেক করুন এবং আবার চেষ্টা করুন।'; + + @override + String get informFailedAuthentication => 'আপনার ইমেল বা পাসওয়ার্ড ভুল হতে পারে। আপনি কি একটি সামাজিক অ্যাকাউন্ট দিয়ে সাইন আপ করেছেন? চেক করুন এবং আবার চেষ্টা করুন।'; + + @override + String get informDeactivatedAccount => 'আপনার অ্যাকাউন্ট নিষ্ক্রিয় করা হয়েছে।'; + + @override + String get informFailedAuthorization => 'অনুমোদন ব্যর্থ হয়েছে।'; + + @override + String get informInvalidResidence => 'অবৈধ বাসস্থান।'; + + @override + String get informInvalidCredentials => 'অবৈধ শংসাপত্র।'; + + @override + String get informMissingOtp => 'একবার পাসওয়ার্ড অনুপস্থিত।'; + + @override + String get informSelfClosed => 'আপনার অ্যাকাউন্ট বন্ধ করা হয়েছে।'; + + @override + String get informUnexpectedError => 'অপ্রত্যাশিত ত্রুটি ঘটেছে।'; + + @override + String get informUnsupportedCountry => 'আপনার দেশ সমর্থিত হয়নি।'; + + @override + String get informExpiredAccount => 'আপনার অ্যাকাউন্টের মেয়াদ শেষ হয়েছে'; + + @override + String get labelCountryConsentBrazil => 'আমি এর মাধ্যমে নিশ্চিত করি যে ব্রাজিলের বাইরে একচেটিয়াভাবে জারি করা এবং দেওয়া ওটিসি পণ্যগুলি বাণিজ্য করার জন্য ডেরিভের সাথে একটি অ্যাকাউন্ট খোলার জন্য আমার অনুরোধ আমার দ্বারা শুরু আমি পুরোপুরি বুঝতে পারি যে ডেরিভ সিভিএম দ্বারা নিয়ন্ত্রিত নয় এবং ডেরিভের কাছে পৌঁছানোর মাধ্যমে আমি একটি বিদেশী সংস্থার সাথে সম্পর্ক স্থাপন করার ইচ্ছা করি।'; + + @override + String get informConnectionError => 'সংযোগ ত্রুটি। দয়া করে পরে আবার চেষ্টা করুন।'; + + @override + String get informSwitchAccountError => 'অ্যাকাউন্ট ত্রুটি পরিবর্তন করুন দয়া করে পরে আবার চেষ্টা করুন।'; + + @override + String get labelDeveloper => 'ডেভেলপার'; + + @override + String get labelEndpoint => 'শেষপ্রান্ত'; + + @override + String get semanticEndpoint => 'শেষপ্রান্ত'; + + @override + String get warnInvalidEndpoint => 'অবৈধ শেষ পয়েন্ট'; + + @override + String get labelApplicationID => 'অ্যাপলিকেশন আইডি'; + + @override + String get semanticApplicationID => 'অ্যাপলিকেশন আইডি'; + + @override + String get warnInvalidApplicationID => 'অবৈধ অ্যাপ্লিকেশন আইডি'; + + @override + String get labelLanguage => 'ভাষা'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_de.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_de.dart new file mode 100644 index 000000000..528b8ff2b --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_de.dart @@ -0,0 +1,276 @@ +import 'deriv_auth_localizations.dart'; + +/// The translations for German (`de`). +class DerivAuthLocalizationsDe extends DerivAuthLocalizations { + DerivAuthLocalizationsDe([String locale = 'de']) : super(locale); + + @override + String get labelNotAvailable => 'Nicht verfügbar'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return '$app ist in deinem Land nicht verfügbar'; + } + + @override + String get actionOk => 'Okay'; + + @override + String get warnNotAvailableCountries => 'Wenn Sie Fragen haben, kontaktieren Sie uns über '; + + @override + String get labelLiveChat => 'Live-chat'; + + @override + String get actionSignUpForFree => 'Melde dich kostenlos an'; + + @override + String get actionLogin => 'Anmelden'; + + @override + String get labelTwoFactorAuth => 'Zwei-Faktor-Authentifizierung'; + + @override + String get informEnterTwoFactorAuthCode => 'Geben Sie den 6-stelligen Code aus der Authentifizierungs-App auf Ihrem Telefon ein.'; + + @override + String get labelTwoFactorAuthenticationCode => '2FA-Kode'; + + @override + String get actionProceed => 'Fortfahren'; + + @override + String get labelLogIn => 'Anmelden'; + + @override + String get informLoginOptions => 'Oder melden Sie sich an mit'; + + @override + String get labelEmail => 'E-Mail'; + + @override + String get labelPassword => 'Passwort'; + + @override + String get actionForgotPassword => 'Passwort vergessen?'; + + @override + String get labelDontHaveAnAccountYet => 'Sie haben noch kein Konto?'; + + @override + String get actionCreateANewAccount => 'Erstelle ein neues Konto'; + + @override + String get informInvalidEmailFormat => 'Geben Sie eine gültige E-Mail Adresse ein'; + + @override + String get warnPasswordLength => 'Sie sollten 6-25 Zeichen eingeben.'; + + @override + String get labelResetPassword => 'Passwort zurücksetzen'; + + @override + String get labelChooseNewPass => 'Wählen Sie ein neues Passwort'; + + @override + String get labelCreatePass => 'Passwort'; + + @override + String get informYourPassHasBeenReset => 'Ihr Passwort wurde zurückgesetzt'; + + @override + String get informRedirectLogin => 'Sie müssen sich mit Ihrem neuen Passwort anmelden. Warten Sie, wir leiten Sie weiter.'; + + @override + String get actionResetPass => 'Setze mein Passwort zurück'; + + @override + String get informInvalidPasswordFormat => 'Bitte geben Sie ein gültiges Passwortformat ein'; + + @override + String get labelCheckEmail => 'Prüfen Sie Ihre E-Mail'; + + @override + String informSendResetPasswordEmail(String email) { + return 'Wir haben eine Nachricht an $email mit einem Link zum Zurücksetzen Ihres Passworts gesendet.'; + } + + @override + String get informResetPassByEmail => 'Wir werden Ihnen per E-Mail Anweisungen zum Zurücksetzen Ihres Passworts zusenden.'; + + @override + String get labelSelectCountry => 'Wo wohnen Sie?'; + + @override + String get labelChooseCountry => 'Land wählen'; + + @override + String get warnCountryNotAvailable => 'Leider ist Deriv in Ihrem Land nicht verfügbar.'; + + @override + String get actionNext => 'Weiter'; + + @override + String get labelEmailIssueHeader => 'Wenn Sie innerhalb weniger Minuten keine E-Mail von uns sehen, könnten einige Dinge passiert sein:'; + + @override + String get labelEmailIssueSpam => 'Die E-Mail befindet sich in Ihrem Spam-Ordner (manchmal gehen dort Dinge verloren).'; + + @override + String get labelEmailIssueWrongEmail => 'Sie haben uns versehentlich eine andere E-Mail-Adresse gegeben (normalerweise eine geschäftliche oder eine persönliche Adresse anstelle der von Ihnen meinten).'; + + @override + String get labelEmailIssueTypo => 'Die von Ihnen eingegebene E-Mail-Adresse hatte einen Fehler oder Tippfehler (passiert den Besten von uns).'; + + @override + String get labelEmailIssueFirewall => 'Wir können die E-Mail an diese Adresse nicht zustellen (in der Regel aufgrund von Firewalls oder Filtern).'; + + @override + String get actionReenterEmail => 'Gib deine E-Mail-Adresse erneut ein und versuche es erneut'; + + @override + String get labelKeepPassword => 'Schützen Sie Ihr Konto mit einem Passwort'; + + @override + String get labelCreatePassword => 'Erstelle ein Passwort'; + + @override + String get actionStartTrading => 'Beginnen Sie zu Handeln'; + + @override + String get actionPrevious => 'Vorherige'; + + @override + String get labelSignUp => 'Anmelden'; + + @override + String get labelOrSignUpWith => 'Oder melden Sie sich bei'; + + @override + String get labelReferralInfoTitle => 'Empfehlungscode für Partner'; + + @override + String get infoReferralInfoDescription => 'Ein alphanumerischer Code, der von einem Deriv-Partner bereitgestellt wird und nur für E-Mail-Anmeldungen gilt.'; + + @override + String get labelGotReferralCode => 'Haben Sie einen Empfehlungscode?'; + + @override + String get labelHaveAccount => 'Haben Sie schon ein Konto?'; + + @override + String get actionCreateAccount => 'Kostenloses Demo-Konto erstellen'; + + @override + String get informInvalidReferralCode => 'Der von Ihnen eingegebene Empfehlungscode ist ungültig. Überprüfen Sie ihn und versuchen Sie es erneut.'; + + @override + String get labelVerifyYourEmail => 'Überprüfen Sie Ihre E-Mail'; + + @override + String get labelThanksEmail => 'Danke, dass du deine E-Mail verifiziert hast'; + + @override + String get informLetsContinue => 'Lassen Sie uns fortfahren.'; + + @override + String get actionContinue => 'Weiter'; + + @override + String get labelSearchCountry => 'Land suchen'; + + @override + String informVerificationEmailSent(String email) { + return 'Wir haben eine Nachricht an $email mit einem Link zur Aktivierung Ihres Kontos gesendet.'; + } + + @override + String get actionEmailNotReceived => 'Haben Sie Ihre E-Mail nicht erhalten?'; + + @override + String get informPasswordPolicy => 'Ihr Passwort muss Folgendes enthalten:'; + + @override + String get informPasswordPolicyLength => '8-25 Zeichen'; + + @override + String get informPasswordPolicyLowerAndUpper => 'Groß- und Kleinbuchstaben'; + + @override + String get informPasswordPolicyNumber => 'Mindestens eine Nummer'; + + @override + String get warnPasswordContainsSymbol => 'Verwenden Sie Symbole für ein sicheres Passwort.'; + + @override + String get labelReferralCode => 'Empfehlungscode'; + + @override + String get actionTryAgain => 'Erneut versuchen'; + + @override + String get informInvalid2FACode => 'Der von Ihnen eingegebene Code ist ungültig. Überprüfen Sie ihn und versuchen Sie es erneut.'; + + @override + String get informFailedAuthentication => 'Ihre E-Mail oder Ihr Passwort ist möglicherweise falsch. Haben Sie sich mit einem sozialen Konto angemeldet? Überprüfen Sie es und versuchen Sie es erneut.'; + + @override + String get informDeactivatedAccount => 'Ihr Konto ist deaktiviert.'; + + @override + String get informFailedAuthorization => 'Die Autorisierung ist fehlgeschlagen.'; + + @override + String get informInvalidResidence => 'Ungültiger Wohnsitz.'; + + @override + String get informInvalidCredentials => 'Ungültige Anmeldeinformationen.'; + + @override + String get informMissingOtp => 'Fehlendes Einmalpasswort.'; + + @override + String get informSelfClosed => 'Ihr Konto wurde geschlossen.'; + + @override + String get informUnexpectedError => 'Ein unerwarteter Fehler ist aufgetreten.'; + + @override + String get informUnsupportedCountry => 'Ihr Land wird nicht unterstützt.'; + + @override + String get informExpiredAccount => 'Ihr Konto ist abgelaufen'; + + @override + String get labelCountryConsentBrazil => 'Ich bestätige hiermit, dass mein Antrag auf Eröffnung eines Kontos bei Deriv für den Handel mit OTC-Produkten, die ausschließlich außerhalb Brasiliens ausgegeben und angeboten werden, von mir initiiert wurde. Ich verstehe voll und ganz, dass Deriv nicht von CVM reguliert wird, und indem ich mich an Deriv wende, beabsichtige ich, eine Beziehung zu einem ausländischen Unternehmen aufzubauen.'; + + @override + String get informConnectionError => 'Verbindungsfehler. Bitte versuchen Sie es später erneut.'; + + @override + String get informSwitchAccountError => 'Fehler beim Kontowechsel. Bitte versuchen Sie es später erneut.'; + + @override + String get labelDeveloper => 'Entwickler'; + + @override + String get labelEndpoint => 'Endpunkt'; + + @override + String get semanticEndpoint => 'Endpunkt'; + + @override + String get warnInvalidEndpoint => 'Ungültiger Endpunkt'; + + @override + String get labelApplicationID => 'Anwendungs-ID'; + + @override + String get semanticApplicationID => 'Anwendungs-ID'; + + @override + String get warnInvalidApplicationID => 'ungültige Anwendungs-ID'; + + @override + String get labelLanguage => 'Sprache'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_en.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_en.dart new file mode 100644 index 000000000..b5fd83709 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_en.dart @@ -0,0 +1,276 @@ +import 'deriv_auth_localizations.dart'; + +/// The translations for English (`en`). +class DerivAuthLocalizationsEn extends DerivAuthLocalizations { + DerivAuthLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get labelNotAvailable => 'Not available'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return '$app isn\'t available in your country'; + } + + @override + String get actionOk => 'OK'; + + @override + String get warnNotAvailableCountries => 'If you have any questions, contact us via '; + + @override + String get labelLiveChat => 'Live chat'; + + @override + String get actionSignUpForFree => 'Sign up for free'; + + @override + String get actionLogin => 'Log in'; + + @override + String get labelTwoFactorAuth => 'Two-factor authentication'; + + @override + String get informEnterTwoFactorAuthCode => 'Enter the 6-digit code from the authenticator app on your phone.'; + + @override + String get labelTwoFactorAuthenticationCode => '2FA code'; + + @override + String get actionProceed => 'Proceed'; + + @override + String get labelLogIn => 'Log in'; + + @override + String get informLoginOptions => 'Or log in with'; + + @override + String get labelEmail => 'Email'; + + @override + String get labelPassword => 'Password'; + + @override + String get actionForgotPassword => 'Forgot password?'; + + @override + String get labelDontHaveAnAccountYet => 'Don’t have an account yet?'; + + @override + String get actionCreateANewAccount => 'Create a new account'; + + @override + String get informInvalidEmailFormat => 'Enter a valid email address'; + + @override + String get warnPasswordLength => 'You should enter 6-25 characters.'; + + @override + String get labelResetPassword => 'Reset password'; + + @override + String get labelChooseNewPass => 'Choose a new password'; + + @override + String get labelCreatePass => 'Password'; + + @override + String get informYourPassHasBeenReset => 'Your password has been reset'; + + @override + String get informRedirectLogin => 'You’ll need to log in with your new password. Hang on, we’re redirecting you.'; + + @override + String get actionResetPass => 'Reset my password'; + + @override + String get informInvalidPasswordFormat => 'Please enter a valid password format'; + + @override + String get labelCheckEmail => 'Check your email'; + + @override + String informSendResetPasswordEmail(String email) { + return 'We’ve sent a message to $email with a link to reset your password.'; + } + + @override + String get informResetPassByEmail => 'We\'ll email you instructions to reset your password.'; + + @override + String get labelSelectCountry => 'Where do you live?'; + + @override + String get labelChooseCountry => 'Choose country'; + + @override + String get warnCountryNotAvailable => 'Unfortunately, Deriv is not available in your country.'; + + @override + String get actionNext => 'Next'; + + @override + String get labelEmailIssueHeader => 'If you don\'t see an email from us within a few minutes, a few things could have happened:'; + + @override + String get labelEmailIssueSpam => 'The email is in your spam folder (Sometimes things get lost there).'; + + @override + String get labelEmailIssueWrongEmail => 'You accidentally gave us another email address (Usually a work or a personal one instead of the one you meant).'; + + @override + String get labelEmailIssueTypo => 'The email address you entered had a mistake or typo (happens to the best of us).'; + + @override + String get labelEmailIssueFirewall => 'We can\'t deliver the email to this address (Usually because of firewalls or filtering).'; + + @override + String get actionReenterEmail => 'Re-enter your email and try again'; + + @override + String get labelKeepPassword => 'Keep your account secure with a password'; + + @override + String get labelCreatePassword => 'Create a password'; + + @override + String get actionStartTrading => 'Start trading'; + + @override + String get actionPrevious => 'Previous'; + + @override + String get labelSignUp => 'Sign up'; + + @override + String get labelOrSignUpWith => 'Or sign up with'; + + @override + String get labelReferralInfoTitle => 'Affiliate referral code'; + + @override + String get infoReferralInfoDescription => 'An alphanumeric code provided by a Deriv affiliate, applicable for email sign-ups only.'; + + @override + String get labelGotReferralCode => 'Got a referral code?'; + + @override + String get labelHaveAccount => 'Already have an account?'; + + @override + String get actionCreateAccount => 'Create free demo account'; + + @override + String get informInvalidReferralCode => 'The referral code you entered is invalid. Check and try again.'; + + @override + String get labelVerifyYourEmail => 'Verify your email'; + + @override + String get labelThanksEmail => 'Thanks for verifying your email'; + + @override + String get informLetsContinue => 'Let\'s continue.'; + + @override + String get actionContinue => 'Continue'; + + @override + String get labelSearchCountry => 'Search country'; + + @override + String informVerificationEmailSent(String email) { + return 'We\'ve sent a message to $email with a link to activate your account.'; + } + + @override + String get actionEmailNotReceived => 'Didn\'t receive your email?'; + + @override + String get informPasswordPolicy => 'Your password must have:'; + + @override + String get informPasswordPolicyLength => '8-25 characters'; + + @override + String get informPasswordPolicyLowerAndUpper => 'Upper and lower case letters'; + + @override + String get informPasswordPolicyNumber => 'At least one number'; + + @override + String get warnPasswordContainsSymbol => 'Use symbols for strong password.'; + + @override + String get labelReferralCode => 'Referral Code'; + + @override + String get actionTryAgain => 'Try Again'; + + @override + String get informInvalid2FACode => 'The code you entered is invalid. Check and try again.'; + + @override + String get informFailedAuthentication => 'Your email or password may be incorrect. Did you sign up with a social account? Check and try again.'; + + @override + String get informDeactivatedAccount => 'Your account is deactivated.'; + + @override + String get informFailedAuthorization => 'Authorization failed.'; + + @override + String get informInvalidResidence => 'Invalid residence.'; + + @override + String get informInvalidCredentials => 'Invalid credentials.'; + + @override + String get informMissingOtp => 'Missing one-time password.'; + + @override + String get informSelfClosed => 'Your account has been closed.'; + + @override + String get informUnexpectedError => 'An unexpected error occurred.'; + + @override + String get informUnsupportedCountry => 'Your country is not supported.'; + + @override + String get informExpiredAccount => 'Your account is expired'; + + @override + String get labelCountryConsentBrazil => 'I hereby confirm that my request for opening an account with Deriv to trade OTC products issued and offered exclusively outside Brazil was initiated by me. I fully understand that Deriv is not regulated by CVM and by approaching Deriv I intend to set up a relation with a foreign company.'; + + @override + String get informConnectionError => 'Connection error. Please try again later.'; + + @override + String get informSwitchAccountError => 'Switch account error. Please try again later.'; + + @override + String get labelDeveloper => 'Developer'; + + @override + String get labelEndpoint => 'Endpoint'; + + @override + String get semanticEndpoint => 'Endpoint'; + + @override + String get warnInvalidEndpoint => 'invalid endpoint'; + + @override + String get labelApplicationID => 'Application ID'; + + @override + String get semanticApplicationID => 'Application ID'; + + @override + String get warnInvalidApplicationID => 'invalid application ID'; + + @override + String get labelLanguage => 'Language'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_es.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_es.dart new file mode 100644 index 000000000..288c8bdb8 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_es.dart @@ -0,0 +1,276 @@ +import 'deriv_auth_localizations.dart'; + +/// The translations for Spanish Castilian (`es`). +class DerivAuthLocalizationsEs extends DerivAuthLocalizations { + DerivAuthLocalizationsEs([String locale = 'es']) : super(locale); + + @override + String get labelNotAvailable => 'No disponible'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return '$app no está disponible en tu país'; + } + + @override + String get actionOk => 'Aceptar'; + + @override + String get warnNotAvailableCountries => 'Si tiene alguna pregunta, póngase en contacto con nosotros a través de '; + + @override + String get labelLiveChat => 'Live Chat'; + + @override + String get actionSignUpForFree => 'Regístrate gratis'; + + @override + String get actionLogin => 'Iniciar sesión'; + + @override + String get labelTwoFactorAuth => 'Autenticación de dos factores'; + + @override + String get informEnterTwoFactorAuthCode => 'Introduzca el código de 6 dígitos de la aplicación autenticadora de su teléfono.'; + + @override + String get labelTwoFactorAuthenticationCode => 'Código 2FA'; + + @override + String get actionProceed => 'Seguir'; + + @override + String get labelLogIn => 'Iniciar sesión'; + + @override + String get informLoginOptions => 'O inicie sesión con'; + + @override + String get labelEmail => 'Correo electrónico'; + + @override + String get labelPassword => 'Contraseña'; + + @override + String get actionForgotPassword => '¿Olvidó la contraseña?'; + + @override + String get labelDontHaveAnAccountYet => '¿Aún no tiene una cuenta?'; + + @override + String get actionCreateANewAccount => 'Crear una cuenta nueva'; + + @override + String get informInvalidEmailFormat => 'Introduzca una dirección de correo electrónico válida'; + + @override + String get warnPasswordLength => 'Debería ingresar de 8 a 25 caracteres.'; + + @override + String get labelResetPassword => 'Restablecer contraseña'; + + @override + String get labelChooseNewPass => 'Elija una contraseña nueva'; + + @override + String get labelCreatePass => 'Contraseña'; + + @override + String get informYourPassHasBeenReset => 'Su contraseña ha sido restablecida'; + + @override + String get informRedirectLogin => 'Tendrá que iniciar sesión con su nueva contraseña. Espere, le estamos redirigiendo.'; + + @override + String get actionResetPass => 'Restablecer mi contraseña'; + + @override + String get informInvalidPasswordFormat => 'Por favor, introduzca un formato de contraseña válido'; + + @override + String get labelCheckEmail => 'Revise su correo electrónico'; + + @override + String informSendResetPasswordEmail(String email) { + return 'Hemos enviado un mensaje a $email con un enlace para restablecer su contraseña.'; + } + + @override + String get informResetPassByEmail => 'Le enviaremos instrucciones por correo electrónico para restablecer su contraseña.'; + + @override + String get labelSelectCountry => '¿Dónde vive?'; + + @override + String get labelChooseCountry => 'Elija su país'; + + @override + String get warnCountryNotAvailable => 'Lamentablemente, Deriv no está disponible en su país.'; + + @override + String get actionNext => 'Siguiente'; + + @override + String get labelEmailIssueHeader => 'Si no ve un correo electrónico nuestro en unos minutos, podrían haber sucedido algunas cosas:'; + + @override + String get labelEmailIssueSpam => 'El correo electrónico está en su carpeta de spam (a veces las cosas se pierden allí).'; + + @override + String get labelEmailIssueWrongEmail => 'Accidentalmente nos proporcionó otra dirección de correo electrónico (tal vez una de trabajo o una personal diferente a la que pensaba utilizar).'; + + @override + String get labelEmailIssueTypo => 'La dirección de correo electrónico que ingresó tenía un fallo o error tipográfico (pasa hasta en las mejores familias).'; + + @override + String get labelEmailIssueFirewall => 'No podemos enviar el correo electrónico a esta dirección (Usualmente debido a firewalls o al filtrado).'; + + @override + String get actionReenterEmail => 'Vuelva a ingresar su correo e inténtelo de nuevo'; + + @override + String get labelKeepPassword => 'Mantenga su cuenta segura con una contraseña'; + + @override + String get labelCreatePassword => 'Cree una contraseña'; + + @override + String get actionStartTrading => 'Comenzar a operar'; + + @override + String get actionPrevious => 'Anterior'; + + @override + String get labelSignUp => 'Crear cuenta'; + + @override + String get labelOrSignUpWith => 'O regístrese con'; + + @override + String get labelReferralInfoTitle => 'Código de referencia del afiliado'; + + @override + String get infoReferralInfoDescription => 'Un código alfanumérico proporcionado por un afiliado de Deriv, aplicable únicamente a las inscripciones por correo electrónico.'; + + @override + String get labelGotReferralCode => '¿Tiene un código de referencia?'; + + @override + String get labelHaveAccount => '¿Ya tiene una cuenta?'; + + @override + String get actionCreateAccount => 'Crear cuenta demo gratis'; + + @override + String get informInvalidReferralCode => 'El código de referencia que ha introducido no es válido. Compruébelo e inténtelo de nuevo.'; + + @override + String get labelVerifyYourEmail => 'Verifique su correo electrónico'; + + @override + String get labelThanksEmail => 'Gracias por verificar su correo electrónico'; + + @override + String get informLetsContinue => 'Continuemos.'; + + @override + String get actionContinue => 'Continuar'; + + @override + String get labelSearchCountry => 'Buscar país'; + + @override + String informVerificationEmailSent(String email) { + return 'Hemos enviado un mensaje a $email con un enlace para activar su cuenta.'; + } + + @override + String get actionEmailNotReceived => '¿No recibió su correo electrónico?'; + + @override + String get informPasswordPolicy => 'Su contraseña debe tener:'; + + @override + String get informPasswordPolicyLength => '8-25 caracteres'; + + @override + String get informPasswordPolicyLowerAndUpper => 'Letras mayúsculas y minúsculas'; + + @override + String get informPasswordPolicyNumber => 'Al menos un número'; + + @override + String get warnPasswordContainsSymbol => 'Utilice símbolos para una contraseña segura.'; + + @override + String get labelReferralCode => 'Código de Referencia'; + + @override + String get actionTryAgain => 'Intentar de nuevo'; + + @override + String get informInvalid2FACode => 'El código que ha introducido no es válido. Compruébelo e inténtelo de nuevo.'; + + @override + String get informFailedAuthentication => 'Su correo electrónico o contraseña pueden ser incorrectos. ¿Se ha registrado con una cuenta social? Compruébelo e inténtelo de nuevo.'; + + @override + String get informDeactivatedAccount => 'Tu cuenta está desactivada.'; + + @override + String get informFailedAuthorization => 'Fallo en la autorización.'; + + @override + String get informInvalidResidence => 'Residencia no válida.'; + + @override + String get informInvalidCredentials => 'Credenciales no válidas.'; + + @override + String get informMissingOtp => 'Falta la contraseña de un solo uso.'; + + @override + String get informSelfClosed => 'Su cuenta ha sido cerrada.'; + + @override + String get informUnexpectedError => 'Ha ocurrido un error inesperado.'; + + @override + String get informUnsupportedCountry => 'Tu país no es compatible.'; + + @override + String get informExpiredAccount => 'Tu cuenta ha caducado'; + + @override + String get labelCountryConsentBrazil => 'Confirmo que mi solicitud de apertura de cuenta en Deriv para operar productos OTC emitidos y ofrecidos exclusivamente fuera de Brasil fue iniciada por mí. Entiendo plenamente que Deriv no está regulada por la CVM y al dirigirme a Deriv pretendo establecer una relación con una empresa extranjera.'; + + @override + String get informConnectionError => 'Error de conexión. Vuelva a intentarlo más tarde.'; + + @override + String get informSwitchAccountError => 'Error al cambiar de cuenta. Vuelva a intentarlo más tarde.'; + + @override + String get labelDeveloper => 'Desarrollador'; + + @override + String get labelEndpoint => 'Punto final'; + + @override + String get semanticEndpoint => 'Punto final'; + + @override + String get warnInvalidEndpoint => 'punto final no válido'; + + @override + String get labelApplicationID => 'ID de aplicación'; + + @override + String get semanticApplicationID => 'ID de aplicación'; + + @override + String get warnInvalidApplicationID => 'ID de aplicación no válido'; + + @override + String get labelLanguage => 'Idioma'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_fr.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_fr.dart new file mode 100644 index 000000000..0794c7216 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_fr.dart @@ -0,0 +1,276 @@ +import 'deriv_auth_localizations.dart'; + +/// The translations for French (`fr`). +class DerivAuthLocalizationsFr extends DerivAuthLocalizations { + DerivAuthLocalizationsFr([String locale = 'fr']) : super(locale); + + @override + String get labelNotAvailable => 'Non disponible'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return '$app n\'est pas disponible dans votre pays'; + } + + @override + String get actionOk => 'OK'; + + @override + String get warnNotAvailableCountries => 'Si vous avez des questions, contactez-nous par '; + + @override + String get labelLiveChat => 'Chat en direct'; + + @override + String get actionSignUpForFree => 'Inscrivez-vous gratuitement'; + + @override + String get actionLogin => 'Se connecter'; + + @override + String get labelTwoFactorAuth => 'Authentification à deux facteurs'; + + @override + String get informEnterTwoFactorAuthCode => 'Saisissez le code à 6 chiffres qui s\'affiche sur l\'application d\'authentification de votre téléphone.'; + + @override + String get labelTwoFactorAuthenticationCode => 'code 2FA'; + + @override + String get actionProceed => 'Continuer'; + + @override + String get labelLogIn => 'Se connecter'; + + @override + String get informLoginOptions => 'Ou connectez-vous avec'; + + @override + String get labelEmail => 'E-mail'; + + @override + String get labelPassword => 'Mot de passe'; + + @override + String get actionForgotPassword => 'Mot de passe oublié ?'; + + @override + String get labelDontHaveAnAccountYet => 'Vous n\'avez pas encore de compte ?'; + + @override + String get actionCreateANewAccount => 'Créer un compte'; + + @override + String get informInvalidEmailFormat => 'Saisissez une adresse e-mail valide'; + + @override + String get warnPasswordLength => 'Vous devez saisir entre 6 à 25 caractères.'; + + @override + String get labelResetPassword => 'Réinitialiser le mot'; + + @override + String get labelChooseNewPass => 'Définir un nouveau mot de passe'; + + @override + String get labelCreatePass => 'Mot de passe'; + + @override + String get informYourPassHasBeenReset => 'Votre mot de passe a été réinitialisé'; + + @override + String get informRedirectLogin => 'Vous devrez vous connecter à l\'aide de votre nouveau mot de passe. Patientez, nous vous redirigeons.'; + + @override + String get actionResetPass => 'Réinitialiser mon mot de passe'; + + @override + String get informInvalidPasswordFormat => 'Veuillez saisir un mot de passe valide'; + + @override + String get labelCheckEmail => 'Vérifiez votre e-mail'; + + @override + String informSendResetPasswordEmail(String email) { + return 'Nous avons envoyé un message à l\'adresse $email contenant un lien de réinitialisation de votre mot de passe.'; + } + + @override + String get informResetPassByEmail => 'Nous vous enverrons des instructions par e-mail pour la réinitialisation de votre mot de passe.'; + + @override + String get labelSelectCountry => 'Où résidez-vous ?'; + + @override + String get labelChooseCountry => 'Sélectionnez votre pays'; + + @override + String get warnCountryNotAvailable => 'Malheureusement, Deriv n\'est pas disponible dans votre pays.'; + + @override + String get actionNext => 'Suivant'; + + @override + String get labelEmailIssueHeader => 'Si vous ne recevez pas un courriel de notre part dans les minutes qui suivent, cela peut être dû aux raisons suivantes :'; + + @override + String get labelEmailIssueSpam => 'L\'e-mail se trouve dans votre dossier spam (parfois des choses s\'y perdent).'; + + @override + String get labelEmailIssueWrongEmail => 'Vous nous avez involontairement fourni une autre adresse électronique (généralement une adresse professionnelle ou personnelle différente de celle que vous vouliez fournir).'; + + @override + String get labelEmailIssueTypo => 'L\'adresse e-mail que vous avez fournie comportait une erreur ou une faute de frappe (cela arrive même aux meilleurs d\'entre nous).'; + + @override + String get labelEmailIssueFirewall => 'Nous ne parvenons pas à envoyer d\'e-mail à cette adresse (généralement en raison de pare-feu ou de filtrage).'; + + @override + String get actionReenterEmail => 'Saisissez à nouveau votre e-mail et réessayez'; + + @override + String get labelKeepPassword => 'Protégez votre compte avec un mot de passe'; + + @override + String get labelCreatePassword => 'Créer un mot de passe'; + + @override + String get actionStartTrading => 'Commencer à trader'; + + @override + String get actionPrevious => 'Précédent'; + + @override + String get labelSignUp => 'S\'inscrire'; + + @override + String get labelOrSignUpWith => 'Ou inscrivez-vous avec'; + + @override + String get labelReferralInfoTitle => 'Code de parrainage de l\'affilié'; + + @override + String get infoReferralInfoDescription => 'Code alphanumérique fourni par un affilié Deriv, applicable uniquement pour les inscriptions par e-mail.'; + + @override + String get labelGotReferralCode => 'Vous avez un code de parrainage ?'; + + @override + String get labelHaveAccount => 'Vous avez déjà un compte ?'; + + @override + String get actionCreateAccount => 'Créer un compte démo'; + + @override + String get informInvalidReferralCode => 'Le code de parrainage que vous avez saisi n\'est pas valide. Vérifiez et réessayez.'; + + @override + String get labelVerifyYourEmail => 'Vérifiez votre e-mail'; + + @override + String get labelThanksEmail => 'Merci d\'avoir vérifié votre e-mail'; + + @override + String get informLetsContinue => 'Poursuivons.'; + + @override + String get actionContinue => 'Continuer'; + + @override + String get labelSearchCountry => 'Rechercher un pays'; + + @override + String informVerificationEmailSent(String email) { + return 'Nous avons envoyé un message à l\'adresse $email contenant un lien d\'activation de votre compte.'; + } + + @override + String get actionEmailNotReceived => 'Vous n\'avez pas reçu d\'e-mail ?'; + + @override + String get informPasswordPolicy => 'Votre mot de passe doit comporter :'; + + @override + String get informPasswordPolicyLength => 'Entre 8 à 25 caractères'; + + @override + String get informPasswordPolicyLowerAndUpper => 'Des lettres majuscules et minuscules'; + + @override + String get informPasswordPolicyNumber => 'Au moins un chiffre'; + + @override + String get warnPasswordContainsSymbol => 'Utilisez des symboles pour obtenir un mot de passe fort.'; + + @override + String get labelReferralCode => 'Code de parrainage'; + + @override + String get actionTryAgain => 'Réessayer'; + + @override + String get informInvalid2FACode => 'Le code que vous avez saisi n\'est pas valide. Vérifiez et réessayez.'; + + @override + String get informFailedAuthentication => 'Votre adresse e-mail ou votre mot de passe est peut-être incorrect. Vous êtes-vous inscrit avec un compte social ? Vérifiez et réessayez.'; + + @override + String get informDeactivatedAccount => 'Votre compte est désactivé.'; + + @override + String get informFailedAuthorization => 'Échec de l\'autorisation.'; + + @override + String get informInvalidResidence => 'Résidence non valide.'; + + @override + String get informInvalidCredentials => 'Informations d\'identification non valides.'; + + @override + String get informMissingOtp => 'Mot de passe à usage unique manquant.'; + + @override + String get informSelfClosed => 'Votre compte a été fermé.'; + + @override + String get informUnexpectedError => 'Une erreur inattendue s\'est produite.'; + + @override + String get informUnsupportedCountry => 'Votre pays n\'est pas pris en charge.'; + + @override + String get informExpiredAccount => 'Votre compte a expiré'; + + @override + String get labelCountryConsentBrazil => 'Je confirme par la présente que ma demande d\'ouverture d\'un compte auprès de Deriv pour y trader des produits OTC émis et offerts exclusivement en dehors du Brésil a été initiée par moi-même. Je comprends parfaitement que Deriv n\'est pas réglementé par la Commission brésilienne des valeurs mobilières et des échanges (CVM) et qu\'en contactant Deriv, j\'ai l\'intention d\'établir une relation avec une société étrangère.'; + + @override + String get informConnectionError => 'Erreur de connexion. Veuillez réessayer ultérieurement.'; + + @override + String get informSwitchAccountError => 'Erreur de changement de compte. Veuillez réessayer ultérieurement.'; + + @override + String get labelDeveloper => 'Développeur'; + + @override + String get labelEndpoint => 'Point de terminaison'; + + @override + String get semanticEndpoint => 'Point de terminaison'; + + @override + String get warnInvalidEndpoint => 'point de terminaison non valide'; + + @override + String get labelApplicationID => 'Identifiant de l\'application'; + + @override + String get semanticApplicationID => 'Identifiant de l\'application'; + + @override + String get warnInvalidApplicationID => 'identifiant de l\'application non valide'; + + @override + String get labelLanguage => 'Langue'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_it.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_it.dart new file mode 100644 index 000000000..558bf4ad5 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_it.dart @@ -0,0 +1,276 @@ +import 'deriv_auth_localizations.dart'; + +/// The translations for Italian (`it`). +class DerivAuthLocalizationsIt extends DerivAuthLocalizations { + DerivAuthLocalizationsIt([String locale = 'it']) : super(locale); + + @override + String get labelNotAvailable => 'Non disponibile'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return '$app non è disponibile nel tuo Paese'; + } + + @override + String get actionOk => 'OK'; + + @override + String get warnNotAvailableCountries => 'Se hai domande, non esitare a contattarci tramite '; + + @override + String get labelLiveChat => 'Chat live'; + + @override + String get actionSignUpForFree => 'Registrati gratis'; + + @override + String get actionLogin => 'Accedi'; + + @override + String get labelTwoFactorAuth => 'Autenticazione a due fattori'; + + @override + String get informEnterTwoFactorAuthCode => 'Inserisca il codice a 6 cifre dall\'app Authenticator sul suo telefono.'; + + @override + String get labelTwoFactorAuthenticationCode => 'Codice 2FA'; + + @override + String get actionProceed => 'Continua'; + + @override + String get labelLogIn => 'Accedi'; + + @override + String get informLoginOptions => 'Oppure accedi con'; + + @override + String get labelEmail => 'E-mail'; + + @override + String get labelPassword => 'Password'; + + @override + String get actionForgotPassword => 'Hai dimenticato la password?'; + + @override + String get labelDontHaveAnAccountYet => 'Non ha ancora un account?'; + + @override + String get actionCreateANewAccount => 'Crea un nuovo conto'; + + @override + String get informInvalidEmailFormat => 'Inserisca un indirizzo e-mail valido'; + + @override + String get warnPasswordLength => 'È necessario inserire da 8 a 25 caratteri.'; + + @override + String get labelResetPassword => 'Reimposta la password'; + + @override + String get labelChooseNewPass => 'Scegli una nuova password'; + + @override + String get labelCreatePass => 'Password'; + + @override + String get informYourPassHasBeenReset => 'La sua password è stata reimpostata'; + + @override + String get informRedirectLogin => 'Dovrà accedere con la nuova password. Aspetti, la stiamo reindirizzando.'; + + @override + String get actionResetPass => 'Reimposta la password'; + + @override + String get informInvalidPasswordFormat => 'Inserisci un formato della password valido'; + + @override + String get labelCheckEmail => 'Controlla la tua casella di posta'; + + @override + String informSendResetPasswordEmail(String email) { + return 'Abbiamo inviato un messaggio a $email con un link per reimpostare la sua password.'; + } + + @override + String get informResetPassByEmail => 'Ti manderemo una e-mail con le istruzioni per reimpostare la password.'; + + @override + String get labelSelectCountry => 'Dove vivi?'; + + @override + String get labelChooseCountry => 'Seleziona il Paese'; + + @override + String get warnCountryNotAvailable => 'Deriv non è disponibile nel tuo Paese.'; + + @override + String get actionNext => 'Successivo'; + + @override + String get labelEmailIssueHeader => 'Se non ricevi una e-mail da parte nostra entro pochi minuti, potrebbero esserci stati dei problemi:'; + + @override + String get labelEmailIssueSpam => 'L\' e-mail è nella cartella spam (dove alle volte finiscono i messaggi).'; + + @override + String get labelEmailIssueWrongEmail => 'Hai erroneamente indicato un altro indirizzo e-mail (normalmente quello di lavoro o personale, invece di quello che intendevi usare).'; + + @override + String get labelEmailIssueTypo => 'L\'indirizzo e-mail inserito contiene un errore o un refuso (è un errore comune, non preoccuparti!).'; + + @override + String get labelEmailIssueFirewall => 'Non possiamo inviare l\'e-mail a questo indirizzo (di solito a causa di filtri o firewall).'; + + @override + String get actionReenterEmail => 'Reinserisci e-mail e prova ancora'; + + @override + String get labelKeepPassword => 'Mantieni in sicurezza il tuo conto con una password'; + + @override + String get labelCreatePassword => 'Crea una password'; + + @override + String get actionStartTrading => 'Inizia il trading'; + + @override + String get actionPrevious => 'Precedente'; + + @override + String get labelSignUp => 'Registrati'; + + @override + String get labelOrSignUpWith => 'Oppure registrati con'; + + @override + String get labelReferralInfoTitle => 'Codice di riferimento dell\'affiliato'; + + @override + String get infoReferralInfoDescription => 'Un codice alfanumerico fornito da un affiliato Deriv, applicabile solo per le iscrizioni via e-mail.'; + + @override + String get labelGotReferralCode => 'Ha un codice di riferimento?'; + + @override + String get labelHaveAccount => 'Hai già un account?'; + + @override + String get actionCreateAccount => 'Crea un conto di prova gratuito'; + + @override + String get informInvalidReferralCode => 'Il codice di riferimento inserito non è valido. Controlli e riprovi.'; + + @override + String get labelVerifyYourEmail => 'Verifica la tua email'; + + @override + String get labelThanksEmail => 'Grazie per aver verificato la tua e-mail'; + + @override + String get informLetsContinue => 'Continuiamo.'; + + @override + String get actionContinue => 'Continua'; + + @override + String get labelSearchCountry => 'Ricerca per Paese'; + + @override + String informVerificationEmailSent(String email) { + return 'Abbiamo inviato un messaggio a $email con un link per attivare il suo account.'; + } + + @override + String get actionEmailNotReceived => 'Non hai ricevuto l\'e-mail?'; + + @override + String get informPasswordPolicy => 'La sua password deve avere:'; + + @override + String get informPasswordPolicyLength => '8-25 caratteri'; + + @override + String get informPasswordPolicyLowerAndUpper => 'Lettere maiuscole e minuscole'; + + @override + String get informPasswordPolicyNumber => 'Almeno un numero'; + + @override + String get warnPasswordContainsSymbol => 'Utilizzi i simboli per una password forte.'; + + @override + String get labelReferralCode => 'Codice di riferimento'; + + @override + String get actionTryAgain => 'Riprova'; + + @override + String get informInvalid2FACode => 'Il codice inserito non è valido. Controlli e riprovi.'; + + @override + String get informFailedAuthentication => 'La sua e-mail o la sua password potrebbero essere errate. Si è iscritto con un account social? Controlli e riprovi.'; + + @override + String get informDeactivatedAccount => 'Il tuo account è disattivato.'; + + @override + String get informFailedAuthorization => 'Autorizzazione non riuscita.'; + + @override + String get informInvalidResidence => 'Residenza non valida.'; + + @override + String get informInvalidCredentials => 'Credenziali non valide.'; + + @override + String get informMissingOtp => 'Password monouso mancante.'; + + @override + String get informSelfClosed => 'Questo conto è stato chiuso.'; + + @override + String get informUnexpectedError => 'Ein unerwarteter Fehler ist aufgetreten.'; + + @override + String get informUnsupportedCountry => 'Il tuo Paese non è supportato.'; + + @override + String get informExpiredAccount => 'Il tuo account è scaduto'; + + @override + String get labelCountryConsentBrazil => 'Con la presente confermo che la mia richiesta di apertura di un conto presso Deriv per negoziare prodotti OTC emessi e offerti esclusivamente al di fuori del Brasile è stata avviata da me. Sono pienamente consapevole che Deriv non è regolamentata dal CVM e che rivolgendomi a Deriv intendo instaurare un rapporto con una società straniera.'; + + @override + String get informConnectionError => 'Errore di connessione. Riprova più tardi.'; + + @override + String get informSwitchAccountError => 'Errore nel cambio di account. Riprova più tardi.'; + + @override + String get labelDeveloper => 'Sviluppatore'; + + @override + String get labelEndpoint => 'Termine'; + + @override + String get semanticEndpoint => 'Termine'; + + @override + String get warnInvalidEndpoint => 'termine non valido'; + + @override + String get labelApplicationID => 'ID applicazione'; + + @override + String get semanticApplicationID => 'ID applicazione'; + + @override + String get warnInvalidApplicationID => 'ID applicazione non valido'; + + @override + String get labelLanguage => 'Lingua'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_km.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_km.dart new file mode 100644 index 000000000..4a1606e2a --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_km.dart @@ -0,0 +1,276 @@ +import 'deriv_auth_localizations.dart'; + +/// The translations for Khmer Central Khmer (`km`). +class DerivAuthLocalizationsKm extends DerivAuthLocalizations { + DerivAuthLocalizationsKm([String locale = 'km']) : super(locale); + + @override + String get labelNotAvailable => 'មិនមាន'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return '$app មិនមាននៅក្នុងប្រទេសរបស់អ្នកទេ'; + } + + @override + String get actionOk => 'យល់ព្រម'; + + @override + String get warnNotAvailableCountries => 'ប្រសិនបើអ្នកមានសំណួរណាមួយ សូមទាក់ទងមកយើងខ្ញុំតាមរយៈ '; + + @override + String get labelLiveChat => 'ជជែកផ្ទាល់'; + + @override + String get actionSignUpForFree => 'ចុះឈ្មោះដោយឥតគិតថ្លៃ'; + + @override + String get actionLogin => 'ចូល'; + + @override + String get labelTwoFactorAuth => 'ការផ្ទៀងផ្ទាត់ភាពត្រឹមត្រូវពីរកម្រិត'; + + @override + String get informEnterTwoFactorAuthCode => 'បញ្ចូលលេខកូដ 6 ខ្ទង់ពីកម្មវិធីផ្ទៀងផ្ទាត់ភាពត្រឹមត្រូវនៅលើទូរស័ព្ទរបស់អ្នក។'; + + @override + String get labelTwoFactorAuthenticationCode => 'លេខកូដ 2FA'; + + @override + String get actionProceed => 'បន្ត'; + + @override + String get labelLogIn => 'ចូល'; + + @override + String get informLoginOptions => 'ឬចូលជាមួយ'; + + @override + String get labelEmail => 'អ៊ីមែល'; + + @override + String get labelPassword => 'ពាក្យសម្ងាត់'; + + @override + String get actionForgotPassword => 'ភ្លេចពាក្យសម្ងាត់?'; + + @override + String get labelDontHaveAnAccountYet => 'មិនទាន់មានគណនីនៅឡើយទេ?'; + + @override + String get actionCreateANewAccount => 'បង្កើតគណនីថ្មី'; + + @override + String get informInvalidEmailFormat => 'បញ្ចូលអាសយដ្ឋានអ៊ីមែលដែលត្រឹមត្រូវ'; + + @override + String get warnPasswordLength => 'អ្នកគួរតែបញ្ចូលតួអក្សរចំនួន 6-25 ។'; + + @override + String get labelResetPassword => 'កំណត់ពាក្យសម្ងាត់ឡើងវិញ'; + + @override + String get labelChooseNewPass => 'ជ្រើសរើសពាក្យសម្ងាត់ថ្មី'; + + @override + String get labelCreatePass => 'ពាក្យសម្ងាត់'; + + @override + String get informYourPassHasBeenReset => 'ពាក្យសម្ងាត់របស់អ្នកត្រូវបានកំណត់ឡើងវិញ'; + + @override + String get informRedirectLogin => 'អ្នកនឹងត្រូវចូលដោយប្រើពាក្យសម្ងាត់ថ្មីរបស់អ្នក។ រង់ចាំបន្តិច យើងកំពុងបញ្ជូនអ្នកឡើងវិញ។'; + + @override + String get actionResetPass => 'កំណត់ពាក្យសម្ងាត់របស់ខ្ញុំឡើងវិញ'; + + @override + String get informInvalidPasswordFormat => 'សូមបញ្ចូលទម្រង់ពាក្យសម្ងាត់ត្រឹមត្រូវ'; + + @override + String get labelCheckEmail => 'ពិនិត្យអ៊ីមែលរបស់អ្នក'; + + @override + String informSendResetPasswordEmail(String email) { + return 'យើងបានផ្ញើសារទៅ $email ជាមួយនឹងតំណដើម្បីកំណត់ពាក្យសម្ងាត់របស់អ្នកឡើងវិញ។'; + } + + @override + String get informResetPassByEmail => 'យើងនឹងផ្ញើអ៊ីមែលជូនអ្នកនូវការណែនាំដើម្បីកំណត់ពាក្យសម្ងាត់របស់អ្នកឡើងវិញ។'; + + @override + String get labelSelectCountry => 'តើអ្នករស់នៅឯណា?'; + + @override + String get labelChooseCountry => 'ជ្រើសរើសប្រទេស'; + + @override + String get warnCountryNotAvailable => 'ជាអកុសល Deriv មិនមាននៅក្នុងប្រទេសរបស់អ្នកទេ។'; + + @override + String get actionNext => 'បន្ទាប់'; + + @override + String get labelEmailIssueHeader => 'ប្រសិនបើអ្នកមិនឃើញអ៊ីមែលពីយើងក្នុងរយៈពេលពីរបីនាទី អ្វីៗមួយចំនួនអាចនឹងកើតឡើង៖'; + + @override + String get labelEmailIssueSpam => 'អ៊ីមែលនេះស្ថិតនៅក្នុងថតឯកសារឥតបានការរបស់អ្នក (ពេលខ្លះរបស់របរបាត់បង់នៅទីនោះ)។'; + + @override + String get labelEmailIssueWrongEmail => 'អ្នកបានផ្តល់អាសយដ្ឋានអ៊ីមែលផ្សេងទៀតដល់យើងដោយចៃដន្យ (ជាធម្មតាជាការងារ ឬផ្ទាល់ខ្លួនជំនួសឱ្យអ្វីដែលអ្នកចង់បាន)។'; + + @override + String get labelEmailIssueTypo => 'អាសយដ្ឋានអ៊ីមែលដែលអ្នកបានបញ្ចូលមានកំហុស ឬវាយអក្សរខុស (កើតឡើងចំពោះយើងភាគច្រើន)។'; + + @override + String get labelEmailIssueFirewall => 'យើងមិនអាចផ្ញើអ៊ីមែលទៅកាន់អាសយដ្ឋាននេះបានទេ (ជាធម្មតាដោយសារតែជញ្ជាំងភ្លើង ឬការច្រោះ)។'; + + @override + String get actionReenterEmail => 'បញ្ចូលអ៊ីមែលរបស់អ្នកឡើងវិញ ហើយសាកល្បងម្តងទៀត'; + + @override + String get labelKeepPassword => 'រក្សាគណនីរបស់អ្នកឱ្យមានសុវត្ថិភាពជាមួយពាក្យសម្ងាត់'; + + @override + String get labelCreatePassword => 'បង្កើតពាក្យសម្ងាត់'; + + @override + String get actionStartTrading => 'ចាប់ផ្តើមការជួញដូរ'; + + @override + String get actionPrevious => 'មុន'; + + @override + String get labelSignUp => 'ចុះឈ្មោះ'; + + @override + String get labelOrSignUpWith => 'ឬចុះឈ្មោះជាមួយ'; + + @override + String get labelReferralInfoTitle => 'លេខកូដបញ្ជូនសម្ព័ន្ធ'; + + @override + String get infoReferralInfoDescription => 'លេខកូដអក្សរក្រម និងលេខដែលផ្តល់ដោយសម្ព័ន្ធ Deriv ដែលអាចអនុវត្តបានសម្រាប់ការចុះឈ្មោះតាមអ៊ីមែលតែប៉ុណ្ណោះ។'; + + @override + String get labelGotReferralCode => 'មានលេខកូដបញ្ជូនទេ?'; + + @override + String get labelHaveAccount => 'មានគណនីរួចហើយឬនៅ?'; + + @override + String get actionCreateAccount => 'បង្កើតគណនីបង្ហាញឥតគិតថ្លៃ'; + + @override + String get informInvalidReferralCode => 'លេខកូដបញ្ជូនដែលអ្នកបានបញ្ចូលមិនត្រឹមត្រូវ។ ពិនិត្យ និងសាកល្បងម្តងទៀត។'; + + @override + String get labelVerifyYourEmail => 'ផ្ទៀងផ្ទាត់អ៊ីមែលរបស់អ្នក'; + + @override + String get labelThanksEmail => 'សូមអរគុណសម្រាប់ការផ្ទៀងផ្ទាត់អ៊ីមែលរបស់អ្នក'; + + @override + String get informLetsContinue => 'តោះបន្ត។'; + + @override + String get actionContinue => 'បន្ត'; + + @override + String get labelSearchCountry => 'ស្វែងរកប្រទេស'; + + @override + String informVerificationEmailSent(String email) { + return 'យើងបានផ្ញើសារទៅ $email ជាមួយនឹងតំណដើម្បីធ្វើឱ្យគណនីរបស់អ្នកសកម្ម។'; + } + + @override + String get actionEmailNotReceived => 'មិនបានទទួលអ៊ីមែលរបស់អ្នកទេ?'; + + @override + String get informPasswordPolicy => 'ពាក្យសម្ងាត់របស់អ្នកត្រូវតែមាន៖'; + + @override + String get informPasswordPolicyLength => '8-25 តួអក្សរ'; + + @override + String get informPasswordPolicyLowerAndUpper => 'អក្សរធំ និងអក្សរតូច'; + + @override + String get informPasswordPolicyNumber => 'យ៉ាងហោចណាស់មួយលេខ'; + + @override + String get warnPasswordContainsSymbol => 'ប្រើនិមិត្តសញ្ញាសម្រាប់ពាក្យសម្ងាត់ខ្លាំង។'; + + @override + String get labelReferralCode => 'លេខកូដបញ្ជូន'; + + @override + String get actionTryAgain => 'សាកល្បងម្តងទៀត'; + + @override + String get informInvalid2FACode => 'លេខកូដដែលអ្នកបានបញ្ចូលមិនត្រឹមត្រូវ។ ពិនិត្យ និងសាកល្បងម្តងទៀត។'; + + @override + String get informFailedAuthentication => 'អ៊ីមែល ឬពាក្យសម្ងាត់របស់អ្នកអាចនឹងមិនត្រឹមត្រូវ។ តើអ្នកបានចុះឈ្មោះជាមួយគណនីសង្គមមែនទេ? ពិនិត្យ និងសាកល្បងម្តងទៀត។'; + + @override + String get informDeactivatedAccount => 'គណនីរបស់អ្នកត្រូវបានអសកម្ម។'; + + @override + String get informFailedAuthorization => 'ការអនុញ្ញាតបរាជ័យ។'; + + @override + String get informInvalidResidence => 'ការស្នាក់នៅមិនត្រឹមត្រូវ។'; + + @override + String get informInvalidCredentials => 'អត្តសញ្ញាណមិនត្រឹមត្រូវ។'; + + @override + String get informMissingOtp => 'ពាក្យសម្ងាត់មួយដងបាត់។'; + + @override + String get informSelfClosed => 'គណនីរបស់អ្នកត្រូវបានបិទ។'; + + @override + String get informUnexpectedError => 'កំហុសដែលមិនបានរំពឹងទុកបានកើតឡើង។'; + + @override + String get informUnsupportedCountry => 'ប្រទេសរបស់អ្នកមិនត្រូវបានគាំទ្រទេ។'; + + @override + String get informExpiredAccount => 'គណនីរបស់អ្នកផុតកំណត់ហើយ'; + + @override + String get labelCountryConsentBrazil => 'ខ្ញុំសូមបញ្ជាក់នៅទីនេះថា សំណើរបស់ខ្ញុំសម្រាប់ការបើកគណនីជាមួយ Deriv ដើម្បីធ្វើពាណិជ្ជកម្មផលិតផល OTC ដែលចេញ និងផ្តល់ជូនផ្តាច់មុខនៅខាងក្រៅប្រទេសប្រេស៊ីល ត្រូវបានផ្តួចផ្តើមដោយខ្ញុំ។ ខ្ញុំយល់ច្បាស់ទាំងស្រុងថា Deriv មិនត្រូវបានគ្រប់គ្រងដោយ CVM ហើយដោយការទាក់ទង Deriv ខ្ញុំមានបំណងបង្កើតទំនាក់ទំនងជាមួយក្រុមហ៊ុនបរទេស។'; + + @override + String get informConnectionError => 'កំហុសក្នុងការតភ្ជាប់។ សូមព្យាយាមម្តងទៀតនៅពេលក្រោយ។'; + + @override + String get informSwitchAccountError => 'កំហុសក្នុងការប្តូរគណនី។ សូមព្យាយាមម្តងទៀតនៅពេលក្រោយ។'; + + @override + String get labelDeveloper => 'អ្នកអភិវឌ្ឍន៍'; + + @override + String get labelEndpoint => 'ចំណុចបញ្ចប់'; + + @override + String get semanticEndpoint => 'ចំណុចបញ្ចប់'; + + @override + String get warnInvalidEndpoint => 'ចំណុចបញ្ចប់មិនត្រឹមត្រូវ'; + + @override + String get labelApplicationID => 'លេខសម្គាល់កម្មវិធី'; + + @override + String get semanticApplicationID => 'លេខសម្គាល់កម្មវិធី'; + + @override + String get warnInvalidApplicationID => 'លេខសម្គាល់កម្មវិធីមិនត្រឹមត្រូវ'; + + @override + String get labelLanguage => 'ភាសា'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_ko.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_ko.dart new file mode 100644 index 000000000..01501babf --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_ko.dart @@ -0,0 +1,276 @@ +import 'deriv_auth_localizations.dart'; + +/// The translations for Korean (`ko`). +class DerivAuthLocalizationsKo extends DerivAuthLocalizations { + DerivAuthLocalizationsKo([String locale = 'ko']) : super(locale); + + @override + String get labelNotAvailable => '사용할 수 없습니다'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return '귀하의 국가에서는 $app 을(를) 사용할 수 없습니다.'; + } + + @override + String get actionOk => '확인'; + + @override + String get warnNotAvailableCountries => '궁금하신 점은 다음을 통해 문의해 주세요 '; + + @override + String get labelLiveChat => '실시간 채팅'; + + @override + String get actionSignUpForFree => '무료로 가입하기'; + + @override + String get actionLogin => '로그인'; + + @override + String get labelTwoFactorAuth => '2단계 인증'; + + @override + String get informEnterTwoFactorAuthCode => '휴대폰 인증 앱에서 6자리 코드를 입력하세요.'; + + @override + String get labelTwoFactorAuthenticationCode => '2FA 코드'; + + @override + String get actionProceed => '진행'; + + @override + String get labelLogIn => '로그인'; + + @override + String get informLoginOptions => '또는 다음을 통해 로그인'; + + @override + String get labelEmail => '이메일'; + + @override + String get labelPassword => '비밀번호'; + + @override + String get actionForgotPassword => '비밀번호를 잊으셨나요?'; + + @override + String get labelDontHaveAnAccountYet => '아직 계정이 없는가요?'; + + @override + String get actionCreateANewAccount => '새 계정 생성'; + + @override + String get informInvalidEmailFormat => '유효한 이메일 주소를 입력해 주세요'; + + @override + String get warnPasswordLength => '6~25자를 입력해야 합니다.'; + + @override + String get labelResetPassword => '비밀번호 재설정'; + + @override + String get labelChooseNewPass => '새 비밀번호를 선택하세요'; + + @override + String get labelCreatePass => '비밀번호'; + + @override + String get informYourPassHasBeenReset => '비밀번호가 재설정되었습니다'; + + @override + String get informRedirectLogin => '새 비밀번호르 로그인해야 합니다. 잠시만 기다려 주세요. 리디렉션 중입니다.'; + + @override + String get actionResetPass => '비밀번호 재설정'; + + @override + String get informInvalidPasswordFormat => '올바른 형식의 비밀번호를 입력해 주세요'; + + @override + String get labelCheckEmail => '이메일을 확인해 주세요'; + + @override + String informSendResetPasswordEmail(String email) { + return '비밀번호 재설정을 위한 링크가 포함된 메시지가 $email로 전송되었습니다.'; + } + + @override + String get informResetPassByEmail => '비밀번호 재설정을 위한 지침을 이메일로 전송해 드리겠습니다.'; + + @override + String get labelSelectCountry => '어디에 사시나요?'; + + @override + String get labelChooseCountry => '국가 선택'; + + @override + String get warnCountryNotAvailable => '아쉽게도 귀하의 국가에서는 Deriv를 이용하실 수 없습니다.'; + + @override + String get actionNext => '다음'; + + @override + String get labelEmailIssueHeader => '몇 분 내에 이메일을 받지 못하셨다면 다음과 같은 몇 가지 문제가 발생했을 수 있습니다:'; + + @override + String get labelEmailIssueSpam => '이메일이 스팸 폴더에 있습니다(이메일이 스팸 폴더에 전송될 수 있습니다).'; + + @override + String get labelEmailIssueWrongEmail => '실수로 다른 이메일 주소를 알려주셨습니다(의도하신 이메일 주소 대신 회사 및 개인 이메일 주소일 수 있습니다).'; + + @override + String get labelEmailIssueTypo => '입력하신 이메일 주소가 잘못되었거나 오타가 있습니다(누구에게나 발생할 수 있습니다).'; + + @override + String get labelEmailIssueFirewall => '이 주소로 이메일이 전송될 수 없습니다(일반적으로는 방화벽 또는 필터링이 원인입니다).'; + + @override + String get actionReenterEmail => '이메일을 다시 입력하시고 재시도해 주세요'; + + @override + String get labelKeepPassword => '비밀번호로 계정을 안전하게 보호하세요'; + + @override + String get labelCreatePassword => '비밀번호 생성'; + + @override + String get actionStartTrading => '거래 시작'; + + @override + String get actionPrevious => '이전'; + + @override + String get labelSignUp => '가입'; + + @override + String get labelOrSignUpWith => '또는 다음을 통해 가입'; + + @override + String get labelReferralInfoTitle => '제휴 추천 코드'; + + @override + String get infoReferralInfoDescription => 'Deriv 제휴자가 제공하는 영숫자 코드이며 이메일 가입 시에만 사용할 수 있습니다.'; + + @override + String get labelGotReferralCode => '추천 코드가 있나요?'; + + @override + String get labelHaveAccount => '이미 계정을 보유하고 계시나요?'; + + @override + String get actionCreateAccount => '무료 데모 게정 생성'; + + @override + String get informInvalidReferralCode => '입력하신 추천 코드가 유효하지 않습니다. 확인 후 다시 시도해 주세요.'; + + @override + String get labelVerifyYourEmail => '이메일 인증'; + + @override + String get labelThanksEmail => '이메일을 인증해 주셔서 감사합니다'; + + @override + String get informLetsContinue => '계속 진행.'; + + @override + String get actionContinue => '계속'; + + @override + String get labelSearchCountry => '국가 검색'; + + @override + String informVerificationEmailSent(String email) { + return '계정 활성화를 위한 링크가 포함된 메시지가 $email로 전송되었습니다.'; + } + + @override + String get actionEmailNotReceived => '이메일을 받지 못하셨나요?'; + + @override + String get informPasswordPolicy => '비밀번호에는 반드시 다음을 포함해야 합니다:'; + + @override + String get informPasswordPolicyLength => '8~25자'; + + @override + String get informPasswordPolicyLowerAndUpper => '대문자 및 소문자'; + + @override + String get informPasswordPolicyNumber => '최소 하나 이상의 숫자'; + + @override + String get warnPasswordContainsSymbol => '비밀번호를 강화하기 위해 기호를 사용하세요.'; + + @override + String get labelReferralCode => '추천 코드'; + + @override + String get actionTryAgain => '재시도'; + + @override + String get informInvalid2FACode => '입력하신 링크가 유효하지 않습니다. 확인 및 다시 시도해 주세요.'; + + @override + String get informFailedAuthentication => '이메일 또는 비밀번호가 올바르지 않을 수 있습니다. 혹시 소셜 계정으로 가입하셨나요? 확인 후 다시 시도해 주시기 바랍니다.'; + + @override + String get informDeactivatedAccount => '계정이 비활성화되었습니다.'; + + @override + String get informFailedAuthorization => '인증에 실패했습니다.'; + + @override + String get informInvalidResidence => '유효하지 않은 거주지입니다.'; + + @override + String get informInvalidCredentials => '유효하지 않은 자격 증명입니다.'; + + @override + String get informMissingOtp => '일회용 비밀번호가 누락되었습니다.'; + + @override + String get informSelfClosed => '계정이 해지되었습니다.'; + + @override + String get informUnexpectedError => '예상하지 못한 오류가 발생했습니다.'; + + @override + String get informUnsupportedCountry => '귀하의 국가는 지원되지 않습니다.'; + + @override + String get informExpiredAccount => '계정이 만료되었습니다'; + + @override + String get labelCountryConsentBrazil => '본인은 브라질 이외 지역에서만 발행 및 제공되는 OTC 상품을 거래하기 위해 Deriv를 통한 계정을 개설하고자 하는 요청이 본인에 의해 시작되었음을 확정합니다. 본인은 Deriv가 CVM의 규제를 받지 않는다는 것을 충분히 이해하며 Deriv에 접근하여 외국 회사와 관계를 구축하고자 하는 것입니다.'; + + @override + String get informConnectionError => '연결 오류입니다. 나중에 다시 시도하시기 바랍니다.'; + + @override + String get informSwitchAccountError => '계정 전환 오류입니다. 나중에 다시 시도하시기 바랍니다.'; + + @override + String get labelDeveloper => '개발자'; + + @override + String get labelEndpoint => '엔드포인트'; + + @override + String get semanticEndpoint => '엔드포인트'; + + @override + String get warnInvalidEndpoint => '유효하지 않은 엔드포인트'; + + @override + String get labelApplicationID => '애플리케이션 ID'; + + @override + String get semanticApplicationID => '애플리케이션 ID'; + + @override + String get warnInvalidApplicationID => '유효하지 않은 애플리케이션 ID'; + + @override + String get labelLanguage => '언어'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_mn.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_mn.dart new file mode 100644 index 000000000..2afceb63e --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_mn.dart @@ -0,0 +1,276 @@ +import 'deriv_auth_localizations.dart'; + +/// The translations for Mongolian (`mn`). +class DerivAuthLocalizationsMn extends DerivAuthLocalizations { + DerivAuthLocalizationsMn([String locale = 'mn']) : super(locale); + + @override + String get labelNotAvailable => 'Not available'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return '$app isn\'t available in your country'; + } + + @override + String get actionOk => 'OK'; + + @override + String get warnNotAvailableCountries => 'If you have any questions, contact us via '; + + @override + String get labelLiveChat => 'Live chat'; + + @override + String get actionSignUpForFree => 'Sign up for free'; + + @override + String get actionLogin => 'Log in'; + + @override + String get labelTwoFactorAuth => 'Two-factor authentication'; + + @override + String get informEnterTwoFactorAuthCode => 'Enter the 6-digit code from the authenticator app on your phone.'; + + @override + String get labelTwoFactorAuthenticationCode => '2FA code'; + + @override + String get actionProceed => 'Proceed'; + + @override + String get labelLogIn => 'Log in'; + + @override + String get informLoginOptions => 'Or log in with'; + + @override + String get labelEmail => 'Email'; + + @override + String get labelPassword => 'Password'; + + @override + String get actionForgotPassword => 'Forgot password?'; + + @override + String get labelDontHaveAnAccountYet => 'Don’t have an account yet?'; + + @override + String get actionCreateANewAccount => 'Create a new account'; + + @override + String get informInvalidEmailFormat => 'Enter a valid email address'; + + @override + String get warnPasswordLength => 'You should enter 6-25 characters.'; + + @override + String get labelResetPassword => 'Reset password'; + + @override + String get labelChooseNewPass => 'Choose a new password'; + + @override + String get labelCreatePass => 'Password'; + + @override + String get informYourPassHasBeenReset => 'Your password has been reset'; + + @override + String get informRedirectLogin => 'You’ll need to log in with your new password. Hang on, we’re redirecting you.'; + + @override + String get actionResetPass => 'Reset my password'; + + @override + String get informInvalidPasswordFormat => 'Please enter a valid password format'; + + @override + String get labelCheckEmail => 'Check your email'; + + @override + String informSendResetPasswordEmail(String email) { + return 'We’ve sent a message to $email with a link to reset your password.'; + } + + @override + String get informResetPassByEmail => 'We\'ll email you instructions to reset your password.'; + + @override + String get labelSelectCountry => 'Where do you live?'; + + @override + String get labelChooseCountry => 'Choose country'; + + @override + String get warnCountryNotAvailable => 'Unfortunately, Deriv is not available in your country.'; + + @override + String get actionNext => 'Next'; + + @override + String get labelEmailIssueHeader => 'If you don\'t see an email from us within a few minutes, a few things could have happened:'; + + @override + String get labelEmailIssueSpam => 'The email is in your spam folder (Sometimes things get lost there).'; + + @override + String get labelEmailIssueWrongEmail => 'You accidentally gave us another email address (Usually a work or a personal one instead of the one you meant).'; + + @override + String get labelEmailIssueTypo => 'The email address you entered had a mistake or typo (happens to the best of us).'; + + @override + String get labelEmailIssueFirewall => 'We can\'t deliver the email to this address (Usually because of firewalls or filtering).'; + + @override + String get actionReenterEmail => 'Re-enter your email and try again'; + + @override + String get labelKeepPassword => 'Keep your account secure with a password'; + + @override + String get labelCreatePassword => 'Create a password'; + + @override + String get actionStartTrading => 'Start trading'; + + @override + String get actionPrevious => 'Previous'; + + @override + String get labelSignUp => 'Sign up'; + + @override + String get labelOrSignUpWith => 'Or sign up with'; + + @override + String get labelReferralInfoTitle => 'Affiliate referral code'; + + @override + String get infoReferralInfoDescription => 'An alphanumeric code provided by a Deriv affiliate, applicable for email sign-ups only.'; + + @override + String get labelGotReferralCode => 'Got a referral code?'; + + @override + String get labelHaveAccount => 'Already have an account?'; + + @override + String get actionCreateAccount => 'Create free demo account'; + + @override + String get informInvalidReferralCode => 'The referral code you entered is invalid. Check and try again.'; + + @override + String get labelVerifyYourEmail => 'Verify your email'; + + @override + String get labelThanksEmail => 'Thanks for verifying your email'; + + @override + String get informLetsContinue => 'Let\'s continue.'; + + @override + String get actionContinue => 'Continue'; + + @override + String get labelSearchCountry => 'Search country'; + + @override + String informVerificationEmailSent(String email) { + return 'We\'ve sent a message to $email with a link to activate your account.'; + } + + @override + String get actionEmailNotReceived => 'Didn\'t receive your email?'; + + @override + String get informPasswordPolicy => 'Your password must have:'; + + @override + String get informPasswordPolicyLength => '8-25 characters'; + + @override + String get informPasswordPolicyLowerAndUpper => 'Upper and lower case letters'; + + @override + String get informPasswordPolicyNumber => 'At least one number'; + + @override + String get warnPasswordContainsSymbol => 'Use symbols for strong password.'; + + @override + String get labelReferralCode => 'Referral Code'; + + @override + String get actionTryAgain => 'Try Again'; + + @override + String get informInvalid2FACode => 'The code you entered is invalid. Check and try again.'; + + @override + String get informFailedAuthentication => 'Your email or password may be incorrect. Did you sign up with a social account? Check and try again.'; + + @override + String get informDeactivatedAccount => 'Your account is deactivated.'; + + @override + String get informFailedAuthorization => 'Authorization failed.'; + + @override + String get informInvalidResidence => 'Invalid residence.'; + + @override + String get informInvalidCredentials => 'Invalid credentials.'; + + @override + String get informMissingOtp => 'Missing one-time password.'; + + @override + String get informSelfClosed => 'Your account has been closed.'; + + @override + String get informUnexpectedError => 'An unexpected error occurred.'; + + @override + String get informUnsupportedCountry => 'Your country is not supported.'; + + @override + String get informExpiredAccount => 'Your account is expired'; + + @override + String get labelCountryConsentBrazil => 'I hereby confirm that my request for opening an account with Deriv to trade OTC products issued and offered exclusively outside Brazil was initiated by me. I fully understand that Deriv is not regulated by CVM and by approaching Deriv I intend to set up a relation with a foreign company.'; + + @override + String get informConnectionError => 'Connection error. Please try again later.'; + + @override + String get informSwitchAccountError => 'Switch account error. Please try again later.'; + + @override + String get labelDeveloper => 'Developer'; + + @override + String get labelEndpoint => 'Endpoint'; + + @override + String get semanticEndpoint => 'Endpoint'; + + @override + String get warnInvalidEndpoint => 'invalid endpoint'; + + @override + String get labelApplicationID => 'Application ID'; + + @override + String get semanticApplicationID => 'Application ID'; + + @override + String get warnInvalidApplicationID => 'invalid application ID'; + + @override + String get labelLanguage => 'Language'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_pl.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_pl.dart new file mode 100644 index 000000000..cc12b74e8 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_pl.dart @@ -0,0 +1,276 @@ +import 'deriv_auth_localizations.dart'; + +/// The translations for Polish (`pl`). +class DerivAuthLocalizationsPl extends DerivAuthLocalizations { + DerivAuthLocalizationsPl([String locale = 'pl']) : super(locale); + + @override + String get labelNotAvailable => 'Niedostępne'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return 'Plik $app nie jest dostępny w Twoim kraju'; + } + + @override + String get actionOk => 'OK'; + + @override + String get warnNotAvailableCountries => 'Jeśli mają Państwo jakiekolwiek pytania, prosimy o kontakt '; + + @override + String get labelLiveChat => 'Czat na żywo'; + + @override + String get actionSignUpForFree => 'Zarejestruj się za darmo'; + + @override + String get actionLogin => 'Zaloguj'; + + @override + String get labelTwoFactorAuth => 'Uwierzytelnianie dwuskładnikowe'; + + @override + String get informEnterTwoFactorAuthCode => 'Proszę wprowadzić 6-cyfrowy kod z aplikacji uwierzytelniającej w telefonie.'; + + @override + String get labelTwoFactorAuthenticationCode => 'Kod dwuskładnikowy'; + + @override + String get actionProceed => 'Kontynuuj'; + + @override + String get labelLogIn => 'Zaloguj'; + + @override + String get informLoginOptions => 'Lub zaloguj się przez'; + + @override + String get labelEmail => 'E-mail'; + + @override + String get labelPassword => 'Hasło'; + + @override + String get actionForgotPassword => 'Nie pamiętasz hasła?'; + + @override + String get labelDontHaveAnAccountYet => 'Nie mają Państwo jeszcze konta?'; + + @override + String get actionCreateANewAccount => 'Załóż nowe konto'; + + @override + String get informInvalidEmailFormat => 'Proszę wprowadzić prawidłowy adres e-mail'; + + @override + String get warnPasswordLength => 'Wprowadź od 8 do 25 znaków.'; + + @override + String get labelResetPassword => 'Zresetuj hasło'; + + @override + String get labelChooseNewPass => 'Wybierz nowe hasło'; + + @override + String get labelCreatePass => 'Hasło'; + + @override + String get informYourPassHasBeenReset => 'Państwa hasło zostało zresetowane'; + + @override + String get informRedirectLogin => 'Proszę zalogować się przy użyciu nowego hasła. Proszę poczekać, przekierowujemy Pana/Panią.'; + + @override + String get actionResetPass => 'Zresetuj moje hasło'; + + @override + String get informInvalidPasswordFormat => 'Wprowadź prawidłowy format hasła'; + + @override + String get labelCheckEmail => 'Sprawdź swój adres e-mail'; + + @override + String informSendResetPasswordEmail(String email) { + return 'Wysłaliśmy wiadomość na adres $email z linkiem umożliwiającym zresetowanie hasła.'; + } + + @override + String get informResetPassByEmail => 'Prześlemy Ci wiadomość e-mail z instrukcją resetowania hasła.'; + + @override + String get labelSelectCountry => 'Gdzie mieszkasz?'; + + @override + String get labelChooseCountry => 'Wybierz kraj'; + + @override + String get warnCountryNotAvailable => 'Niestety Deriv jest niedostępny w Twoim kraju.'; + + @override + String get actionNext => 'Następny'; + + @override + String get labelEmailIssueHeader => 'Jeśli nie widzisz wiadomości e-mail od nas już od kilku minut, kilka rzeczy może być tego powodem:'; + + @override + String get labelEmailIssueSpam => 'Wiadomość e-mail trafiła do folderu spam (czasami wiadomości się tam gubią).'; + + @override + String get labelEmailIssueWrongEmail => 'Przez przypadek podałeś/podałaś nam inny adres e-mail (zazwyczaj jest to adres e-mail z pracy lun osobisty, zamiast właściwego).'; + + @override + String get labelEmailIssueTypo => 'Wprowadzony przez Ciebie adres e-mail zawiera błąd lub literówkę (zdarza się najlepszym).'; + + @override + String get labelEmailIssueFirewall => 'Nie jesteśmy w stanie dostarczyć wiadomości e-mail pod ten adres (zazwyczaj z powodu zapory ogniowej lub filtrów).'; + + @override + String get actionReenterEmail => 'Wprowadź email i spróbuj ponownie'; + + @override + String get labelKeepPassword => 'Zabezpiecz swoje konto hasłem'; + + @override + String get labelCreatePassword => 'Utwórz hasło'; + + @override + String get actionStartTrading => 'Rozpocznij handlowanie'; + + @override + String get actionPrevious => 'Poprzedni'; + + @override + String get labelSignUp => 'Zarejestruj się'; + + @override + String get labelOrSignUpWith => 'Lub zarejestruj się przy użyciu'; + + @override + String get labelReferralInfoTitle => 'Kod polecający partnera'; + + @override + String get infoReferralInfoDescription => 'Kod alfanumeryczny dostarczony przez partnera Deriv, mający zastosowanie wyłącznie do rejestracji e-mail.'; + + @override + String get labelGotReferralCode => 'Mają Państwo kod polecający?'; + + @override + String get labelHaveAccount => 'Masz już konto?'; + + @override + String get actionCreateAccount => 'Utwórz darmowe konto demonstracyjne'; + + @override + String get informInvalidReferralCode => 'Wprowadzony kod polecający jest nieprawidłowy. Proszę sprawdzić i spróbować ponownie.'; + + @override + String get labelVerifyYourEmail => 'Zweryfikuj adres e-mail'; + + @override + String get labelThanksEmail => 'Dziękujemy za zweryfikowanie adresu e-mail'; + + @override + String get informLetsContinue => 'Proszę kontynuować.'; + + @override + String get actionContinue => 'Kontynuuj'; + + @override + String get labelSearchCountry => 'Proszę wyszukać kraj'; + + @override + String informVerificationEmailSent(String email) { + return 'Wysłaliśmy wiadomość na adres $email z linkiem do aktywacji Państwa konta.'; + } + + @override + String get actionEmailNotReceived => 'Wiadomość e-mail nie dotarła?'; + + @override + String get informPasswordPolicy => 'Państwa hasło musi zawierać:'; + + @override + String get informPasswordPolicyLength => '8-25 znaków'; + + @override + String get informPasswordPolicyLowerAndUpper => 'Wielkie i małe litery'; + + @override + String get informPasswordPolicyNumber => 'Co najmniej jeden numer'; + + @override + String get warnPasswordContainsSymbol => 'Proszę używać symboli dla silnego hasła.'; + + @override + String get labelReferralCode => 'Kod polecenia'; + + @override + String get actionTryAgain => 'Spróbuj ponownie'; + + @override + String get informInvalid2FACode => 'Wprowadzony kod jest nieprawidłowy. Proszę sprawdzić i spróbować ponownie.'; + + @override + String get informFailedAuthentication => 'Państwa adres e-mail lub hasło mogą być nieprawidłowe. Czy zarejestrował(a) się Pan(i) za pomocą konta społecznościowego? Proszę sprawdzić i spróbować ponownie.'; + + @override + String get informDeactivatedAccount => 'Twoje konto jest dezaktywowane.'; + + @override + String get informFailedAuthorization => 'Autoryzacja nie powiodła się.'; + + @override + String get informInvalidResidence => 'Nieprawidłowe miejsce zamieszkania.'; + + @override + String get informInvalidCredentials => 'Nieprawidłowe poświadczenia.'; + + @override + String get informMissingOtp => 'Brakujące jednorazowe hasło.'; + + @override + String get informSelfClosed => 'To konto zostało zamknięte.'; + + @override + String get informUnexpectedError => 'Wystąpił nieoczekiwany błąd.'; + + @override + String get informUnsupportedCountry => 'Twój kraj nie jest wspierany.'; + + @override + String get informExpiredAccount => 'Twoje konto wygasło'; + + @override + String get labelCountryConsentBrazil => 'Niniejszym potwierdzam, że mój wniosek o otwarcie rachunku w Deriv w celu handlu produktami OTC emitowanymi i oferowanymi wyłącznie poza Brazylią został zainicjowany przeze mnie. W pełni rozumiem, że Deriv nie jest regulowany przez CVM i zwracając się do Deriv, zamierzam nawiązać relację z zagraniczną firmą.'; + + @override + String get informConnectionError => 'Błąd połączenia. Spróbuj ponownie później.'; + + @override + String get informSwitchAccountError => 'Błąd przełączania konta. Spróbuj ponownie później.'; + + @override + String get labelDeveloper => 'Deweloper'; + + @override + String get labelEndpoint => 'Punkt końcowy'; + + @override + String get semanticEndpoint => 'punkt końcowy'; + + @override + String get warnInvalidEndpoint => 'nieprawidłowy punkt końcowy'; + + @override + String get labelApplicationID => 'ID aplikacji'; + + @override + String get semanticApplicationID => 'ID aplikacji'; + + @override + String get warnInvalidApplicationID => 'nieprawidłowe ID aplikacji'; + + @override + String get labelLanguage => 'Język'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_pt.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_pt.dart new file mode 100644 index 000000000..a4803dd42 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_pt.dart @@ -0,0 +1,276 @@ +import 'deriv_auth_localizations.dart'; + +/// The translations for Portuguese (`pt`). +class DerivAuthLocalizationsPt extends DerivAuthLocalizations { + DerivAuthLocalizationsPt([String locale = 'pt']) : super(locale); + + @override + String get labelNotAvailable => 'Indisponível'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return 'A $app não está disponível no seu país'; + } + + @override + String get actionOk => 'OK'; + + @override + String get warnNotAvailableCountries => 'Em caso de dúvidas, contacte-nos via '; + + @override + String get labelLiveChat => 'Live chat'; + + @override + String get actionSignUpForFree => 'Registe-se gratuitamente'; + + @override + String get actionLogin => 'Iniciar sessão'; + + @override + String get labelTwoFactorAuth => 'Autenticação de dois fatores'; + + @override + String get informEnterTwoFactorAuthCode => 'Introduza o código de 6 dígitos da aplicação de autenticação no seu telemóvel.'; + + @override + String get labelTwoFactorAuthenticationCode => 'Código de autenticação de dois fatores (2FA)'; + + @override + String get actionProceed => 'Continuar'; + + @override + String get labelLogIn => 'Iniciar sessão'; + + @override + String get informLoginOptions => 'Ou inicie sessão através de'; + + @override + String get labelEmail => 'E-mail'; + + @override + String get labelPassword => 'Palavra-passe'; + + @override + String get actionForgotPassword => 'Esqueceu-se da palavra-passe?'; + + @override + String get labelDontHaveAnAccountYet => 'Ainda não tem uma conta?'; + + @override + String get actionCreateANewAccount => 'Criar nova conta'; + + @override + String get informInvalidEmailFormat => 'Introduza um e-mail válido'; + + @override + String get warnPasswordLength => 'Deve introduzir entre 6 a 25 caracteres.'; + + @override + String get labelResetPassword => 'Repor palavra-passe'; + + @override + String get labelChooseNewPass => 'Escolha uma nova palavra-passe'; + + @override + String get labelCreatePass => 'Palavra-passe'; + + @override + String get informYourPassHasBeenReset => 'A palavra-passe foi alterada'; + + @override + String get informRedirectLogin => 'É necessário iniciar sessão com a sua nova palavra-passe. Aguarde, estamos a reencaminhá-lo.'; + + @override + String get actionResetPass => 'Alterar palavra-passe'; + + @override + String get informInvalidPasswordFormat => 'Introduza um formato de palavra-passe válido'; + + @override + String get labelCheckEmail => 'Verifique seu e-mail'; + + @override + String informSendResetPasswordEmail(String email) { + return 'Enviámos uma mensagem para o e-mail $email com um link para alterar a sua palavra-passe.'; + } + + @override + String get informResetPassByEmail => 'Vamos enviar um e-mail com as instruções para alterar a sua palavra-passe.'; + + @override + String get labelSelectCountry => 'Onde é que vive?'; + + @override + String get labelChooseCountry => 'Selecionar país'; + + @override + String get warnCountryNotAvailable => 'Infelizmente, a Deriv não está disponível no seu país.'; + + @override + String get actionNext => 'Próximo'; + + @override + String get labelEmailIssueHeader => 'Se não receber um e-mail nosso dentro de alguns minutos, é possível que:'; + + @override + String get labelEmailIssueSpam => 'O e-mail encontre-se na pasta de spam (às vezes os e-mails ficam perdidos aí).'; + + @override + String get labelEmailIssueWrongEmail => 'Acidentalmente, deu-nos outro e-mail (normalmente um e-mail de trabalho ou pessoal, em vez daquele que pretendia).'; + + @override + String get labelEmailIssueTypo => 'O e-mail que introduziu tinha um erro ou uma gralha (acontece aos melhores).'; + + @override + String get labelEmailIssueFirewall => 'Não foi possível enviar um e-mail para este endereço (normalmente devido a firewalls ou filtragem).'; + + @override + String get actionReenterEmail => 'Volte a introduzir o seu e-mail e tente novamente'; + + @override + String get labelKeepPassword => 'Mantenha a sua conta segura utilizando uma palavra-passe'; + + @override + String get labelCreatePassword => 'Criar palavra-passe'; + + @override + String get actionStartTrading => 'Começar a negociar'; + + @override + String get actionPrevious => 'Anterior'; + + @override + String get labelSignUp => 'Registe-se'; + + @override + String get labelOrSignUpWith => 'Ou registe-se através de'; + + @override + String get labelReferralInfoTitle => 'Código de referência de afiliado'; + + @override + String get infoReferralInfoDescription => 'Código alfanumérico fornecido por um afiliado Deriv, aplicável apenas para registos por e-mail.'; + + @override + String get labelGotReferralCode => 'Tem um código de referência?'; + + @override + String get labelHaveAccount => 'Já tem uma conta?'; + + @override + String get actionCreateAccount => 'Criar conta demo gratuita'; + + @override + String get informInvalidReferralCode => 'O código de referência que introduziu não é válido. Verifique e tente novamente.'; + + @override + String get labelVerifyYourEmail => 'Validar e-mail'; + + @override + String get labelThanksEmail => 'Obrigado por validar o seu e-mail'; + + @override + String get informLetsContinue => 'Vamos continuar.'; + + @override + String get actionContinue => 'Continuar'; + + @override + String get labelSearchCountry => 'Pesquisar país'; + + @override + String informVerificationEmailSent(String email) { + return 'Enviámos uma mensagem para o $email com o link para ativar a sua conta.'; + } + + @override + String get actionEmailNotReceived => 'Não recebeu o e-mail?'; + + @override + String get informPasswordPolicy => 'A palavra-passe deve ter:'; + + @override + String get informPasswordPolicyLength => 'Entre 8 a 25 caracteres'; + + @override + String get informPasswordPolicyLowerAndUpper => 'Letras maiúsculas e minúsculas'; + + @override + String get informPasswordPolicyNumber => 'Pelo menos um número'; + + @override + String get warnPasswordContainsSymbol => 'Utilize símbolos para criar uma palavra-passe forte.'; + + @override + String get labelReferralCode => 'Código de referência'; + + @override + String get actionTryAgain => 'Tente novamente'; + + @override + String get informInvalid2FACode => 'O código que introduziu não é válido. Verifique e tente novamente.'; + + @override + String get informFailedAuthentication => 'O e-mail ou a palavra-passe podem estar incorretos. Registou-se através de umas das contas das redes sociais? Verifique e tente novamente.'; + + @override + String get informDeactivatedAccount => 'A sua conta encontra-se desativada.'; + + @override + String get informFailedAuthorization => 'A autorização não foi bem-sucedida.'; + + @override + String get informInvalidResidence => 'Morada inválida.'; + + @override + String get informInvalidCredentials => 'Credenciais inválidas.'; + + @override + String get informMissingOtp => 'Falta a palavra-passe de utilização única.'; + + @override + String get informSelfClosed => 'A sua conta foi encerrada.'; + + @override + String get informUnexpectedError => 'Ocorreu um erro inesperado.'; + + @override + String get informUnsupportedCountry => 'O seu país não está disponível.'; + + @override + String get informExpiredAccount => 'A sua conta expirou'; + + @override + String get labelCountryConsentBrazil => 'Venho por este meio confirmar que iniciei o pedido de abertura de conta na Deriv para negociar produtos OTC emitidos e oferecidos exclusivamente fora do Brasil. Estou ciente que a Deriv não é regulada pela CVM e que ao abordar a Deriv, estabeleço uma relação com uma empresa estrangeira.'; + + @override + String get informConnectionError => 'Erro de ligação. Por favor, tente novamente mais tarde.'; + + @override + String get informSwitchAccountError => 'Ocorreu um erro durante a troca de conta. Tente novamente mais tarde.'; + + @override + String get labelDeveloper => 'Programador'; + + @override + String get labelEndpoint => 'Endpoint'; + + @override + String get semanticEndpoint => 'Endpoint'; + + @override + String get warnInvalidEndpoint => 'endpoint inválido'; + + @override + String get labelApplicationID => 'ID da aplicação'; + + @override + String get semanticApplicationID => 'ID da aplicação'; + + @override + String get warnInvalidApplicationID => 'ID da aplicação inválido'; + + @override + String get labelLanguage => 'Idioma'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_ru.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_ru.dart new file mode 100644 index 000000000..e56b8dcab --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_ru.dart @@ -0,0 +1,276 @@ +import 'deriv_auth_localizations.dart'; + +/// The translations for Russian (`ru`). +class DerivAuthLocalizationsRu extends DerivAuthLocalizations { + DerivAuthLocalizationsRu([String locale = 'ru']) : super(locale); + + @override + String get labelNotAvailable => 'Недоступно'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return '$app недоступен в вашей стране'; + } + + @override + String get actionOk => 'OK'; + + @override + String get warnNotAvailableCountries => 'Если у Вас возникли вопросы, свяжитесь с нами по '; + + @override + String get labelLiveChat => 'Чат'; + + @override + String get actionSignUpForFree => 'Зарегистрируйтесь бесплатно'; + + @override + String get actionLogin => 'Вход'; + + @override + String get labelTwoFactorAuth => 'Двухфакторная аутентификация'; + + @override + String get informEnterTwoFactorAuthCode => 'Введите 6-значный код из приложения аутентификатора на Вашем телефоне.'; + + @override + String get labelTwoFactorAuthenticationCode => 'Код 2FA'; + + @override + String get actionProceed => 'Далее'; + + @override + String get labelLogIn => 'Вход'; + + @override + String get informLoginOptions => 'Или войдите через'; + + @override + String get labelEmail => 'Email'; + + @override + String get labelPassword => 'Пароль'; + + @override + String get actionForgotPassword => 'Забыли пароль?'; + + @override + String get labelDontHaveAnAccountYet => 'У Вас еще нет учетной записи?'; + + @override + String get actionCreateANewAccount => 'Откройте новый счет'; + + @override + String get informInvalidEmailFormat => 'Введите действительный адрес электронной почты'; + + @override + String get warnPasswordLength => 'Вы должны ввести 8-25 символов.'; + + @override + String get labelResetPassword => 'Сбросить пароль'; + + @override + String get labelChooseNewPass => 'Выберите новый пароль'; + + @override + String get labelCreatePass => 'Пароль'; + + @override + String get informYourPassHasBeenReset => 'Ваш пароль был сброшен'; + + @override + String get informRedirectLogin => 'Вам нужно будет войти в систему с новым паролем. Подождите, мы перенаправляем Вас.'; + + @override + String get actionResetPass => 'Изменить пароль'; + + @override + String get informInvalidPasswordFormat => 'Введите пароль в действующем формате'; + + @override + String get labelCheckEmail => 'Проверьте эл. почту'; + + @override + String informSendResetPasswordEmail(String email) { + return 'Мы отправили сообщение на адрес $email со ссылкой для сброса Вашего пароля.'; + } + + @override + String get informResetPassByEmail => 'Инструкции для изменения пароля будут отправлены на ваш эл. адрес.'; + + @override + String get labelSelectCountry => 'Где вы живете?'; + + @override + String get labelChooseCountry => 'Выберите страну'; + + @override + String get warnCountryNotAvailable => 'К сожалению, Deriv недоступен в вашей стране.'; + + @override + String get actionNext => 'Далее'; + + @override + String get labelEmailIssueHeader => 'Если вы не получили наше письмо в течение нескольких минут, возможно, произошло следующее:'; + + @override + String get labelEmailIssueSpam => 'Письмо попало в папку со спамом.'; + + @override + String get labelEmailIssueWrongEmail => 'Вы по ошибке указали неправильный email (как правило, рабочий или личный email вместо нужного).'; + + @override + String get labelEmailIssueTypo => 'Адрес эл. почты был введен с ошибкой или опечаткой (случается с лучшими из нас).'; + + @override + String get labelEmailIssueFirewall => 'Мы не можем отправить письмо на этот адрес (из-за использования firewall или фильтрации).'; + + @override + String get actionReenterEmail => 'Введите email и попробуйте снова'; + + @override + String get labelKeepPassword => 'Защитите счет паролем'; + + @override + String get labelCreatePassword => 'Придумайте пароль'; + + @override + String get actionStartTrading => 'Начать торговлю'; + + @override + String get actionPrevious => 'Назад'; + + @override + String get labelSignUp => 'Регистрация'; + + @override + String get labelOrSignUpWith => 'Или войдите через'; + + @override + String get labelReferralInfoTitle => 'Партнерский реферальный код'; + + @override + String get infoReferralInfoDescription => 'Буквенно-цифровой код, предоставляемый партнером Deriv, применимый только для регистрации по электронной почте.'; + + @override + String get labelGotReferralCode => 'У Вас есть реферальный код?'; + + @override + String get labelHaveAccount => 'Уже зарегистрированы?'; + + @override + String get actionCreateAccount => 'Открыть бесплатный демо-счет'; + + @override + String get informInvalidReferralCode => 'Введенный Вами реферальный код недействителен. Проверьте и попробуйте еще раз.'; + + @override + String get labelVerifyYourEmail => 'Подтвердите эл. адрес'; + + @override + String get labelThanksEmail => 'Спасибо за подтверждение электронной почты'; + + @override + String get informLetsContinue => 'Давайте продолжим.'; + + @override + String get actionContinue => 'Продолжить'; + + @override + String get labelSearchCountry => 'Страна поиска'; + + @override + String informVerificationEmailSent(String email) { + return 'Мы отправили сообщение на $email со ссылкой для активации Вашей учетной записи.'; + } + + @override + String get actionEmailNotReceived => 'Не получили письмо?'; + + @override + String get informPasswordPolicy => 'Ваш пароль должен иметь:'; + + @override + String get informPasswordPolicyLength => '8-25 символов'; + + @override + String get informPasswordPolicyLowerAndUpper => 'Строчные и заглавные буквы'; + + @override + String get informPasswordPolicyNumber => 'По крайней мере, одно число'; + + @override + String get warnPasswordContainsSymbol => 'Используйте символы для создания надежного пароля.'; + + @override + String get labelReferralCode => 'Реферальный код'; + + @override + String get actionTryAgain => 'Повторить'; + + @override + String get informInvalid2FACode => 'Введенный Вами код недействителен. Проверьте и попробуйте еще раз.'; + + @override + String get informFailedAuthentication => 'Возможно, Ваш e-mail или пароль неверны. Вы зарегистрировались с помощью социальной учетной записи? Проверьте и попробуйте еще раз.'; + + @override + String get informDeactivatedAccount => 'Ваш аккаунт деактивирован.'; + + @override + String get informFailedAuthorization => 'Не удалось авторизоваться.'; + + @override + String get informInvalidResidence => 'Недействительное место жительства.'; + + @override + String get informInvalidCredentials => 'Неверные учетные данные.'; + + @override + String get informMissingOtp => 'Отсутствует одноразовый пароль.'; + + @override + String get informSelfClosed => 'Ваш счет был закрыт.'; + + @override + String get informUnexpectedError => 'Произошла неожиданная ошибка.'; + + @override + String get informUnsupportedCountry => 'Ваша страна не пользуется поддержкой.'; + + @override + String get informExpiredAccount => 'Срок действия вашей учетной записи истек'; + + @override + String get labelCountryConsentBrazil => 'Настоящим я подтверждаю, что запрос на открытие счета в Deriv для торговли внебиржевыми продуктами, выпущенными и предлагаемыми исключительно за пределами Бразилии, был инициирован мной. Я понимаю, что деятельность Deriv не регулируется CVM, и, обратившись в Deriv, вступаю в деловые отношения с иностранной компанией.'; + + @override + String get informConnectionError => 'Ошибка подключения. Пожалуйста, попробуйте позже.'; + + @override + String get informSwitchAccountError => 'Ошибка переключения учетной записи. Пожалуйста, попробуйте позже.'; + + @override + String get labelDeveloper => 'Разработчик'; + + @override + String get labelEndpoint => 'Конечная точка'; + + @override + String get semanticEndpoint => 'Конечная точка'; + + @override + String get warnInvalidEndpoint => 'неверная конечная точка'; + + @override + String get labelApplicationID => 'ID приложения'; + + @override + String get semanticApplicationID => 'ID приложения'; + + @override + String get warnInvalidApplicationID => 'неверный ID приложения'; + + @override + String get labelLanguage => 'Язык'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_si.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_si.dart new file mode 100644 index 000000000..a99eca270 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_si.dart @@ -0,0 +1,276 @@ +import 'deriv_auth_localizations.dart'; + +/// The translations for Sinhala Sinhalese (`si`). +class DerivAuthLocalizationsSi extends DerivAuthLocalizations { + DerivAuthLocalizationsSi([String locale = 'si']) : super(locale); + + @override + String get labelNotAvailable => 'ලබා ගත නොහැක'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return '$app ඔබේ රටේ නොමැත'; + } + + @override + String get actionOk => 'හරි'; + + @override + String get warnNotAvailableCountries => 'ඔබට කිසියම් ප්රශ්නයක් ඇත්නම්, අප අමතන්න '; + + @override + String get labelLiveChat => 'සජීවී කතාබස්'; + + @override + String get actionSignUpForFree => 'නොමිලේ ලියාපදිංචි වන්න'; + + @override + String get actionLogin => 'පුරනය වන්න'; + + @override + String get labelTwoFactorAuth => 'ද්වි-සාධක සත්‍යාපනය'; + + @override + String get informEnterTwoFactorAuthCode => 'ඔබේ දුරකථනයේ සත්යාපන යෙදුමෙන් ඉලක්කම් 6 ක කේතය ඇතුළත් කරන්න.'; + + @override + String get labelTwoFactorAuthenticationCode => '2FA කේතය'; + + @override + String get actionProceed => 'ඉදිරියට යන්න'; + + @override + String get labelLogIn => 'පුරනය වන්න'; + + @override + String get informLoginOptions => 'නැතහොත් සමඟ ලොග් වන්න'; + + @override + String get labelEmail => 'ඊ-තැපෑල'; + + @override + String get labelPassword => 'මුරපදය'; + + @override + String get actionForgotPassword => 'මුරපදය අමතක ද?'; + + @override + String get labelDontHaveAnAccountYet => 'තවම ගිණුමක් නැද්ද?'; + + @override + String get actionCreateANewAccount => 'නව ගිණුමක් සාදන්න'; + + @override + String get informInvalidEmailFormat => 'වලංගු විද්යුත් තැපැල් ලිපිනයක් ඇතුළත් කරන්න'; + + @override + String get warnPasswordLength => 'ඔබ අක්ෂර 8-25 ක් ඇතුළත් කළ යුතුය.'; + + @override + String get labelResetPassword => 'මුරපදය නැවත සකස් කරන්න'; + + @override + String get labelChooseNewPass => 'නව මුරපදයක් තෝරන්න'; + + @override + String get labelCreatePass => 'මුරපදය'; + + @override + String get informYourPassHasBeenReset => 'ඔබගේ මුරපදය නැවත සකසා ඇත'; + + @override + String get informRedirectLogin => 'ඔබට ඔබගේ නව මුරපදය සමඟ ලොග් විය යුතුය. ඉතින්, අපි ඔයාව හරවා යවනවා.'; + + @override + String get actionResetPass => 'මගේ මුරපදය යළි සකසන්න'; + + @override + String get informInvalidPasswordFormat => 'කරුණාකර වලංගු මුරපද ආකෘතියක් ඇතුළත්'; + + @override + String get labelCheckEmail => 'ඔබේ ඊ-තැපෑල පරීක්ෂා කරන්න​'; + + @override + String informSendResetPasswordEmail(String email) { + return 'ඔබගේ මුරපදය නැවත සකස් කිරීම සඳහා සබැඳියක් $email සහිතව අපි වෙත පණිවිඩයක් යවා ඇත්තෙමු.'; + } + + @override + String get informResetPassByEmail => 'ඔබේ මුරපදය යළි සැකසීමට අවශ්‍ය උපදෙස් අපි ඔබට ඊ-තැපෑලෙන් එවන්නෙමු.'; + + @override + String get labelSelectCountry => 'ඔබ ජීවත් වන්නේ කොහේද?'; + + @override + String get labelChooseCountry => 'රට තෝරන්න'; + + @override + String get warnCountryNotAvailable => 'අවාසනාවකට මෙන්, ඩෙරිව් ඔබේ රටේ නොලැබේ.'; + + @override + String get actionNext => 'ඊළඟ'; + + @override + String get labelEmailIssueHeader => 'මිනිත්තු කිහිපයක් ඇතුළත ඔබට අපෙන් ඊ-තැපෑලක් නොලැබුණොත්, පහත සඳහන් දේවල් සිදු විය හැක:'; + + @override + String get labelEmailIssueSpam => 'ඊ-තැපෑල ඔබේ අයාචිත තැපැල් ෆෝල්ඩරයේ ඇත (සමහර ඊ-තැපැල් ඒ වෙත යොමු වෙයි).'; + + @override + String get labelEmailIssueWrongEmail => 'ඔබ අහම්බෙන් අපට වෙනත් ඊ-තැපැල් ලිපිනයක් ලබා දී ඇත​ (සාමාන්‍යයෙන් ඔබ අදහස් කරන එක වෙනුවට කාර්යාලීය හෝ පෞද්ගලික ඊ තැපැල් ලිපිනයක්).'; + + @override + String get labelEmailIssueTypo => 'ඔබ ඇතුළත් කළ​ ඊ-තැපැල් ලිපිනයේ වැරදීමක් හෝ මුද්‍රණ දෝෂයක් ඇත (බොහෝ අයට මෙය සිදු වේ).'; + + @override + String get labelEmailIssueFirewall => 'අපිට මේ ලිපිනයට ඊමේල් එක බාර දෙන්න බැහැ (සාමාන්යයෙන් ෆයර්වෝල් හෝ ෆිල්ටිං නිසා).'; + + @override + String get actionReenterEmail => 'ඔබගේ ඊ-තැපෑල නැවත ඇතුළත් කර නැවත උත්සාහ කරන්න'; + + @override + String get labelKeepPassword => 'මුරපදයකින් ඔබේ ගිණුම සුරක්ෂිතව තබා ගන්න'; + + @override + String get labelCreatePassword => 'මුරපදයක් සාදන්න'; + + @override + String get actionStartTrading => 'ගනුදෙනු ආරම්භ කරන්න'; + + @override + String get actionPrevious => 'පෙර'; + + @override + String get labelSignUp => 'ලියාපදිංචි වන්න'; + + @override + String get labelOrSignUpWith => 'නැතහොත් ලියාපදිංචි වන්න'; + + @override + String get labelReferralInfoTitle => 'අනුබද්ධ යොමු කේතය'; + + @override + String get infoReferralInfoDescription => 'ඊමේල් ලියාපදිංචි කිරීම් සඳහා පමණක් අදාළ වන ඩෙරිව් අනුබද්ධ සමාගමක් විසින් සපයනු ලබන අක්ෂර කේතයක්.'; + + @override + String get labelGotReferralCode => 'යොමු කේතයක් තිබේද?'; + + @override + String get labelHaveAccount => 'දැනටමත් ගිණුමක් තිබේද?'; + + @override + String get actionCreateAccount => 'නොමිලේ ආදර්ශන ගිණුමක් සාදන්න'; + + @override + String get informInvalidReferralCode => 'ඔබ ඇතුළත් කළ යොමු කේතය අවලංගු වේ. පරීක්ෂා කර නැවත උත්සාහ කරන්න.'; + + @override + String get labelVerifyYourEmail => 'ඔබගේ විද්යුත් තැපෑල තහවුරු'; + + @override + String get labelThanksEmail => 'ඔබගේ විද්යුත් තැපෑල සත්යාපනය කිරීම ගැන ස්ත'; + + @override + String get informLetsContinue => 'අපි දිගටම කරගෙන යමු.'; + + @override + String get actionContinue => 'ඉදිරියට යන්න'; + + @override + String get labelSearchCountry => 'රට සොයන්න'; + + @override + String informVerificationEmailSent(String email) { + return 'ඔබගේ ගිණුම සක්රිය කිරීම සඳහා සබැඳියක් ස $email හිතව අපි වෙත පණිවිඩයක් යවා ඇත්තෙමු.'; + } + + @override + String get actionEmailNotReceived => 'ඔබගේ ඊ-තැපෑල ලැබුණේ නැද්ද?'; + + @override + String get informPasswordPolicy => 'ඔබගේ මුරපදය තිබිය යුතුය:'; + + @override + String get informPasswordPolicyLength => 'අක්ෂර 8-25'; + + @override + String get informPasswordPolicyLowerAndUpper => 'ඉහළ සහ පහළ අකුරු'; + + @override + String get informPasswordPolicyNumber => 'අවම වශයෙන් එක් අංකයක්'; + + @override + String get warnPasswordContainsSymbol => 'ශක්තිමත් මුරපදය සඳහා සංකේත භාවිතා කරන්න.'; + + @override + String get labelReferralCode => 'යොමු කේතය'; + + @override + String get actionTryAgain => 'නැවත උත්සාහ කරන්න'; + + @override + String get informInvalid2FACode => 'ඔබ ඇතුළත් කළ කේතය අවලංගු වේ. පරීක්ෂා කර නැවත උත්සාහ කරන්න.'; + + @override + String get informFailedAuthentication => 'ඔබගේ විද්යුත් තැපෑල හෝ මුරපදය වැරදි විය හැකිය. ඔබ සමාජ ගිණුමක් සමඟ ලියාපදිංචි වූවාද? පරීක්ෂා කර නැවත උත්සාහ කරන්න.'; + + @override + String get informDeactivatedAccount => 'ඔබගේ ගිණුම අක්රිය කර ඇත.'; + + @override + String get informFailedAuthorization => 'බලය අසාර්ථක විය.'; + + @override + String get informInvalidResidence => 'අවලංගු පදිංචිය.'; + + @override + String get informInvalidCredentials => 'අවලංගු අක්තපත්ර'; + + @override + String get informMissingOtp => 'එක් වරක් මුරපදය අතුරුදහන් වේ.'; + + @override + String get informSelfClosed => 'ඔබේ ගිණුම වසා ඇත.'; + + @override + String get informUnexpectedError => 'අනපේක්ෂිත දෝෂයක් ඇති විය.'; + + @override + String get informUnsupportedCountry => 'ඔබේ රට සහයෝගය නොදක්වයි.'; + + @override + String get informExpiredAccount => 'ඔබගේ ගිණුම කල් ඉකුත් වී ඇත'; + + @override + String get labelCountryConsentBrazil => 'බ්‍රසීලයෙන් පිටතදී නිකුත් කරන ලද සහ පිරිනමන OTC නිෂ්පාදන ගනුදෙනු කිරීමට Deriv සමඟ ගිණුමක් විවෘත කිරීම සඳහා වන මගේ ඉල්ලීම මා විසින් ආරම්භ කරන ලද බව මෙයින් තහවුරු කරමි. Deriv CVM මඟින් නියාමනය නොවන බව මට හොඳින් වැටහෙන අතර Deriv වෙත පිවිසීමෙන් මම විදේශීය සමාගමක් සමඟ සබඳතාවක් පිහිටුවීමට අදහස් කරමි.'; + + @override + String get informConnectionError => 'සම්බන්ධතා දෝෂය. කරුණාකර පසුව නැවත උත්සාහ කරන්න.'; + + @override + String get informSwitchAccountError => 'ගිණුම් දෝෂය මාරු කරන්න. කරුණාකර පසුව නැවත උත්සාහ කරන්න.'; + + @override + String get labelDeveloper => 'සංවර්ධක'; + + @override + String get labelEndpoint => 'අන්ත ලක්ෂ්‍යය'; + + @override + String get semanticEndpoint => 'අන්ත ලක්ෂ්‍යය'; + + @override + String get warnInvalidEndpoint => 'අවලංගු අවසාන ලක්ෂ්යය'; + + @override + String get labelApplicationID => 'අයදුම් හැඳුනුම්පත'; + + @override + String get semanticApplicationID => 'අයදුම් හැඳුනුම්පත'; + + @override + String get warnInvalidApplicationID => 'අවලංගු යෙදුම් හැඳුනු'; + + @override + String get labelLanguage => 'භාෂාව'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_sw.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_sw.dart new file mode 100644 index 000000000..35f8be41f --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_sw.dart @@ -0,0 +1,276 @@ +import 'deriv_auth_localizations.dart'; + +/// The translations for Swahili (`sw`). +class DerivAuthLocalizationsSw extends DerivAuthLocalizations { + DerivAuthLocalizationsSw([String locale = 'sw']) : super(locale); + + @override + String get labelNotAvailable => 'Haipatikani'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return '$app haipatikani katika nchi yako'; + } + + @override + String get actionOk => 'SAWA'; + + @override + String get warnNotAvailableCountries => 'Ikiwa una maswali yoyote, wasiliana nasi kupitia '; + + @override + String get labelLiveChat => 'Mazungumzo mubashara'; + + @override + String get actionSignUpForFree => 'Jisajili bila malipo'; + + @override + String get actionLogin => 'Ingia'; + + @override + String get labelTwoFactorAuth => 'Uthibitishaji-wa hatua mbili'; + + @override + String get informEnterTwoFactorAuthCode => 'Ingiza nambari ya tarakimu 6 kutoka kwa programu ya uthibitishaji kwenye simu yako.'; + + @override + String get labelTwoFactorAuthenticationCode => 'Nambari ya 2FA'; + + @override + String get actionProceed => 'Endelea'; + + @override + String get labelLogIn => 'Ingia'; + + @override + String get informLoginOptions => 'Au ingia na'; + + @override + String get labelEmail => 'Barua pepe'; + + @override + String get labelPassword => 'Nenosiri'; + + @override + String get actionForgotPassword => 'Umesahau nenosiri?'; + + @override + String get labelDontHaveAnAccountYet => 'Huna akaunti bado?'; + + @override + String get actionCreateANewAccount => 'Unda akaunti mpya'; + + @override + String get informInvalidEmailFormat => 'Ingiza anwani halali ya barua pepe'; + + @override + String get warnPasswordLength => 'Unapaswa kuingiza mchanganyiko wa tarakimu 8-25.'; + + @override + String get labelResetPassword => 'Rudisha nenosiri'; + + @override + String get labelChooseNewPass => 'Chagua nenosiri mpya'; + + @override + String get labelCreatePass => 'Nenosiri'; + + @override + String get informYourPassHasBeenReset => 'Nenosiri lako limewekwa upya'; + + @override + String get informRedirectLogin => 'Utahitaji kuingia na nenosiri lako jipya. Endelea, tunakuelekeza.'; + + @override + String get actionResetPass => 'Weka upya nywila yangu'; + + @override + String get informInvalidPasswordFormat => 'Tafadhali ingiza muundo halali wa nenosiri'; + + @override + String get labelCheckEmail => 'Angalia barua pepe yako'; + + @override + String informSendResetPasswordEmail(String email) { + return 'Tumetuma ujumbe kwa $email na kiungo cha kuweka upya nenosiri lako.'; + } + + @override + String get informResetPassByEmail => 'Tutakutumia barua pepe yenye maelekezo ya kuweka upya nenosiri lako.'; + + @override + String get labelSelectCountry => 'Unaishi wapi?'; + + @override + String get labelChooseCountry => 'Chagua nchi'; + + @override + String get warnCountryNotAvailable => 'Kwa bahati mbaya, Deriv haipatikani katika nchi yako.'; + + @override + String get actionNext => 'Ifuatayo'; + + @override + String get labelEmailIssueHeader => 'Ikiwa huoni barua pepe kutoka kwetu ndani ya dakika chache, mambo machache yanaweza kuwa yametokea:'; + + @override + String get labelEmailIssueSpam => 'Barua pepe iko kwenye folda yako ya spam (Wakati mwingine mambo yanapotelea huko).'; + + @override + String get labelEmailIssueWrongEmail => 'Kwa bahati mbaya ulitupa anwani nyingine ya barua pepe (Labda ya kazi au ya binafsi badala ya ile uliyokusudia).'; + + @override + String get labelEmailIssueTypo => 'Anwani ya barua pepe uliyoingiza ilikuwa na makosa (hutokea kwa ubora kwetu sote).'; + + @override + String get labelEmailIssueFirewall => 'Hatuwezi kutoa barua pepe kwa anwani hii (Kawaida kwa sababu ya firewalls au kuchuja).'; + + @override + String get actionReenterEmail => 'Ingiza tena barua pepe yako na ujaribu tena'; + + @override + String get labelKeepPassword => 'Weka akaunti yako salama na nenosiri'; + + @override + String get labelCreatePassword => 'Unda nenosiri'; + + @override + String get actionStartTrading => 'Anza biashara'; + + @override + String get actionPrevious => 'Iliyotangulia'; + + @override + String get labelSignUp => 'Jisajili'; + + @override + String get labelOrSignUpWith => 'Au jisajili kwa'; + + @override + String get labelReferralInfoTitle => 'Nambari ya rufaa ya ushirika'; + + @override + String get infoReferralInfoDescription => 'Nambari ya alfanisi iliyotolewa na mshirika wa Deriv, inayotumika kwa usajili wa barua pepe tu.'; + + @override + String get labelGotReferralCode => 'Una nambari ya rufaa?'; + + @override + String get labelHaveAccount => 'Tayari una akaunti?'; + + @override + String get actionCreateAccount => 'Unda demo akaunti'; + + @override + String get informInvalidReferralCode => 'Nambari ya rufaa uliyoingiza sio halali. Angalia na jaribu tena.'; + + @override + String get labelVerifyYourEmail => 'thibitisha barua pepe yako'; + + @override + String get labelThanksEmail => 'Asante kwa kuthibitisha barua pepe'; + + @override + String get informLetsContinue => 'Hebu tuendelee.'; + + @override + String get actionContinue => 'Endelea'; + + @override + String get labelSearchCountry => 'Tafuta nchi'; + + @override + String informVerificationEmailSent(String email) { + return 'Tumetuma ujumbe kwa $email na kiungo cha kuamsha akaunti yako.'; + } + + @override + String get actionEmailNotReceived => 'Hukupokea barua pepe yako?'; + + @override + String get informPasswordPolicy => 'Nenosiri lako lazima iwe na:'; + + @override + String get informPasswordPolicyLength => '8-25 herufi'; + + @override + String get informPasswordPolicyLowerAndUpper => 'Barua za juu na ndogo'; + + @override + String get informPasswordPolicyNumber => 'Angalau nambari moja'; + + @override + String get warnPasswordContainsSymbol => 'Tumia alama kwa nenosiri yenye nguvu.'; + + @override + String get labelReferralCode => 'Msimbo wa Rufaa'; + + @override + String get actionTryAgain => 'Jaribu tena'; + + @override + String get informInvalid2FACode => 'Nambari uliyoingiza ni halali. Angalia na jaribu tena.'; + + @override + String get informFailedAuthentication => 'Barua pepe yako au nenosiri inaweza kuwa sahihi. Je! Ulijiandikisha na akaunti ya kijamii? Angalia na jaribu tena.'; + + @override + String get informDeactivatedAccount => 'Akaunti yako imezimwa.'; + + @override + String get informFailedAuthorization => 'Idhini ilishindwa.'; + + @override + String get informInvalidResidence => 'Makazi halali.'; + + @override + String get informInvalidCredentials => 'Hati zisizo sahihi.'; + + @override + String get informMissingOtp => 'Kukosa nenosiri la wakati mmoja.'; + + @override + String get informSelfClosed => 'Akaunti yako imefungwa.'; + + @override + String get informUnexpectedError => 'Hitilafu isiyotarajiwa ilitokea'; + + @override + String get informUnsupportedCountry => 'Nchi yako haikuungwa mkono.'; + + @override + String get informExpiredAccount => 'Akaunti yako imekwisha'; + + @override + String get labelCountryConsentBrazil => 'Ninathibitisha kwamba ombi langu la kufungua akaunti na Deriv kufanya biashara ya bidhaa za OTC zilizotolewa na kutolewa tu nje ya Brazil lilianzishwa na mimi. Ninaelewa kikamilifu kwamba Deriv haidhibitiwi na CVM na kwa kukaribia Deriv nina nia ya kuanzisha uhusiano na kampuni ya kigeni.'; + + @override + String get informConnectionError => 'Hitilafu ya unganisho Tafadhali jaribu tena baadaye.'; + + @override + String get informSwitchAccountError => 'Badilisha kosa la akaunti. Tafadhali jaribu tena baadaye.'; + + @override + String get labelDeveloper => 'Msanidi programu'; + + @override + String get labelEndpoint => 'Pointi ya mwisho'; + + @override + String get semanticEndpoint => 'Pointi ya mwisho'; + + @override + String get warnInvalidEndpoint => 'hatua ya mwisho halali'; + + @override + String get labelApplicationID => 'Kitambulisho cha programu'; + + @override + String get semanticApplicationID => 'Kitambulisho cha programu'; + + @override + String get warnInvalidApplicationID => 'kitambulisho cha programu halali'; + + @override + String get labelLanguage => 'Lugha'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_th.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_th.dart new file mode 100644 index 000000000..45f5b63d1 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_th.dart @@ -0,0 +1,276 @@ +import 'deriv_auth_localizations.dart'; + +/// The translations for Thai (`th`). +class DerivAuthLocalizationsTh extends DerivAuthLocalizations { + DerivAuthLocalizationsTh([String locale = 'th']) : super(locale); + + @override + String get labelNotAvailable => 'ไม่พร้อมใช้งาน'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return '$app ไม่พร้อมใช้งานในประเทศของคุณ'; + } + + @override + String get actionOk => 'OK'; + + @override + String get warnNotAvailableCountries => 'หากคุณมีคำถามใดๆ โปรดติดต่อเราผ่านทาง '; + + @override + String get labelLiveChat => 'แชทสด'; + + @override + String get actionSignUpForFree => 'ลงทะเบียนได้ฟรี'; + + @override + String get actionLogin => 'เข้าสู่ระบบ'; + + @override + String get labelTwoFactorAuth => 'ระบบยืนยันตัวตนสองขั้นตอน'; + + @override + String get informEnterTwoFactorAuthCode => 'ป้อนรหัส 6 หลักจากแอปตรวจสอบสิทธิ์บนโทรศัพท์ของคุณ'; + + @override + String get labelTwoFactorAuthenticationCode => 'รหัส 2FA'; + + @override + String get actionProceed => 'ประมวลผล'; + + @override + String get labelLogIn => 'เข้าสู่ระบบ'; + + @override + String get informLoginOptions => 'หรือเข้าสู่ระบบด้วย'; + + @override + String get labelEmail => 'อีเมล์'; + + @override + String get labelPassword => 'รหัสผ่าน'; + + @override + String get actionForgotPassword => 'คุณลืมรหัสผ่านหรือไม่'; + + @override + String get labelDontHaveAnAccountYet => 'ยังไม่มีบัญชีใช่ไหม?'; + + @override + String get actionCreateANewAccount => 'สร้างบัญชีใหม่'; + + @override + String get informInvalidEmailFormat => 'ป้อนที่อยู่อีเมล์ที่ถูกต้อง'; + + @override + String get warnPasswordLength => 'คุณควรป้อนอักขระ 6-25 ตัว'; + + @override + String get labelResetPassword => 'ตั้งค่ารหัสผ่าน'; + + @override + String get labelChooseNewPass => 'เลือกรหัสผ่านอันใหม่'; + + @override + String get labelCreatePass => 'รหัสผ่าน'; + + @override + String get informYourPassHasBeenReset => 'รหัสผ่านของคุณถูกเปลี่ยนแปลงแล้ว'; + + @override + String get informRedirectLogin => 'คุณจะต้องเข้าสู่ระบบด้วยรหัสผ่านใหม่ของคุณ รอสักครู่ เรากำลังพาคุณไป'; + + @override + String get actionResetPass => 'ตั้งรหัสผ่านของฉันใหม่'; + + @override + String get informInvalidPasswordFormat => 'กรุณากรอกรูปแบบรหัสผ่านที่ถูกต้อง'; + + @override + String get labelCheckEmail => 'ตรวจสอบอีเมล์ของคุณ'; + + @override + String informSendResetPasswordEmail(String email) { + return 'เราได้ส่งข้อความไปที่ $email พร้อมลิงก์เพื่อรีเซ็ตรหัสผ่านของคุณ'; + } + + @override + String get informResetPassByEmail => 'เราจะส่งอีเมล์ให้คุณเพื่อแนะนำเกี่ยวกับการตั้งรหัสผ่านใหม่ของคุณ'; + + @override + String get labelSelectCountry => 'คุณอาศัยอยู่ที่ไหน?'; + + @override + String get labelChooseCountry => 'เลือกประเทศ'; + + @override + String get warnCountryNotAvailable => 'ขออภัย Deriv ไม่พร้อมให้บริการในประเทศของคุณ'; + + @override + String get actionNext => 'ถัดไป'; + + @override + String get labelEmailIssueHeader => 'หากคุณไม่ได้รับอีเมล์จากเราภายในไม่กี่นาที อาจมีบางสิ่งเกิดขึ้น:'; + + @override + String get labelEmailIssueSpam => 'อีเมล์นั้นอยู่ในโฟลเดอร์จดหมายขยะของคุณ (บางทีก็มีการพลัดหลงไปที่นั่นได้)'; + + @override + String get labelEmailIssueWrongEmail => 'คุณเผอิญให้ที่อยู่อีเมล์อื่นแก่เรา (ปกติมักจะเป็นอีเมล์จากที่ทำงานหรืออีเมล์ส่วนตัวแทนที่จะเป็นอันที่คุณต้องการใช้)'; + + @override + String get labelEmailIssueTypo => 'ที่อยู่อีเมล์ที่คุณป้อนมีข้อผิดพลาดหรือคำพิมพ์ผิด (เกิดขึ้นได้กับทุกคน)'; + + @override + String get labelEmailIssueFirewall => 'เราไม่สามารถส่งอีเมล์ไปยังที่อยู่นี้ (โดยปกติจะเป็นเพราะไฟร์วอลล์หรือการกรอง)'; + + @override + String get actionReenterEmail => 'ป้อนที่อยู่อีเมล์ของคุณแล้วลองอีกครั้ง'; + + @override + String get labelKeepPassword => 'รักษาบัญชีของคุณให้ปลอดภัยด้วยรหัสผ่าน'; + + @override + String get labelCreatePassword => 'สร้างรหัสผ่าน'; + + @override + String get actionStartTrading => 'เริ่มการเทรด'; + + @override + String get actionPrevious => 'ก่อนหน้า'; + + @override + String get labelSignUp => 'ลงทะเบียนสมัครใช้งาน'; + + @override + String get labelOrSignUpWith => 'หรือลงทะเบียนสมัครไปกับ'; + + @override + String get labelReferralInfoTitle => 'รหัสอ้างอิงพันธมิตร'; + + @override + String get infoReferralInfoDescription => 'รหัสตัวอักษรและตัวเลขที่จัดทำโดยพันธมิตรของ Deriv ซึ่งใช้ได้สำหรับการลงทะเบียนสมัครทางอีเมล์เท่านั้น'; + + @override + String get labelGotReferralCode => 'มีรหัสอ้างอิงหรือไม่?'; + + @override + String get labelHaveAccount => 'มีบัญชีอยู่แล้วใช่ไหม?'; + + @override + String get actionCreateAccount => 'สร้างบัญชีทดลองฟรี'; + + @override + String get informInvalidReferralCode => 'รหัสอ้างอิงที่คุณป้อนไม่ถูกต้อง โปรดตรวจสอบและลองอีกครั้ง'; + + @override + String get labelVerifyYourEmail => 'ยืนยันอีเมล์ของคุณ'; + + @override + String get labelThanksEmail => 'ขอบคุณที่ยืนยันอีเมล์ของคุณ'; + + @override + String get informLetsContinue => 'ดำเนินการต่อกันเถอะ'; + + @override + String get actionContinue => 'ดำเนินการต่อ'; + + @override + String get labelSearchCountry => 'ค้นหาประเทศ'; + + @override + String informVerificationEmailSent(String email) { + return 'เราได้ส่งข้อความไปที่ $email พร้อมลิงก์เพื่อเปิดใช้งานบัญชีของคุณ'; + } + + @override + String get actionEmailNotReceived => 'ไม่ได้รับอีเมล์ของคุณใช่หรือไม่?'; + + @override + String get informPasswordPolicy => 'รหัสผ่านของคุณต้องมี:'; + + @override + String get informPasswordPolicyLength => '8-25 อักขระ'; + + @override + String get informPasswordPolicyLowerAndUpper => 'ตัวอักษรตัวพิมพ์ใหญ่และตัวพิมพ์เล็ก'; + + @override + String get informPasswordPolicyNumber => 'อย่างน้อยหนึ่งหมายเลข'; + + @override + String get warnPasswordContainsSymbol => 'ใช้สัญลักษณ์เพื่อให้รหัสผ่านเดาได้ยาก'; + + @override + String get labelReferralCode => 'รหัสอ้างอิง'; + + @override + String get actionTryAgain => 'ลองอีกครั้ง'; + + @override + String get informInvalid2FACode => 'รหัสที่คุณป้อนไม่ถูกต้อง โปรดตรวจสอบแล้วลองอีกครั้ง'; + + @override + String get informFailedAuthentication => 'อีเมล์หรือรหัสผ่านของคุณอาจไม่ถูกต้อง คุณได้ลงทะเบียนด้วยบัญชีโซเชียลหรือไม่? โปรดตรวจสอบแล้วลองอีกครั้ง'; + + @override + String get informDeactivatedAccount => 'บัญชีของคุณถูกปิดใช้งาน'; + + @override + String get informFailedAuthorization => 'การอนุญาตให้สิทธิ์ล้มเหลว'; + + @override + String get informInvalidResidence => 'ที่พักอาศัยไม่ถูกต้อง'; + + @override + String get informInvalidCredentials => 'ข้อมูลประจำตัวไม่ถูกต้อง'; + + @override + String get informMissingOtp => 'รหัสผ่านแบบครั้งเดียวขาดหายไป'; + + @override + String get informSelfClosed => 'บัญชีของคุณได้ถูกปิดแล้ว'; + + @override + String get informUnexpectedError => 'มีข้อผิดพลาดเกิดขึ้น'; + + @override + String get informUnsupportedCountry => 'ไม่รองรับการใช้งานในประเทศของคุณ'; + + @override + String get informExpiredAccount => 'บัญชีของคุณหมดอายุ'; + + @override + String get labelCountryConsentBrazil => 'ข้าพเจ้าขอยืนยันว่าคำขอเปิดบัญชีกับ Deriv เพื่อทำการซื้อขายผลิตภัณฑ์ OTC ที่ออกและนำเสนอเฉพาะนอกประเทศบราซิลนั้นมาจากความคิดริเริ่มโดยข้าพเจ้าเอง ข้าพเจ้าเข้าใจดีว่า Deriv ไม่ได้ถูกควบคุมดูแลโดย CVM และในการที่ข้าพเจ้าเข้าหา Deriv นั้นข้าพเจ้ามีเจตนาที่จะสร้างความสัมพันธ์กับบริษัทจากต่างประเทศ'; + + @override + String get informConnectionError => 'มีข้อผิดพลาดในการเชื่อมต่อ โปรดลองใหม่ในภายหลัง'; + + @override + String get informSwitchAccountError => 'มีข้อผิดพลาดในการสลับบัญชี โปรดลองใหม่ในภายหลัง'; + + @override + String get labelDeveloper => 'ผู้พัฒนาระบบ'; + + @override + String get labelEndpoint => 'อุปกรณ์ปลายทาง'; + + @override + String get semanticEndpoint => 'อุปกรณ์ปลายทาง'; + + @override + String get warnInvalidEndpoint => 'อุปกรณ์ปลายทางไม่ถูกต้อง'; + + @override + String get labelApplicationID => 'รหัสไอดีแอปพลิเคชั่น'; + + @override + String get semanticApplicationID => 'รหัสไอดีแอปพลิเคชั่น'; + + @override + String get warnInvalidApplicationID => 'รหัสไอดีแอปพลิเคชั่นไม่ถูกต้อง'; + + @override + String get labelLanguage => 'ภาษา'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_tr.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_tr.dart new file mode 100644 index 000000000..a43f8a4b0 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_tr.dart @@ -0,0 +1,276 @@ +import 'deriv_auth_localizations.dart'; + +/// The translations for Turkish (`tr`). +class DerivAuthLocalizationsTr extends DerivAuthLocalizations { + DerivAuthLocalizationsTr([String locale = 'tr']) : super(locale); + + @override + String get labelNotAvailable => 'Mevcut değil'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return '$app ülkenizde mevcut değil'; + } + + @override + String get actionOk => 'OK'; + + @override + String get warnNotAvailableCountries => 'Herhangi bir sorunuz varsa, bizimle şu yolla iletişime geçin '; + + @override + String get labelLiveChat => 'Canlı sohbet'; + + @override + String get actionSignUpForFree => 'Ücretsiz Üye Olun'; + + @override + String get actionLogin => 'Giriş yap'; + + @override + String get labelTwoFactorAuth => 'İki faktörlü kimlik doğrulama'; + + @override + String get informEnterTwoFactorAuthCode => 'Telefonunuzdaki kimlik doğrulayıcı uygulamasından 6 haneli kodu girin.'; + + @override + String get labelTwoFactorAuthenticationCode => '2FA kodu'; + + @override + String get actionProceed => 'Devam et'; + + @override + String get labelLogIn => 'Oturum Aç'; + + @override + String get informLoginOptions => 'Veya bununla oturum aç'; + + @override + String get labelEmail => 'E-mail'; + + @override + String get labelPassword => 'Şifre'; + + @override + String get actionForgotPassword => 'Şifreyi mi unuttun?'; + + @override + String get labelDontHaveAnAccountYet => 'Henüz bir hesabınız yok mu?'; + + @override + String get actionCreateANewAccount => 'Yeni bir hesap oluştur'; + + @override + String get informInvalidEmailFormat => 'Geçerli bir e-posta adresi girin'; + + @override + String get warnPasswordLength => '6-25 arası karakter girmelisiniz.'; + + @override + String get labelResetPassword => 'Şifreyi sıfırla'; + + @override + String get labelChooseNewPass => 'Yeni bir şifre seç'; + + @override + String get labelCreatePass => 'Şifre'; + + @override + String get informYourPassHasBeenReset => 'Şifreniz sıfırlandı'; + + @override + String get informRedirectLogin => 'Yeni şifrenizle giriş yapmanız gerekecek. Bekle, seni yönlendiriyoruz.'; + + @override + String get actionResetPass => 'Şifremi sıfırla'; + + @override + String get informInvalidPasswordFormat => 'Lütfen geçerli bir şifre formatı girin'; + + @override + String get labelCheckEmail => 'E-postanızı kontrol edin'; + + @override + String informSendResetPasswordEmail(String email) { + return '$email adresine şifrenizi sıfırlamanız için bir bağlantı içeren bir mesaj gönderdik.'; + } + + @override + String get informResetPassByEmail => 'Şifrenizi sıfırlamak için size e-posta ile talimatlar göndereceğiz.'; + + @override + String get labelSelectCountry => 'Nerede yaşıyorsunuz?'; + + @override + String get labelChooseCountry => 'Ülke seçin'; + + @override + String get warnCountryNotAvailable => 'Maalesef, Deriv ülkenizde mevcut değil.'; + + @override + String get actionNext => 'Sonraki'; + + @override + String get labelEmailIssueHeader => 'Birkaç dakika içinde bizden bir e-posta görmezseniz, birkaç şey olmuş olabilir:'; + + @override + String get labelEmailIssueSpam => 'E-posta, spam klasörünüzdedir (bazen orada bir şeyler kaybolur).'; + + @override + String get labelEmailIssueWrongEmail => 'Yanlışlıkla bize başka bir e-posta adresi verdiniz (Genellikle kastettiğiniz e-posta yerine bir iş veya kişisel bir e-posta).'; + + @override + String get labelEmailIssueTypo => 'Girdiğiniz e-posta adresi bir hata veya yazım hatası içerdi (en iyilerimizin bile başına geliyor).'; + + @override + String get labelEmailIssueFirewall => 'E-postayı bu adrese teslim edemiyoruz (Genellikle güvenlik duvarları veya filtreleme nedeniyle).'; + + @override + String get actionReenterEmail => 'E-postanızı yine girin ve deneyin'; + + @override + String get labelKeepPassword => 'Hesabınızı bir şifre ile güvende tutun'; + + @override + String get labelCreatePassword => 'Bir şifre oluşturun'; + + @override + String get actionStartTrading => 'Ticarete başlayın'; + + @override + String get actionPrevious => 'Önceki'; + + @override + String get labelSignUp => 'Kayıt ol'; + + @override + String get labelOrSignUpWith => 'Veya bununla kaydolun'; + + @override + String get labelReferralInfoTitle => 'Affiliate yönlendirme kodu'; + + @override + String get infoReferralInfoDescription => 'Bir Deriv affiliate tarafından sağlanan ve yalnızca e-posta kayıtları için geçerli olan alfanümerik bir kod.'; + + @override + String get labelGotReferralCode => 'Yönlendirme kodunuz var mı?'; + + @override + String get labelHaveAccount => 'Zaten bir hesabınız var?'; + + @override + String get actionCreateAccount => 'Ücretsiz demo hesabı oluştur'; + + @override + String get informInvalidReferralCode => 'Girdiğiniz yönlendirme kodu geçersiz. Kontrol edin ve tekrar deneyin.'; + + @override + String get labelVerifyYourEmail => 'E-postanızı doğrulayın'; + + @override + String get labelThanksEmail => 'E-posta doğrulama için teşekkürler'; + + @override + String get informLetsContinue => 'Devam edelim.'; + + @override + String get actionContinue => 'Devam et'; + + @override + String get labelSearchCountry => 'Ülke ara'; + + @override + String informVerificationEmailSent(String email) { + return 'Hesabınızı etkinleştirmek için $email adresine bir bağlantı içeren bir mesaj gönderdik.'; + } + + @override + String get actionEmailNotReceived => 'E-postanızı almadınız mı?'; + + @override + String get informPasswordPolicy => 'Şifreniz şunlara sahip olmalıdır:'; + + @override + String get informPasswordPolicyLength => '8-25 karakter'; + + @override + String get informPasswordPolicyLowerAndUpper => 'Büyük ve küçük harfler'; + + @override + String get informPasswordPolicyNumber => 'En az bir sayı'; + + @override + String get warnPasswordContainsSymbol => 'Güçlü şifre için semboller kullanın.'; + + @override + String get labelReferralCode => 'Yönlendirme Kodu'; + + @override + String get actionTryAgain => 'Tekrar Deneyin'; + + @override + String get informInvalid2FACode => 'Girdiğiniz kod geçersiz. Kontrol edin ve tekrar deneyin.'; + + @override + String get informFailedAuthentication => 'E-posta adresiniz veya şifreniz yanlış olabilir. Sosyal bir hesapla mı kaydoldunuz? Kontrol edin ve tekrar deneyin.'; + + @override + String get informDeactivatedAccount => 'Hesabınız devre dışı bırakıldı.'; + + @override + String get informFailedAuthorization => 'Yetkilendirme başarısız oldu.'; + + @override + String get informInvalidResidence => 'Geçersiz ikamet.'; + + @override + String get informInvalidCredentials => 'Geçersiz kimlik bilgileri.'; + + @override + String get informMissingOtp => 'Tek seferlik şifre eksik.'; + + @override + String get informSelfClosed => 'Hesabınız kapatıldı.'; + + @override + String get informUnexpectedError => 'Beklenmedik bir hata oluştu.'; + + @override + String get informUnsupportedCountry => 'Ülkeniz desteklenmiyor.'; + + @override + String get informExpiredAccount => 'Hesabınızın süresi doldu'; + + @override + String get labelCountryConsentBrazil => 'Sadece Brezilya dışında yayınlanan ve sunulan OTC ürünlerinin ticaretini yapmak için Deriv\'de bir hesap açma talebimin tarafımdan başlatıldığını onaylıyorum. Deriv\'in CVM tarafından düzenlenmediğini ve Deriv\'e başvurarak yabancı bir şirketle ilişki kurmayı amaçladığımı tamamen anlıyorum.'; + + @override + String get informConnectionError => 'Bağlantı hatası. Lütfen daha sonra tekrar deneyin.'; + + @override + String get informSwitchAccountError => 'Hesap hatası değiştir. Lütfen daha sonra tekrar deneyin.'; + + @override + String get labelDeveloper => 'Geliştirici'; + + @override + String get labelEndpoint => 'Bitiş noktası'; + + @override + String get semanticEndpoint => 'Bitiş noktası'; + + @override + String get warnInvalidEndpoint => 'geçersiz bitiş noktası'; + + @override + String get labelApplicationID => 'Başvuru ID'; + + @override + String get semanticApplicationID => 'Başvuru ID'; + + @override + String get warnInvalidApplicationID => 'geçersiz başvuru ID'; + + @override + String get labelLanguage => 'Dil'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_uz.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_uz.dart new file mode 100644 index 000000000..e32f34c0d --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_uz.dart @@ -0,0 +1,276 @@ +import 'deriv_auth_localizations.dart'; + +/// The translations for Uzbek (`uz`). +class DerivAuthLocalizationsUz extends DerivAuthLocalizations { + DerivAuthLocalizationsUz([String locale = 'uz']) : super(locale); + + @override + String get labelNotAvailable => 'Not available'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return '$app isn\'t available in your country'; + } + + @override + String get actionOk => 'OK'; + + @override + String get warnNotAvailableCountries => 'If you have any questions, contact us via '; + + @override + String get labelLiveChat => 'Live chat'; + + @override + String get actionSignUpForFree => 'Sign up for free'; + + @override + String get actionLogin => 'Log in'; + + @override + String get labelTwoFactorAuth => 'Two-factor authentication'; + + @override + String get informEnterTwoFactorAuthCode => 'Enter the 6-digit code from the authenticator app on your phone.'; + + @override + String get labelTwoFactorAuthenticationCode => '2FA code'; + + @override + String get actionProceed => 'Proceed'; + + @override + String get labelLogIn => 'Log in'; + + @override + String get informLoginOptions => 'Or log in with'; + + @override + String get labelEmail => 'Email'; + + @override + String get labelPassword => 'Password'; + + @override + String get actionForgotPassword => 'Forgot password?'; + + @override + String get labelDontHaveAnAccountYet => 'Don’t have an account yet?'; + + @override + String get actionCreateANewAccount => 'Create a new account'; + + @override + String get informInvalidEmailFormat => 'Enter a valid email address'; + + @override + String get warnPasswordLength => 'You should enter 6-25 characters.'; + + @override + String get labelResetPassword => 'Reset password'; + + @override + String get labelChooseNewPass => 'Choose a new password'; + + @override + String get labelCreatePass => 'Password'; + + @override + String get informYourPassHasBeenReset => 'Your password has been reset'; + + @override + String get informRedirectLogin => 'You’ll need to log in with your new password. Hang on, we’re redirecting you.'; + + @override + String get actionResetPass => 'Reset my password'; + + @override + String get informInvalidPasswordFormat => 'Please enter a valid password format'; + + @override + String get labelCheckEmail => 'Check your email'; + + @override + String informSendResetPasswordEmail(String email) { + return 'We’ve sent a message to $email with a link to reset your password.'; + } + + @override + String get informResetPassByEmail => 'We\'ll email you instructions to reset your password.'; + + @override + String get labelSelectCountry => 'Where do you live?'; + + @override + String get labelChooseCountry => 'Choose country'; + + @override + String get warnCountryNotAvailable => 'Unfortunately, Deriv is not available in your country.'; + + @override + String get actionNext => 'Next'; + + @override + String get labelEmailIssueHeader => 'If you don\'t see an email from us within a few minutes, a few things could have happened:'; + + @override + String get labelEmailIssueSpam => 'The email is in your spam folder (Sometimes things get lost there).'; + + @override + String get labelEmailIssueWrongEmail => 'You accidentally gave us another email address (Usually a work or a personal one instead of the one you meant).'; + + @override + String get labelEmailIssueTypo => 'The email address you entered had a mistake or typo (happens to the best of us).'; + + @override + String get labelEmailIssueFirewall => 'We can\'t deliver the email to this address (Usually because of firewalls or filtering).'; + + @override + String get actionReenterEmail => 'Re-enter your email and try again'; + + @override + String get labelKeepPassword => 'Keep your account secure with a password'; + + @override + String get labelCreatePassword => 'Create a password'; + + @override + String get actionStartTrading => 'Start trading'; + + @override + String get actionPrevious => 'Previous'; + + @override + String get labelSignUp => 'Sign up'; + + @override + String get labelOrSignUpWith => 'Or sign up with'; + + @override + String get labelReferralInfoTitle => 'Affiliate referral code'; + + @override + String get infoReferralInfoDescription => 'An alphanumeric code provided by a Deriv affiliate, applicable for email sign-ups only.'; + + @override + String get labelGotReferralCode => 'Got a referral code?'; + + @override + String get labelHaveAccount => 'Already have an account?'; + + @override + String get actionCreateAccount => 'Create free demo account'; + + @override + String get informInvalidReferralCode => 'The referral code you entered is invalid. Check and try again.'; + + @override + String get labelVerifyYourEmail => 'Verify your email'; + + @override + String get labelThanksEmail => 'Thanks for verifying your email'; + + @override + String get informLetsContinue => 'Let\'s continue.'; + + @override + String get actionContinue => 'Continue'; + + @override + String get labelSearchCountry => 'Search country'; + + @override + String informVerificationEmailSent(String email) { + return 'We\'ve sent a message to $email with a link to activate your account.'; + } + + @override + String get actionEmailNotReceived => 'Didn\'t receive your email?'; + + @override + String get informPasswordPolicy => 'Your password must have:'; + + @override + String get informPasswordPolicyLength => '8-25 characters'; + + @override + String get informPasswordPolicyLowerAndUpper => 'Upper and lower case letters'; + + @override + String get informPasswordPolicyNumber => 'At least one number'; + + @override + String get warnPasswordContainsSymbol => 'Use symbols for strong password.'; + + @override + String get labelReferralCode => 'Referral Code'; + + @override + String get actionTryAgain => 'Try Again'; + + @override + String get informInvalid2FACode => 'The code you entered is invalid. Check and try again.'; + + @override + String get informFailedAuthentication => 'Your email or password may be incorrect. Did you sign up with a social account? Check and try again.'; + + @override + String get informDeactivatedAccount => 'Your account is deactivated.'; + + @override + String get informFailedAuthorization => 'Authorization failed.'; + + @override + String get informInvalidResidence => 'Invalid residence.'; + + @override + String get informInvalidCredentials => 'Invalid credentials.'; + + @override + String get informMissingOtp => 'Missing one-time password.'; + + @override + String get informSelfClosed => 'Your account has been closed.'; + + @override + String get informUnexpectedError => 'An unexpected error occurred.'; + + @override + String get informUnsupportedCountry => 'Your country is not supported.'; + + @override + String get informExpiredAccount => 'Your account is expired'; + + @override + String get labelCountryConsentBrazil => 'I hereby confirm that my request for opening an account with Deriv to trade OTC products issued and offered exclusively outside Brazil was initiated by me. I fully understand that Deriv is not regulated by CVM and by approaching Deriv I intend to set up a relation with a foreign company.'; + + @override + String get informConnectionError => 'Connection error. Please try again later.'; + + @override + String get informSwitchAccountError => 'Switch account error. Please try again later.'; + + @override + String get labelDeveloper => 'Developer'; + + @override + String get labelEndpoint => 'Endpoint'; + + @override + String get semanticEndpoint => 'Endpoint'; + + @override + String get warnInvalidEndpoint => 'invalid endpoint'; + + @override + String get labelApplicationID => 'Application ID'; + + @override + String get semanticApplicationID => 'Application ID'; + + @override + String get warnInvalidApplicationID => 'invalid application ID'; + + @override + String get labelLanguage => 'Language'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_vi.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_vi.dart new file mode 100644 index 000000000..61bd9e96a --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_vi.dart @@ -0,0 +1,276 @@ +import 'deriv_auth_localizations.dart'; + +/// The translations for Vietnamese (`vi`). +class DerivAuthLocalizationsVi extends DerivAuthLocalizations { + DerivAuthLocalizationsVi([String locale = 'vi']) : super(locale); + + @override + String get labelNotAvailable => 'Không tồn tại'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return '$app không có sẵn ở quốc gia của bạn'; + } + + @override + String get actionOk => 'Ok'; + + @override + String get warnNotAvailableCountries => 'Nếu bạn có bất kỳ câu hỏi nào, hãy liên hệ với chúng tôi qua '; + + @override + String get labelLiveChat => 'Live chat'; + + @override + String get actionSignUpForFree => 'Đăng ký miễn phí'; + + @override + String get actionLogin => 'Đăng nhập'; + + @override + String get labelTwoFactorAuth => 'Xác thực hai yếu tố'; + + @override + String get informEnterTwoFactorAuthCode => 'Nhập mã gồm 6 chữ số từ ứng dụng xác thực trên điện thoại của bạn.'; + + @override + String get labelTwoFactorAuthenticationCode => 'mã 2FA'; + + @override + String get actionProceed => 'Xử lý'; + + @override + String get labelLogIn => 'Đăng nhập'; + + @override + String get informLoginOptions => 'Hoặc đăng nhập với'; + + @override + String get labelEmail => 'Email'; + + @override + String get labelPassword => 'Mật khẩu'; + + @override + String get actionForgotPassword => 'Quên mật khẩu?'; + + @override + String get labelDontHaveAnAccountYet => 'Chưa có tài khoản?'; + + @override + String get actionCreateANewAccount => 'Tạo một tài khoản mới'; + + @override + String get informInvalidEmailFormat => 'Nhập địa chỉ email hợp lệ'; + + @override + String get warnPasswordLength => 'Bạn nên nhập từ 8-25 ký tự.'; + + @override + String get labelResetPassword => 'Đặt lại mật khẩu'; + + @override + String get labelChooseNewPass => 'Chọn mật khẩu mới'; + + @override + String get labelCreatePass => 'Mật khẩu'; + + @override + String get informYourPassHasBeenReset => 'Mật khẩu của bạn đã được đặt lại'; + + @override + String get informRedirectLogin => 'Bạn sẽ cần đăng nhập bằng mật khẩu mới của mình. Chờ đã, chúng tôi đang chuyển hướng bạn.'; + + @override + String get actionResetPass => 'Đặt lại mật khẩu'; + + @override + String get informInvalidPasswordFormat => 'Vui lòng nhập một định dạng mật khẩu hợp lệ'; + + @override + String get labelCheckEmail => 'Hãy kiểm tra email của bạn'; + + @override + String informSendResetPasswordEmail(String email) { + return 'Chúng tôi đã gửi tin nhắn $email với một liên kết để đặt lại mật khẩu của bạn.'; + } + + @override + String get informResetPassByEmail => 'Chúng tôi sẽ gửi email hướng dẫn đổi mật khẩu cho bạn.'; + + @override + String get labelSelectCountry => 'Bạn sống ở đâu?'; + + @override + String get labelChooseCountry => 'Chọn quốc gia'; + + @override + String get warnCountryNotAvailable => 'Rất tiếc, Deriv không khả dụng ở quốc gia của bạn.'; + + @override + String get actionNext => 'Tiếp theo'; + + @override + String get labelEmailIssueHeader => 'Nếu sau vài phút bạn không nhận được email từ chúng tôi, một vài điều sau có thể đã xảy ra:'; + + @override + String get labelEmailIssueSpam => 'Email có thể đã bị chuyển vào hòm thư spam (Đôi lúc dữ liệu bị lạc mất trong đó).'; + + @override + String get labelEmailIssueWrongEmail => 'Bạn đã vô tình đưa cho chúng tôi một tài khoản email khác (Thường là một tài khoản cho công việc hoặc cá nhân thay vì tài khoản mà bạn định sử dụng).'; + + @override + String get labelEmailIssueTypo => 'Địa chỉ email bạn nhập bị nhầm hoặc có lỗi chính tả (hay xảy ra với chúng tôi).'; + + @override + String get labelEmailIssueFirewall => 'Chúng tôi không thể chuyển email đến địa chỉ này (Thường là do tường lửa hoặc bộ lọc).'; + + @override + String get actionReenterEmail => 'Nhập lại email của bạn và thử lại'; + + @override + String get labelKeepPassword => 'Bảo vệ tài khoản của bạn an toàn với mật khẩu'; + + @override + String get labelCreatePassword => 'Tạo một mật khẩu'; + + @override + String get actionStartTrading => 'Bắt đầu giao dịch'; + + @override + String get actionPrevious => 'Trước'; + + @override + String get labelSignUp => 'Đăng kí'; + + @override + String get labelOrSignUpWith => 'Hoặc đang ký với'; + + @override + String get labelReferralInfoTitle => 'Mã giới thiệu Affiliate'; + + @override + String get infoReferralInfoDescription => 'Mã chữ và số được cung cấp bởi một đơn vị liên kết Deriv, chỉ áp dụng cho đăng ký qua email.'; + + @override + String get labelGotReferralCode => 'Có mã giới thiệu?'; + + @override + String get labelHaveAccount => 'Đã có tài khoản?'; + + @override + String get actionCreateAccount => 'Tạo tài khoản demo miễn phí'; + + @override + String get informInvalidReferralCode => 'Mã giới thiệu bạn đã nhập không hợp lệ. Kiểm tra và thử lại.'; + + @override + String get labelVerifyYourEmail => 'Xác minh email của bạn'; + + @override + String get labelThanksEmail => 'Cảm ơn đã xác thực email của bạn'; + + @override + String get informLetsContinue => 'Hãy tiếp tục.'; + + @override + String get actionContinue => 'Tiếp tục'; + + @override + String get labelSearchCountry => 'Tìm kiếm quốc gia'; + + @override + String informVerificationEmailSent(String email) { + return 'Chúng tôi đã gửi tin nhắn $email với một liên kết để kích hoạt tài khoản của bạn.'; + } + + @override + String get actionEmailNotReceived => 'Không nhận được email bạn gửi?'; + + @override + String get informPasswordPolicy => 'Mật khẩu của bạn phải có:'; + + @override + String get informPasswordPolicyLength => '8-25 ký tự'; + + @override + String get informPasswordPolicyLowerAndUpper => 'Chữ hoa và chữ thường'; + + @override + String get informPasswordPolicyNumber => 'Ít nhất một số'; + + @override + String get warnPasswordContainsSymbol => 'Sử dụng các ký hiệu cho mật khẩu mạnh.'; + + @override + String get labelReferralCode => 'Mã giới thiệu'; + + @override + String get actionTryAgain => 'Thử lại'; + + @override + String get informInvalid2FACode => 'Mã bạn nhập không hợp lệ. Kiểm tra và thử lại.'; + + @override + String get informFailedAuthentication => 'Email hoặc mật khẩu của bạn có thể không chính xác. Bạn đã đăng ký với một tài khoản xã hội chưa? Kiểm tra và thử lại.'; + + @override + String get informDeactivatedAccount => 'Tài khoản của bạn bị vô hiệu hóa.'; + + @override + String get informFailedAuthorization => 'Ủy quyền không thành công.'; + + @override + String get informInvalidResidence => 'Nơi cư trú không hợp lệ.'; + + @override + String get informInvalidCredentials => 'Thông tin đăng nhập không hợp lệ.'; + + @override + String get informMissingOtp => 'Thiếu mật khẩu một lần.'; + + @override + String get informSelfClosed => 'Tài khoản này của bạn đã bị vô hiệu hóa.'; + + @override + String get informUnexpectedError => 'Đã có lỗi bất ngờ xảy ra.'; + + @override + String get informUnsupportedCountry => 'Đất nước của bạn không được hỗ trợ.'; + + @override + String get informExpiredAccount => 'Tài khoản của bạn đã hết hạn'; + + @override + String get labelCountryConsentBrazil => 'Tôi xác nhận rằng yêu cầu mở tài khoản của tôi với Deriv để giao dịch các sản phẩm OTC được phát hành và cung cấp độc quyền bên ngoài Brazil là do tôi thực hiện. Tôi hoàn toàn hiểu rằng Deriv không bị quản lý bởi CVM và thông qua Deriv, tôi dự định thiết lập quan hệ với một công ty nước ngoài.'; + + @override + String get informConnectionError => 'Lỗi kết nối. Vui lòng thử lại sau.'; + + @override + String get informSwitchAccountError => 'Chuyển đổi lỗi tài khoản. Vui lòng thử lại sau.'; + + @override + String get labelDeveloper => 'Nhà phát triển'; + + @override + String get labelEndpoint => 'Điểm kết thúc'; + + @override + String get semanticEndpoint => 'Điểm kết thúc'; + + @override + String get warnInvalidEndpoint => 'điểm kết thúc không hợp lệ'; + + @override + String get labelApplicationID => 'Mã đăng ký'; + + @override + String get semanticApplicationID => 'Mã đăng ký'; + + @override + String get warnInvalidApplicationID => 'mã đăng ký không hợp lệ'; + + @override + String get labelLanguage => 'Ngôn ngữ'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_zh.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_zh.dart new file mode 100644 index 000000000..d6cb0b7ac --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_auth/deriv_auth_localizations_zh.dart @@ -0,0 +1,826 @@ +import 'deriv_auth_localizations.dart'; + +/// The translations for Chinese (`zh`). +class DerivAuthLocalizationsZh extends DerivAuthLocalizations { + DerivAuthLocalizationsZh([String locale = 'zh']) : super(locale); + + @override + String get labelNotAvailable => '不可用'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return '您的國家/地區不能使用 $app'; + } + + @override + String get actionOk => '确定'; + + @override + String get warnNotAvailableCountries => '如果有任何疑問,請通過以下方式與我們聯繫 '; + + @override + String get labelLiveChat => '即時聊天'; + + @override + String get actionSignUpForFree => '免費註冊'; + + @override + String get actionLogin => '登入'; + + @override + String get labelTwoFactorAuth => '雙因素身份驗證'; + + @override + String get informEnterTwoFactorAuthCode => '在手機的驗證器應用程式中輸入 6 位數代碼。'; + + @override + String get labelTwoFactorAuthenticationCode => '2FA 代碼'; + + @override + String get actionProceed => '執行'; + + @override + String get labelLogIn => '登入'; + + @override + String get informLoginOptions => '或通過以下方式登入'; + + @override + String get labelEmail => '電子郵件'; + + @override + String get labelPassword => '密碼'; + + @override + String get actionForgotPassword => '忘記密碼?'; + + @override + String get labelDontHaveAnAccountYet => '還沒有帳戶?'; + + @override + String get actionCreateANewAccount => '開立新帳戶'; + + @override + String get informInvalidEmailFormat => '請輸入有效的電子郵件地址'; + + @override + String get warnPasswordLength => '必須輸入 6-25 個字元。'; + + @override + String get labelResetPassword => '重設密碼'; + + @override + String get labelChooseNewPass => '選新密碼'; + + @override + String get labelCreatePass => '密碼'; + + @override + String get informYourPassHasBeenReset => '密碼已重設'; + + @override + String get informRedirectLogin => '需要使用新密碼登入。等等,正在重導向。'; + + @override + String get actionResetPass => '重設密碼'; + + @override + String get informInvalidPasswordFormat => '請輸入有效的密碼格式'; + + @override + String get labelCheckEmail => '查看電子郵件'; + + @override + String informSendResetPasswordEmail(String email) { + return '已傳送郵件至$email ,內含重設密碼的連結。'; + } + + @override + String get informResetPassByEmail => '將以電子郵件傳送重設密碼的說明。'; + + @override + String get labelSelectCountry => '您的居住地是哪里?'; + + @override + String get labelChooseCountry => '選擇國家'; + + @override + String get warnCountryNotAvailable => '對不起,您的所在國不可用 Deriv。'; + + @override + String get actionNext => '下一頁'; + + @override + String get labelEmailIssueHeader => '如果數分鐘內沒有收到我們傳送的電子郵件,可能是因為發生了幾種情況而引起的:'; + + @override + String get labelEmailIssueSpam => '郵件在您的垃圾郵箱(有時一些郵件會誤送到那兒)。'; + + @override + String get labelEmailIssueWrongEmail => '您不小心給了我們另一個電子郵件地址(通常非您本意,而是屬於工作或個人性質的)。'; + + @override + String get labelEmailIssueTypo => '您輸入的電子郵件地址拼寫有誤(有時這是難免的)。'; + + @override + String get labelEmailIssueFirewall => '電子郵件無法傳送到此地址(通常是因為安裝了防火牆或篩選器)。'; + + @override + String get actionReenterEmail => '重新輸入電子郵件並重試'; + + @override + String get labelKeepPassword => '用密碼保障帳戶安全'; + + @override + String get labelCreatePassword => '建立密碼'; + + @override + String get actionStartTrading => '開始交易'; + + @override + String get actionPrevious => '上一頁'; + + @override + String get labelSignUp => '註冊'; + + @override + String get labelOrSignUpWith => '或使用以下方式註冊'; + + @override + String get labelReferralInfoTitle => '聯盟推薦代碼'; + + @override + String get infoReferralInfoDescription => '由 Deriv 聯盟會員提供的字母數字代碼,僅適用於電子郵件註冊。'; + + @override + String get labelGotReferralCode => '有推薦代碼嗎?'; + + @override + String get labelHaveAccount => '已經有帳戶?'; + + @override + String get actionCreateAccount => '開立免費示範帳戶'; + + @override + String get informInvalidReferralCode => '輸入的推薦代碼無效。檢查並再試一次。'; + + @override + String get labelVerifyYourEmail => '電子郵件驗證'; + + @override + String get labelThanksEmail => '謝謝您驗證了電子郵件'; + + @override + String get informLetsContinue => '讓我們繼續。'; + + @override + String get actionContinue => '繼續'; + + @override + String get labelSearchCountry => '搜尋國家'; + + @override + String informVerificationEmailSent(String email) { + return '已傳送郵件至$email ,內含激活帳戶的連結。'; + } + + @override + String get actionEmailNotReceived => '沒收到郵件?'; + + @override + String get informPasswordPolicy => '密碼必須具有:'; + + @override + String get informPasswordPolicyLength => '8-25 個字元'; + + @override + String get informPasswordPolicyLowerAndUpper => '大小寫英文字母'; + + @override + String get informPasswordPolicyNumber => '至少一個數字'; + + @override + String get warnPasswordContainsSymbol => '使用符號加強密碼。'; + + @override + String get labelReferralCode => '推薦代碼'; + + @override + String get actionTryAgain => '重試'; + + @override + String get informInvalid2FACode => '輸入的推薦代碼無效。檢查並再試一次。'; + + @override + String get informFailedAuthentication => '電子郵件地址或密碼不正確。是否用社交帳戶註冊?檢查並再試一次。'; + + @override + String get informDeactivatedAccount => '帳戶已停用。'; + + @override + String get informFailedAuthorization => '授權失敗。'; + + @override + String get informInvalidResidence => '無效的居住地。'; + + @override + String get informInvalidCredentials => '無效的憑證.'; + + @override + String get informMissingOtp => '缺少一次性密碼。'; + + @override + String get informSelfClosed => '帳戶已關閉.'; + + @override + String get informUnexpectedError => '發生不可預測錯誤.'; + + @override + String get informUnsupportedCountry => '您的國家不受支援。'; + + @override + String get informExpiredAccount => '帳戶已過期'; + + @override + String get labelCountryConsentBrazil => '本人特此確認,在 Deriv 開立帳戶以交易由巴西境外推出和提供的場外交易產品的要求是由本人提出的。本人完全理解 Deriv 不受 CVM 監管,且通過與 Deriv 接觸,本人打算與一家外國公司建立關係。'; + + @override + String get informConnectionError => '連線錯誤。請稍後再試一次。'; + + @override + String get informSwitchAccountError => '切換帳戶錯誤。請稍後再試一次。'; + + @override + String get labelDeveloper => '開發員'; + + @override + String get labelEndpoint => '端點'; + + @override + String get semanticEndpoint => '端點'; + + @override + String get warnInvalidEndpoint => '無效的端點'; + + @override + String get labelApplicationID => '應用程式 ID'; + + @override + String get semanticApplicationID => '應用程式 ID'; + + @override + String get warnInvalidApplicationID => '無效的應用程式 ID'; + + @override + String get labelLanguage => '語言'; +} + +/// The translations for Chinese, as used in China (`zh_CN`). +class DerivAuthLocalizationsZhCn extends DerivAuthLocalizationsZh { + DerivAuthLocalizationsZhCn(): super('zh_CN'); + + @override + String get labelNotAvailable => '不可用'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return '您的国家不能使用 $app'; + } + + @override + String get actionOk => '确定'; + + @override + String get warnNotAvailableCountries => '如有任何疑问,请通过以下方式联系我们 '; + + @override + String get labelLiveChat => '实时聊天'; + + @override + String get actionSignUpForFree => '免费注册'; + + @override + String get actionLogin => '登录'; + + @override + String get labelTwoFactorAuth => '双因素身份验证'; + + @override + String get informEnterTwoFactorAuthCode => '在手机输入身份验证器应用程序的 6 位数代码。'; + + @override + String get labelTwoFactorAuthenticationCode => '2FA 代码'; + + @override + String get actionProceed => '执行'; + + @override + String get labelLogIn => '登录'; + + @override + String get informLoginOptions => '或通过以下登录'; + + @override + String get labelEmail => '电子邮件'; + + @override + String get labelPassword => '密码'; + + @override + String get actionForgotPassword => '忘记密码?'; + + @override + String get labelDontHaveAnAccountYet => '还没有账户?'; + + @override + String get actionCreateANewAccount => '开立新账户'; + + @override + String get informInvalidEmailFormat => '输入有效的电子邮件地址'; + + @override + String get warnPasswordLength => '必须输入 6-25 个字符。'; + + @override + String get labelResetPassword => '重置密码'; + + @override + String get labelChooseNewPass => '选新密码'; + + @override + String get labelCreatePass => '密码'; + + @override + String get informYourPassHasBeenReset => '密码已重置'; + + @override + String get informRedirectLogin => '需要使用新密码登录。请稍等,正在重定向。'; + + @override + String get actionResetPass => '重置密码'; + + @override + String get informInvalidPasswordFormat => '请输入有效的密码格式'; + + @override + String get labelCheckEmail => '查看电子邮件'; + + @override + String informSendResetPasswordEmail(String email) { + return '已发送邮件至 $email,内含重置密码的链接。'; + } + + @override + String get informResetPassByEmail => '会传送内含重设密码的说明的电子邮件。'; + + @override + String get labelSelectCountry => '您的居住地是哪里?'; + + @override + String get labelChooseCountry => '选择国家'; + + @override + String get warnCountryNotAvailable => '对不起,您的所在国不可用 Deriv。'; + + @override + String get actionNext => '下一步'; + + @override + String get labelEmailIssueHeader => '如果数分钟内没有收到我们发送的电子邮件,可能是因为发生了几种情况而引起的:'; + + @override + String get labelEmailIssueSpam => '邮件在垃圾邮箱里(有时一些邮件会误送到那儿)。'; + + @override + String get labelEmailIssueWrongEmail => '您不小心给了我们另一个电子邮件地址(通常非您本意,而是属于工作或个人性质的)。'; + + @override + String get labelEmailIssueTypo => '您输入的电子邮件地址拼写有误(有时这是难免的)。'; + + @override + String get labelEmailIssueFirewall => '无法发送电子邮件到此地址(通常是因为安装了防火墙或筛选器)。'; + + @override + String get actionReenterEmail => '重新输入电子邮件并重试'; + + @override + String get labelKeepPassword => '用密码保障账户安全'; + + @override + String get labelCreatePassword => '创建密码'; + + @override + String get actionStartTrading => '开始交易'; + + @override + String get actionPrevious => '之前'; + + @override + String get labelSignUp => '注册'; + + @override + String get labelOrSignUpWith => '或通过以下注册'; + + @override + String get labelReferralInfoTitle => '联盟推荐码'; + + @override + String get infoReferralInfoDescription => 'Deriv 联盟会员提供的字母数字代码,仅用于电子邮件注册。'; + + @override + String get labelGotReferralCode => '有推荐码吗?'; + + @override + String get labelHaveAccount => '已经有账户?'; + + @override + String get actionCreateAccount => '开立免费演示账户'; + + @override + String get informInvalidReferralCode => '输入的推荐码无效。请检查并重试。'; + + @override + String get labelVerifyYourEmail => '电子邮件验证'; + + @override + String get labelThanksEmail => '谢谢您验证了电子邮件'; + + @override + String get informLetsContinue => '让我们继续。'; + + @override + String get actionContinue => '继续'; + + @override + String get labelSearchCountry => '搜索国家'; + + @override + String informVerificationEmailSent(String email) { + return '已发送邮件至 $email,内含激活账户的链接。'; + } + + @override + String get actionEmailNotReceived => '没收到邮件?'; + + @override + String get informPasswordPolicy => '密码必须有:'; + + @override + String get informPasswordPolicyLength => '8-25 个字符'; + + @override + String get informPasswordPolicyLowerAndUpper => '大写和小写字母'; + + @override + String get informPasswordPolicyNumber => '至少一个数字'; + + @override + String get warnPasswordContainsSymbol => '使用符号加强密码安全。'; + + @override + String get labelReferralCode => '推荐码'; + + @override + String get actionTryAgain => '重试'; + + @override + String get informInvalid2FACode => '输入的代码无效。请检查并重试。'; + + @override + String get informFailedAuthentication => '电子邮件或密码可能不正确。是否用社交账户注册?请检查并重试。'; + + @override + String get informDeactivatedAccount => '账户已停用。'; + + @override + String get informFailedAuthorization => '授权失败。'; + + @override + String get informInvalidResidence => '无效的居住地。'; + + @override + String get informInvalidCredentials => '凭证无效。'; + + @override + String get informMissingOtp => '缺少一次性密码。'; + + @override + String get informSelfClosed => '账户已关闭.'; + + @override + String get informUnexpectedError => '发生不可预测的错误.'; + + @override + String get informUnsupportedCountry => '您的国家不受支持。'; + + @override + String get informExpiredAccount => '账户已过期'; + + @override + String get labelCountryConsentBrazil => '本人特此确认,在 Deriv 开立账户以交易由巴西境外推出和提供的场外交易产品的请求是由本人提出的。本人完全理解 Deriv 不受 CVM 监管,且通过与 Deriv 接触,本人打算与一家外国公司建立关系。'; + + @override + String get informConnectionError => '连接错误。请稍后再试。'; + + @override + String get informSwitchAccountError => '切换账户错误。请稍后再试。'; + + @override + String get labelDeveloper => '开发员'; + + @override + String get labelEndpoint => '终结点'; + + @override + String get semanticEndpoint => '终结点'; + + @override + String get warnInvalidEndpoint => '无效的终结点'; + + @override + String get labelApplicationID => '应用程序 ID'; + + @override + String get semanticApplicationID => '应用程序 ID'; + + @override + String get warnInvalidApplicationID => '无效的应用程序 ID'; + + @override + String get labelLanguage => '语言'; +} + +/// The translations for Chinese, as used in Taiwan (`zh_TW`). +class DerivAuthLocalizationsZhTw extends DerivAuthLocalizationsZh { + DerivAuthLocalizationsZhTw(): super('zh_TW'); + + @override + String get labelNotAvailable => '不可用'; + + @override + String warnNotAvailableCountriesTitle(String app) { + return '您的國家/地區不能使用 $app'; + } + + @override + String get actionOk => '确定'; + + @override + String get warnNotAvailableCountries => '如果有任何疑問,請通過以下方式與我們聯繫 '; + + @override + String get labelLiveChat => '即時聊天'; + + @override + String get actionSignUpForFree => '免費註冊'; + + @override + String get actionLogin => '登入'; + + @override + String get labelTwoFactorAuth => '雙因素身份驗證'; + + @override + String get informEnterTwoFactorAuthCode => '在手機的驗證器應用程式中輸入 6 位數代碼。'; + + @override + String get labelTwoFactorAuthenticationCode => '2FA 代碼'; + + @override + String get actionProceed => '執行'; + + @override + String get labelLogIn => '登入'; + + @override + String get informLoginOptions => '或通過以下方式登入'; + + @override + String get labelEmail => '電子郵件'; + + @override + String get labelPassword => '密碼'; + + @override + String get actionForgotPassword => '忘記密碼?'; + + @override + String get labelDontHaveAnAccountYet => '還沒有帳戶?'; + + @override + String get actionCreateANewAccount => '開立新帳戶'; + + @override + String get informInvalidEmailFormat => '請輸入有效的電子郵件地址'; + + @override + String get warnPasswordLength => '必須輸入 6-25 個字元。'; + + @override + String get labelResetPassword => '重設密碼'; + + @override + String get labelChooseNewPass => '選新密碼'; + + @override + String get labelCreatePass => '密碼'; + + @override + String get informYourPassHasBeenReset => '密碼已重設'; + + @override + String get informRedirectLogin => '需要使用新密碼登入。等等,正在重導向。'; + + @override + String get actionResetPass => '重設密碼'; + + @override + String get informInvalidPasswordFormat => '請輸入有效的密碼格式'; + + @override + String get labelCheckEmail => '查看電子郵件'; + + @override + String informSendResetPasswordEmail(String email) { + return '已傳送郵件至$email ,內含重設密碼的連結。'; + } + + @override + String get informResetPassByEmail => '將以電子郵件傳送重設密碼的說明。'; + + @override + String get labelSelectCountry => '您的居住地是哪里?'; + + @override + String get labelChooseCountry => '選擇國家'; + + @override + String get warnCountryNotAvailable => '對不起,您的所在國不可用 Deriv。'; + + @override + String get actionNext => '下一頁'; + + @override + String get labelEmailIssueHeader => '如果數分鐘內沒有收到我們傳送的電子郵件,可能是因為發生了幾種情況而引起的:'; + + @override + String get labelEmailIssueSpam => '郵件在您的垃圾郵箱(有時一些郵件會誤送到那兒)。'; + + @override + String get labelEmailIssueWrongEmail => '您不小心給了我們另一個電子郵件地址(通常非您本意,而是屬於工作或個人性質的)。'; + + @override + String get labelEmailIssueTypo => '您輸入的電子郵件地址拼寫有誤(有時這是難免的)。'; + + @override + String get labelEmailIssueFirewall => '電子郵件無法傳送到此地址(通常是因為安裝了防火牆或篩選器)。'; + + @override + String get actionReenterEmail => '重新輸入電子郵件並重試'; + + @override + String get labelKeepPassword => '用密碼保障帳戶安全'; + + @override + String get labelCreatePassword => '建立密碼'; + + @override + String get actionStartTrading => '開始交易'; + + @override + String get actionPrevious => '上一頁'; + + @override + String get labelSignUp => '註冊'; + + @override + String get labelOrSignUpWith => '或使用以下方式註冊'; + + @override + String get labelReferralInfoTitle => '聯盟推薦代碼'; + + @override + String get infoReferralInfoDescription => '由 Deriv 聯盟會員提供的字母數字代碼,僅適用於電子郵件註冊。'; + + @override + String get labelGotReferralCode => '有推薦代碼嗎?'; + + @override + String get labelHaveAccount => '已經有帳戶?'; + + @override + String get actionCreateAccount => '開立免費示範帳戶'; + + @override + String get informInvalidReferralCode => '輸入的推薦代碼無效。檢查並再試一次。'; + + @override + String get labelVerifyYourEmail => '電子郵件驗證'; + + @override + String get labelThanksEmail => '謝謝您驗證了電子郵件'; + + @override + String get informLetsContinue => '讓我們繼續。'; + + @override + String get actionContinue => '繼續'; + + @override + String get labelSearchCountry => '搜尋國家'; + + @override + String informVerificationEmailSent(String email) { + return '已傳送郵件至$email ,內含激活帳戶的連結。'; + } + + @override + String get actionEmailNotReceived => '沒收到郵件?'; + + @override + String get informPasswordPolicy => '密碼必須具有:'; + + @override + String get informPasswordPolicyLength => '8-25 個字元'; + + @override + String get informPasswordPolicyLowerAndUpper => '大小寫英文字母'; + + @override + String get informPasswordPolicyNumber => '至少一個數字'; + + @override + String get warnPasswordContainsSymbol => '使用符號加強密碼。'; + + @override + String get labelReferralCode => '推薦代碼'; + + @override + String get actionTryAgain => '重試'; + + @override + String get informInvalid2FACode => '輸入的推薦代碼無效。檢查並再試一次。'; + + @override + String get informFailedAuthentication => '電子郵件地址或密碼不正確。是否用社交帳戶註冊?檢查並再試一次。'; + + @override + String get informDeactivatedAccount => '帳戶已停用。'; + + @override + String get informFailedAuthorization => '授權失敗。'; + + @override + String get informInvalidResidence => '無效的居住地。'; + + @override + String get informInvalidCredentials => '無效的憑證.'; + + @override + String get informMissingOtp => '缺少一次性密碼。'; + + @override + String get informSelfClosed => '帳戶已關閉.'; + + @override + String get informUnexpectedError => '發生不可預測錯誤.'; + + @override + String get informUnsupportedCountry => '您的國家不受支援。'; + + @override + String get informExpiredAccount => '帳戶已過期'; + + @override + String get labelCountryConsentBrazil => '本人特此確認,在 Deriv 開立帳戶以交易由巴西境外推出和提供的場外交易產品的要求是由本人提出的。本人完全理解 Deriv 不受 CVM 監管,且通過與 Deriv 接觸,本人打算與一家外國公司建立關係。'; + + @override + String get informConnectionError => '連線錯誤。請稍後再試一次。'; + + @override + String get informSwitchAccountError => '切換帳戶錯誤。請稍後再試一次。'; + + @override + String get labelDeveloper => '開發員'; + + @override + String get labelEndpoint => '端點'; + + @override + String get semanticEndpoint => '端點'; + + @override + String get warnInvalidEndpoint => '無效的端點'; + + @override + String get labelApplicationID => '應用程式 ID'; + + @override + String get semanticApplicationID => '應用程式 ID'; + + @override + String get warnInvalidApplicationID => '無效的應用程式 ID'; + + @override + String get labelLanguage => '語言'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations.dart new file mode 100644 index 000000000..24389524b --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations.dart @@ -0,0 +1,769 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'deriv_mobile_chart_wrapper_localizations_ar.dart'; +import 'deriv_mobile_chart_wrapper_localizations_bn.dart'; +import 'deriv_mobile_chart_wrapper_localizations_de.dart'; +import 'deriv_mobile_chart_wrapper_localizations_en.dart'; +import 'deriv_mobile_chart_wrapper_localizations_es.dart'; +import 'deriv_mobile_chart_wrapper_localizations_fr.dart'; +import 'deriv_mobile_chart_wrapper_localizations_it.dart'; +import 'deriv_mobile_chart_wrapper_localizations_km.dart'; +import 'deriv_mobile_chart_wrapper_localizations_ko.dart'; +import 'deriv_mobile_chart_wrapper_localizations_mn.dart'; +import 'deriv_mobile_chart_wrapper_localizations_pl.dart'; +import 'deriv_mobile_chart_wrapper_localizations_pt.dart'; +import 'deriv_mobile_chart_wrapper_localizations_ru.dart'; +import 'deriv_mobile_chart_wrapper_localizations_si.dart'; +import 'deriv_mobile_chart_wrapper_localizations_sw.dart'; +import 'deriv_mobile_chart_wrapper_localizations_th.dart'; +import 'deriv_mobile_chart_wrapper_localizations_tr.dart'; +import 'deriv_mobile_chart_wrapper_localizations_uz.dart'; +import 'deriv_mobile_chart_wrapper_localizations_vi.dart'; +import 'deriv_mobile_chart_wrapper_localizations_zh.dart'; + +/// Callers can lookup localized strings with an instance of DerivMobileChartWrapperLocalizations +/// returned by `DerivMobileChartWrapperLocalizations.of(context)`. +/// +/// Applications need to include `DerivMobileChartWrapperLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: DerivMobileChartWrapperLocalizations.localizationsDelegates, +/// supportedLocales: DerivMobileChartWrapperLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the DerivMobileChartWrapperLocalizations.supportedLocales +/// property. +abstract class DerivMobileChartWrapperLocalizations { + DerivMobileChartWrapperLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static DerivMobileChartWrapperLocalizations of(BuildContext context) { + return Localizations.of(context, DerivMobileChartWrapperLocalizations)!; + } + + static const LocalizationsDelegate delegate = _DerivMobileChartWrapperLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [ + Locale('ar'), + Locale('bn'), + Locale('de'), + Locale('en'), + Locale('es'), + Locale('fr'), + Locale('it'), + Locale('km'), + Locale('ko'), + Locale('mn'), + Locale('pl'), + Locale('pt'), + Locale('ru'), + Locale('si'), + Locale('sw'), + Locale('th'), + Locale('tr'), + Locale('uz'), + Locale('vi'), + Locale('zh', 'CN'), + Locale('zh', 'TW'), + Locale('zh') + ]; + + /// No description provided for @labelIndicators. + /// + /// In en, this message translates to: + /// **'Indicators'** + String get labelIndicators; + + /// No description provided for @labelActive. + /// + /// In en, this message translates to: + /// **'Active'** + String get labelActive; + + /// No description provided for @labelAll. + /// + /// In en, this message translates to: + /// **'All'** + String get labelAll; + + /// No description provided for @labelMomentum. + /// + /// In en, this message translates to: + /// **'Momentum'** + String get labelMomentum; + + /// No description provided for @labelVolatility. + /// + /// In en, this message translates to: + /// **'Volatility'** + String get labelVolatility; + + /// No description provided for @labelMovingAverages. + /// + /// In en, this message translates to: + /// **'Moving averages'** + String get labelMovingAverages; + + /// No description provided for @labelMACD. + /// + /// In en, this message translates to: + /// **'MACD'** + String get labelMACD; + + /// No description provided for @labelRelativeStrengthIndex. + /// + /// In en, this message translates to: + /// **'Relative Strength Index (RSI)'** + String get labelRelativeStrengthIndex; + + /// No description provided for @labelRSI. + /// + /// In en, this message translates to: + /// **'RSI'** + String get labelRSI; + + /// No description provided for @labelBollingerBands. + /// + /// In en, this message translates to: + /// **'Bollinger Bands (BB)'** + String get labelBollingerBands; + + /// No description provided for @labelBB. + /// + /// In en, this message translates to: + /// **'BB'** + String get labelBB; + + /// No description provided for @labelMovingAverage. + /// + /// In en, this message translates to: + /// **'Moving Average (MA)'** + String get labelMovingAverage; + + /// No description provided for @labelMA. + /// + /// In en, this message translates to: + /// **'MA'** + String get labelMA; + + /// No description provided for @infoMACD. + /// + /// In en, this message translates to: + /// **'MACD is a trading indicator used in technical analysis of stock prices. It is supposed to reveal changes in the strength, direction, momentum, and duration of a trend in a stock\'s price.'** + String get infoMACD; + + /// No description provided for @infoRSI. + /// + /// In en, this message translates to: + /// **'The Relative Strength Index (RSI) was published by J. Welles Wilder. The current price is normalized as a percentage between 0 and 100. The flutter_chart_id of this oscillator is misleading because it does not compare the instrument relative to another instrument or set of instruments, but rather represents the current price relative to other recent pieces within the selected lookback window length.'** + String get infoRSI; + + /// No description provided for @infoBB. + /// + /// In en, this message translates to: + /// **'Bollinger Bands (BB) can be used to measure the highness or lowness of the price relative to previous trades.'** + String get infoBB; + + /// No description provided for @infoMA. + /// + /// In en, this message translates to: + /// **'The Moving Average (MA) helps to identify the overall market trend by filtering out short-term price fluctuations. Using historical data, it calculates the average price over a specific period and plots a line on the chart. If the MA line moves upwards, it’s an indicator of an uptrend, a downtrend if it moves downwards. A buy signal occurs when the price moves above the MA line.'** + String get infoMA; + + /// No description provided for @infoMaximumActiveIndicatorsAdded. + /// + /// In en, this message translates to: + /// **'You\'ve added the maximum number of active indicators.'** + String get infoMaximumActiveIndicatorsAdded; + + /// No description provided for @infoAddSelectedIndicator. + /// + /// In en, this message translates to: + /// **'Add {indicator}'** + String infoAddSelectedIndicator(Object indicator); + + /// No description provided for @infoAddIndicator. + /// + /// In en, this message translates to: + /// **'Add indicator'** + String get infoAddIndicator; + + /// No description provided for @labelDeleteAllIndicators. + /// + /// In en, this message translates to: + /// **'Delete all indicators'** + String get labelDeleteAllIndicators; + + /// No description provided for @infoDeleteAllIndicators. + /// + /// In en, this message translates to: + /// **'This will delete all active indicators.'** + String get infoDeleteAllIndicators; + + /// No description provided for @infoResetIndicators. + /// + /// In en, this message translates to: + /// **'This will reset the {indicator} indicator to its default settings.'** + String infoResetIndicators(Object indicator); + + /// No description provided for @labelDeleteIndicator. + /// + /// In en, this message translates to: + /// **'Delete {indicator} indicator'** + String labelDeleteIndicator(Object indicator); + + /// No description provided for @labelResetIndicator. + /// + /// In en, this message translates to: + /// **'Reset {indicator} indicator'** + String labelResetIndicator(Object indicator); + + /// No description provided for @infoDeleteIndicator. + /// + /// In en, this message translates to: + /// **'Are you sure you want to delete this indicator?'** + String get infoDeleteIndicator; + + /// No description provided for @labelCancel. + /// + /// In en, this message translates to: + /// **'Cancel'** + String get labelCancel; + + /// No description provided for @labelDelete. + /// + /// In en, this message translates to: + /// **'Delete'** + String get labelDelete; + + /// No description provided for @labelDeleteAll. + /// + /// In en, this message translates to: + /// **'Delete All'** + String get labelDeleteAll; + + /// No description provided for @infoUpto3indicatorsAllowed. + /// + /// In en, this message translates to: + /// **'Up to 3 active indicators allowed.'** + String get infoUpto3indicatorsAllowed; + + /// No description provided for @infoNoActiveIndicators. + /// + /// In en, this message translates to: + /// **'No active indicators.'** + String get infoNoActiveIndicators; + + /// No description provided for @labelReset. + /// + /// In en, this message translates to: + /// **'Reset'** + String get labelReset; + + /// No description provided for @labelApply. + /// + /// In en, this message translates to: + /// **'Apply'** + String get labelApply; + + /// No description provided for @labelOK. + /// + /// In en, this message translates to: + /// **'OK'** + String get labelOK; + + /// No description provided for @labelRSILine. + /// + /// In en, this message translates to: + /// **'RSI line'** + String get labelRSILine; + + /// No description provided for @labelPeriod. + /// + /// In en, this message translates to: + /// **'Period'** + String get labelPeriod; + + /// No description provided for @labelMinRange. + /// + /// In en, this message translates to: + /// **'Min range'** + String get labelMinRange; + + /// No description provided for @labelMaxRange. + /// + /// In en, this message translates to: + /// **'Max range'** + String get labelMaxRange; + + /// No description provided for @labelSource. + /// + /// In en, this message translates to: + /// **'Source'** + String get labelSource; + + /// No description provided for @labelClose. + /// + /// In en, this message translates to: + /// **'Close'** + String get labelClose; + + /// No description provided for @labelOpen. + /// + /// In en, this message translates to: + /// **'Open'** + String get labelOpen; + + /// No description provided for @labelHigh. + /// + /// In en, this message translates to: + /// **'High'** + String get labelHigh; + + /// No description provided for @labelLow. + /// + /// In en, this message translates to: + /// **'Low'** + String get labelLow; + + /// No description provided for @labelHl2. + /// + /// In en, this message translates to: + /// **'Hl/2'** + String get labelHl2; + + /// No description provided for @labelHlc3. + /// + /// In en, this message translates to: + /// **'Hlc/3'** + String get labelHlc3; + + /// No description provided for @labelHlcc4. + /// + /// In en, this message translates to: + /// **'Hlcc/4'** + String get labelHlcc4; + + /// No description provided for @labelOhlc4. + /// + /// In en, this message translates to: + /// **'Ohlc/4'** + String get labelOhlc4; + + /// No description provided for @labelShowZones. + /// + /// In en, this message translates to: + /// **'Show Zones'** + String get labelShowZones; + + /// No description provided for @labelOverbought. + /// + /// In en, this message translates to: + /// **'Overbought'** + String get labelOverbought; + + /// No description provided for @labelOversold. + /// + /// In en, this message translates to: + /// **'Oversold'** + String get labelOversold; + + /// No description provided for @labelMinSize. + /// + /// In en, this message translates to: + /// **'Min size'** + String get labelMinSize; + + /// No description provided for @labelMaxSize. + /// + /// In en, this message translates to: + /// **'Max size'** + String get labelMaxSize; + + /// No description provided for @labelRange. + /// + /// In en, this message translates to: + /// **'Range'** + String get labelRange; + + /// No description provided for @labelOverboughtLine. + /// + /// In en, this message translates to: + /// **'Overbought line'** + String get labelOverboughtLine; + + /// No description provided for @labelOversoldLine. + /// + /// In en, this message translates to: + /// **'Oversold line'** + String get labelOversoldLine; + + /// No description provided for @labelMACDLine. + /// + /// In en, this message translates to: + /// **'MACD line'** + String get labelMACDLine; + + /// No description provided for @labelFastMAPeriod. + /// + /// In en, this message translates to: + /// **'Fast MA period'** + String get labelFastMAPeriod; + + /// No description provided for @labelSlowMAPeriod. + /// + /// In en, this message translates to: + /// **'Slow MA period'** + String get labelSlowMAPeriod; + + /// No description provided for @labelSignalLine. + /// + /// In en, this message translates to: + /// **'Signal line'** + String get labelSignalLine; + + /// No description provided for @labelSignalPeriod. + /// + /// In en, this message translates to: + /// **'Signal period'** + String get labelSignalPeriod; + + /// No description provided for @labelIncreasingBar. + /// + /// In en, this message translates to: + /// **'Increasing bar'** + String get labelIncreasingBar; + + /// No description provided for @labelDecreasingBar. + /// + /// In en, this message translates to: + /// **'Decreasing bar'** + String get labelDecreasingBar; + + /// No description provided for @labelBollingerBandsTop. + /// + /// In en, this message translates to: + /// **'Bollinger Bands top'** + String get labelBollingerBandsTop; + + /// No description provided for @labelBollingerBandsMedian. + /// + /// In en, this message translates to: + /// **'Bollinger Bands median'** + String get labelBollingerBandsMedian; + + /// No description provided for @labelBollingerBandsBottom. + /// + /// In en, this message translates to: + /// **'Bollinger Bands bottom'** + String get labelBollingerBandsBottom; + + /// No description provided for @labelChannelFill. + /// + /// In en, this message translates to: + /// **'Channel fill'** + String get labelChannelFill; + + /// No description provided for @labelFillColor. + /// + /// In en, this message translates to: + /// **'Fill color'** + String get labelFillColor; + + /// No description provided for @labelStandardDeviations. + /// + /// In en, this message translates to: + /// **'Standard deviations'** + String get labelStandardDeviations; + + /// No description provided for @labelMovingAverageType. + /// + /// In en, this message translates to: + /// **'Moving Average Type'** + String get labelMovingAverageType; + + /// No description provided for @labelMALine. + /// + /// In en, this message translates to: + /// **'MA line'** + String get labelMALine; + + /// No description provided for @labelOffset. + /// + /// In en, this message translates to: + /// **'Offset'** + String get labelOffset; + + /// No description provided for @labelType. + /// + /// In en, this message translates to: + /// **'Type'** + String get labelType; + + /// No description provided for @labelSimple. + /// + /// In en, this message translates to: + /// **'Simple'** + String get labelSimple; + + /// No description provided for @labelExponential. + /// + /// In en, this message translates to: + /// **'Exponential'** + String get labelExponential; + + /// No description provided for @labelWeighted. + /// + /// In en, this message translates to: + /// **'Weighted'** + String get labelWeighted; + + /// No description provided for @labelHull. + /// + /// In en, this message translates to: + /// **'Hull'** + String get labelHull; + + /// No description provided for @labelZeroLag. + /// + /// In en, this message translates to: + /// **'Zero Lag'** + String get labelZeroLag; + + /// No description provided for @labelTimeSeries. + /// + /// In en, this message translates to: + /// **'Time Series'** + String get labelTimeSeries; + + /// No description provided for @labelWellesWilder. + /// + /// In en, this message translates to: + /// **'Welles Wilder'** + String get labelWellesWilder; + + /// No description provided for @labelVariable. + /// + /// In en, this message translates to: + /// **'Variable'** + String get labelVariable; + + /// No description provided for @labelTriangular. + /// + /// In en, this message translates to: + /// **'Triangular'** + String get labelTriangular; + + /// No description provided for @label2Exponential. + /// + /// In en, this message translates to: + /// **'2-Exponential'** + String get label2Exponential; + + /// No description provided for @label3Exponential. + /// + /// In en, this message translates to: + /// **'3-Exponential'** + String get label3Exponential; + + /// No description provided for @warnEnterValueBetweenMinMax. + /// + /// In en, this message translates to: + /// **'Enter a value between {min} and {max}'** + String warnEnterValueBetweenMinMax(Object max, Object min); + + /// No description provided for @warnRangeMinMax. + /// + /// In en, this message translates to: + /// **'Range {min} - {max}'** + String warnRangeMinMax(Object max, Object min); + + /// No description provided for @labelDrawingTools. + /// + /// In en, this message translates to: + /// **'Drawing tools'** + String get labelDrawingTools; + + /// No description provided for @labelTools. + /// + /// In en, this message translates to: + /// **'Tools'** + String get labelTools; + + /// No description provided for @labelLine. + /// + /// In en, this message translates to: + /// **'Line'** + String get labelLine; + + /// No description provided for @labelRay. + /// + /// In en, this message translates to: + /// **'Ray'** + String get labelRay; + + /// No description provided for @informTapToSetFirstPoint. + /// + /// In en, this message translates to: + /// **'Tap to set first point'** + String get informTapToSetFirstPoint; + + /// No description provided for @informTapToSetFinalPoint. + /// + /// In en, this message translates to: + /// **'Tap to set final point'** + String get informTapToSetFinalPoint; + + /// No description provided for @informNoActiveDrawingTools. + /// + /// In en, this message translates to: + /// **'No active drawing tools.'** + String get informNoActiveDrawingTools; + + /// No description provided for @actionAddDrawingTool. + /// + /// In en, this message translates to: + /// **'Add drawing tool'** + String get actionAddDrawingTool; + + /// No description provided for @labelOf. + /// + /// In en, this message translates to: + /// **'of'** + String get labelOf; + + /// No description provided for @labelDeleteAllDrawingTools. + /// + /// In en, this message translates to: + /// **'Delete all drawing tools'** + String get labelDeleteAllDrawingTools; + + /// No description provided for @informDeleteAllDrawingTools. + /// + /// In en, this message translates to: + /// **'This will delete all active drawing tools.'** + String get informDeleteAllDrawingTools; +} + +class _DerivMobileChartWrapperLocalizationsDelegate extends LocalizationsDelegate { + const _DerivMobileChartWrapperLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupDerivMobileChartWrapperLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => ['ar', 'bn', 'de', 'en', 'es', 'fr', 'it', 'km', 'ko', 'mn', 'pl', 'pt', 'ru', 'si', 'sw', 'th', 'tr', 'uz', 'vi', 'zh'].contains(locale.languageCode); + + @override + bool shouldReload(_DerivMobileChartWrapperLocalizationsDelegate old) => false; +} + +DerivMobileChartWrapperLocalizations lookupDerivMobileChartWrapperLocalizations(Locale locale) { + + // Lookup logic when language+country codes are specified. + switch (locale.languageCode) { + case 'zh': { + switch (locale.countryCode) { + case 'CN': return DerivMobileChartWrapperLocalizationsZhCn(); +case 'TW': return DerivMobileChartWrapperLocalizationsZhTw(); + } + break; + } + } + + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'ar': return DerivMobileChartWrapperLocalizationsAr(); + case 'bn': return DerivMobileChartWrapperLocalizationsBn(); + case 'de': return DerivMobileChartWrapperLocalizationsDe(); + case 'en': return DerivMobileChartWrapperLocalizationsEn(); + case 'es': return DerivMobileChartWrapperLocalizationsEs(); + case 'fr': return DerivMobileChartWrapperLocalizationsFr(); + case 'it': return DerivMobileChartWrapperLocalizationsIt(); + case 'km': return DerivMobileChartWrapperLocalizationsKm(); + case 'ko': return DerivMobileChartWrapperLocalizationsKo(); + case 'mn': return DerivMobileChartWrapperLocalizationsMn(); + case 'pl': return DerivMobileChartWrapperLocalizationsPl(); + case 'pt': return DerivMobileChartWrapperLocalizationsPt(); + case 'ru': return DerivMobileChartWrapperLocalizationsRu(); + case 'si': return DerivMobileChartWrapperLocalizationsSi(); + case 'sw': return DerivMobileChartWrapperLocalizationsSw(); + case 'th': return DerivMobileChartWrapperLocalizationsTh(); + case 'tr': return DerivMobileChartWrapperLocalizationsTr(); + case 'uz': return DerivMobileChartWrapperLocalizationsUz(); + case 'vi': return DerivMobileChartWrapperLocalizationsVi(); + case 'zh': return DerivMobileChartWrapperLocalizationsZh(); + } + + throw FlutterError( + 'DerivMobileChartWrapperLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.' + ); +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_ar.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_ar.dart new file mode 100644 index 000000000..af0bcefbc --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_ar.dart @@ -0,0 +1,306 @@ +import 'deriv_mobile_chart_wrapper_localizations.dart'; + +/// The translations for Arabic (`ar`). +class DerivMobileChartWrapperLocalizationsAr extends DerivMobileChartWrapperLocalizations { + DerivMobileChartWrapperLocalizationsAr([String locale = 'ar']) : super(locale); + + @override + String get labelIndicators => 'المؤشرات'; + + @override + String get labelActive => 'نشط'; + + @override + String get labelAll => 'الكل'; + + @override + String get labelMomentum => 'الزخم'; + + @override + String get labelVolatility => 'التقلبات'; + + @override + String get labelMovingAverages => 'المتوسطات المتحركة'; + + @override + String get labelMACD => 'MACD'; + + @override + String get labelRelativeStrengthIndex => 'مؤشر القوة النسبية (RSI)'; + + @override + String get labelRSI => 'مؤشر القوة النسبية'; + + @override + String get labelBollingerBands => 'البولنجر باند (BB)'; + + @override + String get labelBB => 'ب ب'; + + @override + String get labelMovingAverage => 'المتوسط المتحرك (MA)'; + + @override + String get labelMA => 'ماجستير'; + + @override + String get infoMACD => 'مؤشر MACD هو مؤشر تداول يستخدم في التحليل الفني لأسعار الأسهم. ومن المفترض أن يكشف عن التغيرات في قوة واتجاه وزخم ومدة الاتجاه في سعر السهم.'; + + @override + String get infoRSI => 'تم نشر مؤشر القوة النسبية (RSI) بواسطة J. Welles Wilder. يتم تطبيع السعر الحالي كنسبة مئوية بين 0 و 100. إن معرف flutter_chart_id لهذا المذبذب مضلل لأنه لا يقارن الأداة بالنسبة إلى أداة أخرى أو مجموعة من الأدوات، بل يمثل السعر الحالي بالنسبة إلى القطع الحديثة الأخرى ضمن طول نافذة الاسترجاع المحددة.'; + + @override + String get infoBB => 'يمكن استخدام مؤشر بولينجر باند (BB) لقياس مدى ارتفاع أو انخفاض السعر بالنسبة للصفقات السابقة.'; + + @override + String get infoMA => 'يساعد المتوسط المتحرك (MA) على تحديد الاتجاه العام للسوق من خلال تصفية تقلبات الأسعار على المدى القصير. وباستخدام البيانات التاريخية، يحسب المتوسط المتحرك متوسط السعر خلال فترة محددة ويرسم خطًا على الرسم البياني. إذا تحرك خط المتوسط المتحرك المتحرك لأعلى، فهذا مؤشر على وجود اتجاه صعودي، ويكون مؤشرًا على اتجاه هبوطي إذا تحرك لأسفل. وتحدث إشارة الشراء عندما يتحرك السعر فوق خط المتوسط المتحرك.'; + + @override + String get infoMaximumActiveIndicatorsAdded => 'لقد أضفت الحد الأقصى لعدد المؤشرات النشطة.'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return 'إضافة $indicator'; + } + + @override + String get infoAddIndicator => 'إضافة مؤشر'; + + @override + String get labelDeleteAllIndicators => 'حذف جميع المؤشرات'; + + @override + String get infoDeleteAllIndicators => 'سيؤدي ذلك إلى حذف جميع المؤشرات النشطة.'; + + @override + String infoResetIndicators(Object indicator) { + return 'سيؤدي هذا إلى إعادة تعيين مؤشر $indicator إلى إعداداته الافتراضية.'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return 'حذف المؤشر $indicator'; + } + + @override + String labelResetIndicator(Object indicator) { + return 'إعادة تعيين مؤشر $indicator'; + } + + @override + String get infoDeleteIndicator => 'هل أنت متأكد من رغبتك في حذف هذا المؤشر؟'; + + @override + String get labelCancel => 'إلغاء'; + + @override + String get labelDelete => 'حذف'; + + @override + String get labelDeleteAll => 'حذف الكل'; + + @override + String get infoUpto3indicatorsAllowed => 'يُسمح بحد أقصى 3 مؤشرات نشطة.'; + + @override + String get infoNoActiveIndicators => 'لا توجد مؤشرات نشطة.'; + + @override + String get labelReset => 'إعادة تعيين'; + + @override + String get labelApply => 'قدم طلبك'; + + @override + String get labelOK => 'حسناً'; + + @override + String get labelRSILine => 'خط مؤشر القوة النسبية RSI'; + + @override + String get labelPeriod => 'الفترة'; + + @override + String get labelMinRange => 'الحد الأدنى للنطاق'; + + @override + String get labelMaxRange => 'الحد الأقصى للنطاق'; + + @override + String get labelSource => 'المصدر'; + + @override + String get labelClose => 'إغلاق'; + + @override + String get labelOpen => 'افتح'; + + @override + String get labelHigh => 'عالية'; + + @override + String get labelLow => 'منخفضة'; + + @override + String get labelHl2 => 'هـ / 2'; + + @override + String get labelHlc3 => 'Hlc/3'; + + @override + String get labelHlcc4 => 'Hlcc/4'; + + @override + String get labelOhlc4 => 'أولك/4'; + + @override + String get labelShowZones => 'مناطق العرض'; + + @override + String get labelOverbought => 'الإفراط في الشراء'; + + @override + String get labelOversold => 'المبالغة في البيع'; + + @override + String get labelMinSize => 'الحد الأدنى للحجم'; + + @override + String get labelMaxSize => 'الحد الأقصى للحجم'; + + @override + String get labelRange => 'النطاق'; + + @override + String get labelOverboughtLine => 'خط ذروة الشراء'; + + @override + String get labelOversoldLine => 'خط المبيع الزائد'; + + @override + String get labelMACDLine => 'خط MACD'; + + @override + String get labelFastMAPeriod => 'فترة المتوسط المتحرك السريع'; + + @override + String get labelSlowMAPeriod => 'فترة بطء المتوسط المتحرك البطيء'; + + @override + String get labelSignalLine => 'خط الإشارة'; + + @override + String get labelSignalPeriod => 'فترة الإشارة'; + + @override + String get labelIncreasingBar => 'زيادة الشريط'; + + @override + String get labelDecreasingBar => 'شريط تنازلي'; + + @override + String get labelBollingerBandsTop => 'قمة بولينجر باندز'; + + @override + String get labelBollingerBandsMedian => 'متوسط بولينجر باندز'; + + @override + String get labelBollingerBandsBottom => 'قاع بولينجر باندز'; + + @override + String get labelChannelFill => 'تعبئة القناة'; + + @override + String get labelFillColor => 'لون التعبئة'; + + @override + String get labelStandardDeviations => 'الانحرافات المعيارية'; + + @override + String get labelMovingAverageType => 'نوع المتوسط المتحرك'; + + @override + String get labelMALine => 'خط MA'; + + @override + String get labelOffset => 'الأوفست'; + + @override + String get labelType => 'النوع'; + + @override + String get labelSimple => 'بسيطة'; + + @override + String get labelExponential => 'الأسي'; + + @override + String get labelWeighted => 'مرجح'; + + @override + String get labelHull => 'هال'; + + @override + String get labelZeroLag => 'التأخر الصفري'; + + @override + String get labelTimeSeries => 'السلاسل الزمنية'; + + @override + String get labelWellesWilder => 'ويلز وايلدر'; + + @override + String get labelVariable => 'متغير'; + + @override + String get labelTriangular => 'مثلث الشكل'; + + @override + String get label2Exponential => '2-الأسي'; + + @override + String get label3Exponential => '3-الإكسبونسي'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return 'أدخل قيمة بين $min و $max'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return 'النطاق $min - $max'; + } + + @override + String get labelDrawingTools => 'أدوات الرسم'; + + @override + String get labelTools => 'أدوات'; + + @override + String get labelLine => 'خط'; + + @override + String get labelRay => 'شعاع'; + + @override + String get informTapToSetFirstPoint => 'انقر لتعيين النقطة الأولى'; + + @override + String get informTapToSetFinalPoint => 'انقر لتعيين النقطة النهائية'; + + @override + String get informNoActiveDrawingTools => 'لا توجد أدوات رسم نشطة.'; + + @override + String get actionAddDrawingTool => 'إضافة أداة رسم'; + + @override + String get labelOf => 'من'; + + @override + String get labelDeleteAllDrawingTools => 'احذف جميع أدوات الرسم'; + + @override + String get informDeleteAllDrawingTools => 'سيؤدي هذا إلى حذف جميع أدوات الرسم النشطة.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_bn.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_bn.dart new file mode 100644 index 000000000..bc59ab682 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_bn.dart @@ -0,0 +1,306 @@ +import 'deriv_mobile_chart_wrapper_localizations.dart'; + +/// The translations for Bengali Bangla (`bn`). +class DerivMobileChartWrapperLocalizationsBn extends DerivMobileChartWrapperLocalizations { + DerivMobileChartWrapperLocalizationsBn([String locale = 'bn']) : super(locale); + + @override + String get labelIndicators => 'সূচক'; + + @override + String get labelActive => 'সক্রিয়'; + + @override + String get labelAll => 'সব'; + + @override + String get labelMomentum => 'মোমেন্টাম'; + + @override + String get labelVolatility => 'অস্থিরতা'; + + @override + String get labelMovingAverages => 'মুভিং গড়'; + + @override + String get labelMACD => 'ম্যাকডি'; + + @override + String get labelRelativeStrengthIndex => 'আপেক্ষিক শক্তি সূচক (আরএসআই)'; + + @override + String get labelRSI => 'আরএসআই'; + + @override + String get labelBollingerBands => 'বোলিঙ্গার ব্যান্ড (বিবি)'; + + @override + String get labelBB => 'বিবি'; + + @override + String get labelMovingAverage => 'মুভিং এভারেজ (এমএ)'; + + @override + String get labelMA => 'মা'; + + @override + String get infoMACD => 'এমএসিডি স্টকের দামের প্রযুক্তিগত বিশ্লেষণে ব্যবহৃত একটি ট্রেডিং সূচক। এটি কোনও স্টকের দামের প্রবণতার শক্তি, দিক, গতি এবং সময়কালের পরিবর্তনগুলি প্রকাশ করবে বলে মনে করা হয়।'; + + @override + String get infoRSI => 'আপেক্ষিক শক্তি সূচক (আরএসআই) প্রকাশ করেছিলেন জে ওয়েলস ওয়াইল্ডার। বর্তমান মূল্য 0 থেকে 100 এর মধ্যে শতাংশ হিসাবে স্বাভাবিক করা হয়। এই অসিলেটরের ফ্লুটার_চার্ট_আইডি বিভ্রান্তিকর কারণ এটি অন্য যন্ত্র বা যন্ত্রের সেটের সাথে তুলনা করে না, বরং নির্বাচিত লুকব্যাক উইন্ডো দৈর্ঘ্যের মধ্যে অন্যান্য সাম্প্রতিক টুকরোগুলির তুলনায় বর্তমান দামের প্রতিনিধিত্ব করে।'; + + @override + String get infoBB => 'পূর্ববর্তী ট্রেডগুলির তুলনায় মূল্যের উচ্চতা বা নিম্নতা পরিমাপ করতে বোলিঙ্গার ব্যান্ড (বিবি) ব্যবহার করা যেতে পারে।'; + + @override + String get infoMA => 'মুভিং এভারেজ (এমএ) স্বল্পমেয়াদী মূল্যের ওঠানামা ফিল্টার করে সামগ্রিক বাজারের প্রবণতা সনাক্ত করতে ঐতিহাসিক ডেটা ব্যবহার করে, এটি একটি নির্দিষ্ট সময়কালে গড় মূল্য গণনা করে এবং চার্টে একটি লাইন প্লট করে। এমএ লাইন যদি উপরের দিকে চলে যায় তবে এটি একটি আপট্রেন্ডের সূচক, নীচের দিকে চলে গেলে একটি ডাউনট্রেন্ড। মূল্য এমএ লাইনের উপরে চলে গেলে একটি ক্রয় সংকেত ঘটে।'; + + @override + String get infoMaximumActiveIndicatorsAdded => 'আপনি সর্বাধিক সংখ্যক সক্রিয় সূচক যুক্ত করেছেন।'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return 'যোগ করুন $indicator'; + } + + @override + String get infoAddIndicator => 'সূচক যোগ করুন'; + + @override + String get labelDeleteAllIndicators => 'সমস্ত সূচক মুছুন'; + + @override + String get infoDeleteAllIndicators => 'এটি সমস্ত সক্রিয় সূচক মুছে ফেলবে।'; + + @override + String infoResetIndicators(Object indicator) { + return 'এটি $indicator সূচকটিকে তার ডিফল্ট সেটিংসে পুনরায় সেট করবে।'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return '$indicator সূচক মুছুন'; + } + + @override + String labelResetIndicator(Object indicator) { + return 'রিসেট $indicator সূচক'; + } + + @override + String get infoDeleteIndicator => 'আপনি কি নিশ্চিত যে আপনি এই সূচকটি মুছে ফেলতে চান?'; + + @override + String get labelCancel => 'বাতিল করুন'; + + @override + String get labelDelete => 'মুছে ফেলুন'; + + @override + String get labelDeleteAll => 'সব মুছে ফেলুন'; + + @override + String get infoUpto3indicatorsAllowed => '3 টি পর্যন্ত সক্রিয় সূচক অনুমোদিত।'; + + @override + String get infoNoActiveIndicators => 'কোন সক্রিয় সূচক নেই।'; + + @override + String get labelReset => 'রিসেট করুন'; + + @override + String get labelApply => 'আবেদন করুন'; + + @override + String get labelOK => 'ঠিক আছে'; + + @override + String get labelRSILine => 'আরএসআই লাইন'; + + @override + String get labelPeriod => 'সময়কাল'; + + @override + String get labelMinRange => 'নিম্ন পরিসীমা'; + + @override + String get labelMaxRange => 'সর্বোচ্চ ব্যাপ্তি'; + + @override + String get labelSource => 'উৎস'; + + @override + String get labelClose => 'বন্ধ করুন'; + + @override + String get labelOpen => 'খোলা'; + + @override + String get labelHigh => 'উচ্চ'; + + @override + String get labelLow => 'নিম্ন'; + + @override + String get labelHl2 => 'এইচএল/2'; + + @override + String get labelHlc3 => 'এইচএলসি/3'; + + @override + String get labelHlcc4 => 'এইচএলসিসি/4'; + + @override + String get labelOhlc4 => 'ওএইচএলসি/4'; + + @override + String get labelShowZones => 'অঞ্চল দেখান'; + + @override + String get labelOverbought => 'অতিরিক্ত খরচ'; + + @override + String get labelOversold => 'অতিরিক্ত বিক্রি'; + + @override + String get labelMinSize => 'ন্যূনতম আকার'; + + @override + String get labelMaxSize => 'সর্বোচ্চ আকার'; + + @override + String get labelRange => 'পরিসীমা'; + + @override + String get labelOverboughtLine => 'ওভারবাউট লাইন'; + + @override + String get labelOversoldLine => 'ওভারসোল্ড লাইন'; + + @override + String get labelMACDLine => 'এমএসিডি লাইন'; + + @override + String get labelFastMAPeriod => 'দ্রুত এমএ পিরিয়ড'; + + @override + String get labelSlowMAPeriod => 'ধীর এমএ পিরিয়ড'; + + @override + String get labelSignalLine => 'সিগন্যাল লাইন'; + + @override + String get labelSignalPeriod => 'সংকেত সময়কাল'; + + @override + String get labelIncreasingBar => 'বর্ধমান বার'; + + @override + String get labelDecreasingBar => 'হ্রাস করা বার'; + + @override + String get labelBollingerBandsTop => 'বোলিংগার ব্যান্ড শীর্ষ'; + + @override + String get labelBollingerBandsMedian => 'বোলিঙ্গার ব্যান্ডের মাঝারি'; + + @override + String get labelBollingerBandsBottom => 'বোলিঙ্গার ব্যান্ড নীচে'; + + @override + String get labelChannelFill => 'চ্যানেল পূরণ'; + + @override + String get labelFillColor => 'রঙ পূরণ করুন'; + + @override + String get labelStandardDeviations => 'মান বিচ্যুতি'; + + @override + String get labelMovingAverageType => 'মুভিং এভারেজ প্রকার'; + + @override + String get labelMALine => 'এমএ লাইন'; + + @override + String get labelOffset => 'অফসেট'; + + @override + String get labelType => 'টাইপ'; + + @override + String get labelSimple => 'সহজ'; + + @override + String get labelExponential => 'এক্সপোনেন্সিয়াল'; + + @override + String get labelWeighted => 'ওজনযুক্ত'; + + @override + String get labelHull => 'হাল'; + + @override + String get labelZeroLag => 'জিরো ল্যাগ'; + + @override + String get labelTimeSeries => 'টাইম সিরিজ'; + + @override + String get labelWellesWilder => 'ওয়েলস ওয়াইল্ডার'; + + @override + String get labelVariable => 'পরিবর্তনশীল'; + + @override + String get labelTriangular => 'ত্রিভুজ'; + + @override + String get label2Exponential => '2-এক্সপোনেন্সিয়াল'; + + @override + String get label3Exponential => '3-এক্সপোনেন্সিয়াল'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return '$min এবং $maxএর মধ্যে একটি মান লিখুন'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return 'রেঞ্জ $min - $max'; + } + + @override + String get labelDrawingTools => 'অঙ্কন টুলস'; + + @override + String get labelTools => 'সরঞ্জাম'; + + @override + String get labelLine => 'লাইন'; + + @override + String get labelRay => 'রশ্মি'; + + @override + String get informTapToSetFirstPoint => 'প্রথম পয়েন্ট সেট করতে আলতো চাপুন'; + + @override + String get informTapToSetFinalPoint => 'চূড়ান্ত পয়েন্ট সেট করতে ট্যাপ করুন'; + + @override + String get informNoActiveDrawingTools => 'সক্রিয় অঙ্কন সরঞ্জাম নেই।'; + + @override + String get actionAddDrawingTool => 'অঙ্কন টুল যোগ করুন'; + + @override + String get labelOf => 'এর'; + + @override + String get labelDeleteAllDrawingTools => 'সমস্ত অঙ্কন সরঞ্জাম মুছে'; + + @override + String get informDeleteAllDrawingTools => 'এটি সমস্ত সক্রিয় অঙ্কন সরঞ্জাম মুছে ফেলবে।'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_de.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_de.dart new file mode 100644 index 000000000..200b31793 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_de.dart @@ -0,0 +1,306 @@ +import 'deriv_mobile_chart_wrapper_localizations.dart'; + +/// The translations for German (`de`). +class DerivMobileChartWrapperLocalizationsDe extends DerivMobileChartWrapperLocalizations { + DerivMobileChartWrapperLocalizationsDe([String locale = 'de']) : super(locale); + + @override + String get labelIndicators => 'Indikatoren'; + + @override + String get labelActive => 'Aktiv'; + + @override + String get labelAll => 'Alle'; + + @override + String get labelMomentum => 'Momentum'; + + @override + String get labelVolatility => 'Volatilität'; + + @override + String get labelMovingAverages => 'Gleitende Durchschnitte'; + + @override + String get labelMACD => 'MACD'; + + @override + String get labelRelativeStrengthIndex => 'Relative Strength Index (RSI)'; + + @override + String get labelRSI => 'RSI'; + + @override + String get labelBollingerBands => 'Bollinger Bands (BB)'; + + @override + String get labelBB => 'BB'; + + @override + String get labelMovingAverage => 'Gleitender Durchschnitt (MA)'; + + @override + String get labelMA => 'MA'; + + @override + String get infoMACD => 'Der MACD ist ein Handelsindikator, der bei der technischen Analyse von Aktienkursen verwendet wird. Er soll Veränderungen in der Stärke, Richtung, Dynamik und Dauer eines Trends bei einem Aktienkurs aufzeigen.'; + + @override + String get infoRSI => 'Der Relative Strength Index (RSI) wurde von J. Welles Wilder veröffentlicht. Der aktuelle Preis wird als Prozentsatz zwischen 0 und 100 normiert. Die flutter_chart_id dieses Oszillators ist irreführend, da sie das Instrument nicht relativ zu einem anderen Instrument oder einer Gruppe von Instrumenten vergleicht, sondern den aktuellen Preis relativ zu anderen kürzlich beobachteten Werten innerhalb des ausgewählten Betrachtungszeitraums darstellt.'; + + @override + String get infoBB => 'Bollinger Bänder (BB) können verwendet werden, um die Höhe oder Tiefe des Kurses im Vergleich zu früheren Handel zu messen.'; + + @override + String get infoMA => 'Der gleitende Durchschnitt (MA) hilft dabei, den allgemeinen Markttrend zu erkennen, indem er kurzfristige Kursschwankungen herausfiltert. Anhand historischer Daten berechnet er den Durchschnittspreis über einen bestimmten Zeitraum und zeichnet eine Linie in den Chart ein. Bewegt sich die MA-Linie nach oben, ist dies ein Indikator für einen Aufwärtstrend, bewegt sie sich nach unten, ist dies ein Abwärtstrend. Ein Kaufsignal entsteht, wenn sich der Kurs über die MA-Linie bewegt.'; + + @override + String get infoMaximumActiveIndicatorsAdded => 'Sie haben die maximale Anzahl von aktiven Indikatoren hinzugefügt.'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return '${indicator}hinzufügen'; + } + + @override + String get infoAddIndicator => 'Indikator hinzufügen'; + + @override + String get labelDeleteAllIndicators => 'Alle Indikatoren löschen'; + + @override + String get infoDeleteAllIndicators => 'Dadurch werden alle aktiven Indikatoren gelöscht.'; + + @override + String infoResetIndicators(Object indicator) { + return 'Dadurch wird der Indikator $indicator auf seine Standardeinstellungen zurückgesetzt.'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return 'Löschen $indicator Indikator'; + } + + @override + String labelResetIndicator(Object indicator) { + return 'Anzeige $indicator zurücksetzen'; + } + + @override + String get infoDeleteIndicator => 'Sind Sie sicher, dass Sie diesen Indikator löschen möchten?'; + + @override + String get labelCancel => 'Abbrechen'; + + @override + String get labelDelete => 'Löschen'; + + @override + String get labelDeleteAll => 'Alle löschen'; + + @override + String get infoUpto3indicatorsAllowed => 'Bis zu 3 aktive Indikatoren erlaubt.'; + + @override + String get infoNoActiveIndicators => 'Keine aktiven Indikatoren.'; + + @override + String get labelReset => 'Zurücksetzen'; + + @override + String get labelApply => 'Anwenden'; + + @override + String get labelOK => 'OK'; + + @override + String get labelRSILine => 'RSI-Linie'; + + @override + String get labelPeriod => 'Zeitraum'; + + @override + String get labelMinRange => 'Min. Reichweite'; + + @override + String get labelMaxRange => 'Maximale Reichweite'; + + @override + String get labelSource => 'Quelle'; + + @override + String get labelClose => 'Schließen Sie'; + + @override + String get labelOpen => 'Öffnen Sie'; + + @override + String get labelHigh => 'High'; + + @override + String get labelLow => 'Low'; + + @override + String get labelHl2 => 'Hl/2'; + + @override + String get labelHlc3 => 'Hlc/3'; + + @override + String get labelHlcc4 => 'Hlcc/4'; + + @override + String get labelOhlc4 => 'Ohlc/4'; + + @override + String get labelShowZones => 'Zonen anzeigen'; + + @override + String get labelOverbought => 'Überkauft'; + + @override + String get labelOversold => 'Überverkauft'; + + @override + String get labelMinSize => 'Minimale Größe'; + + @override + String get labelMaxSize => 'Maximale Größe'; + + @override + String get labelRange => 'Reichweite'; + + @override + String get labelOverboughtLine => 'Überkaufte Linie'; + + @override + String get labelOversoldLine => 'Überverkaufte Linie'; + + @override + String get labelMACDLine => 'MACD-Linie'; + + @override + String get labelFastMAPeriod => 'Schnelle MA-Periode'; + + @override + String get labelSlowMAPeriod => 'Langsame MA-Periode'; + + @override + String get labelSignalLine => 'Signalleitung'; + + @override + String get labelSignalPeriod => 'Signalperiode'; + + @override + String get labelIncreasingBar => 'Steigende Bar'; + + @override + String get labelDecreasingBar => 'Abnehmender Balken'; + + @override + String get labelBollingerBandsTop => 'Bollinger Bands oben'; + + @override + String get labelBollingerBandsMedian => 'Bollinger Bands Median'; + + @override + String get labelBollingerBandsBottom => 'Bollinger Bands unten'; + + @override + String get labelChannelFill => 'Kanal füllen'; + + @override + String get labelFillColor => 'Farbe ausfüllen'; + + @override + String get labelStandardDeviations => 'Standardabweichungen'; + + @override + String get labelMovingAverageType => 'Gleitender Durchschnitt Typ'; + + @override + String get labelMALine => 'MA-Linie'; + + @override + String get labelOffset => 'Versetzt'; + + @override + String get labelType => 'Typ'; + + @override + String get labelSimple => 'Einfach'; + + @override + String get labelExponential => 'Exponential'; + + @override + String get labelWeighted => 'Gewichtet'; + + @override + String get labelHull => 'Rumpf'; + + @override + String get labelZeroLag => 'Null Verzögerung'; + + @override + String get labelTimeSeries => 'Zeitreihen'; + + @override + String get labelWellesWilder => 'Welles Wilder'; + + @override + String get labelVariable => 'Variabel'; + + @override + String get labelTriangular => 'Dreieckig'; + + @override + String get label2Exponential => '2-Exponential'; + + @override + String get label3Exponential => '3-Exponential'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return 'Geben Sie einen Wert zwischen $min und ${max}ein.'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return 'Reichweite $min - $max'; + } + + @override + String get labelDrawingTools => 'Werkzeuge zum Zeichnen'; + + @override + String get labelTools => 'Werkzeuge'; + + @override + String get labelLine => 'Linie'; + + @override + String get labelRay => 'Ray'; + + @override + String get informTapToSetFirstPoint => 'Tippe, um den ersten Punkt zu setzen'; + + @override + String get informTapToSetFinalPoint => 'Tippe, um den Endpunkt festzulegen'; + + @override + String get informNoActiveDrawingTools => 'Keine aktiven Zeichenwerkzeuge.'; + + @override + String get actionAddDrawingTool => 'Zeichentool hinzufügen'; + + @override + String get labelOf => 'von'; + + @override + String get labelDeleteAllDrawingTools => 'Alle Zeichenwerkzeuge löschen'; + + @override + String get informDeleteAllDrawingTools => 'Dadurch werden alle aktiven Zeichenwerkzeuge gelöscht.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_en.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_en.dart new file mode 100644 index 000000000..6b71b63b1 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_en.dart @@ -0,0 +1,306 @@ +import 'deriv_mobile_chart_wrapper_localizations.dart'; + +/// The translations for English (`en`). +class DerivMobileChartWrapperLocalizationsEn extends DerivMobileChartWrapperLocalizations { + DerivMobileChartWrapperLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get labelIndicators => 'Indicators'; + + @override + String get labelActive => 'Active'; + + @override + String get labelAll => 'All'; + + @override + String get labelMomentum => 'Momentum'; + + @override + String get labelVolatility => 'Volatility'; + + @override + String get labelMovingAverages => 'Moving averages'; + + @override + String get labelMACD => 'MACD'; + + @override + String get labelRelativeStrengthIndex => 'Relative Strength Index (RSI)'; + + @override + String get labelRSI => 'RSI'; + + @override + String get labelBollingerBands => 'Bollinger Bands (BB)'; + + @override + String get labelBB => 'BB'; + + @override + String get labelMovingAverage => 'Moving Average (MA)'; + + @override + String get labelMA => 'MA'; + + @override + String get infoMACD => 'MACD is a trading indicator used in technical analysis of stock prices. It is supposed to reveal changes in the strength, direction, momentum, and duration of a trend in a stock\'s price.'; + + @override + String get infoRSI => 'The Relative Strength Index (RSI) was published by J. Welles Wilder. The current price is normalized as a percentage between 0 and 100. The flutter_chart_id of this oscillator is misleading because it does not compare the instrument relative to another instrument or set of instruments, but rather represents the current price relative to other recent pieces within the selected lookback window length.'; + + @override + String get infoBB => 'Bollinger Bands (BB) can be used to measure the highness or lowness of the price relative to previous trades.'; + + @override + String get infoMA => 'The Moving Average (MA) helps to identify the overall market trend by filtering out short-term price fluctuations. Using historical data, it calculates the average price over a specific period and plots a line on the chart. If the MA line moves upwards, it’s an indicator of an uptrend, a downtrend if it moves downwards. A buy signal occurs when the price moves above the MA line.'; + + @override + String get infoMaximumActiveIndicatorsAdded => 'You\'ve added the maximum number of active indicators.'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return 'Add $indicator'; + } + + @override + String get infoAddIndicator => 'Add indicator'; + + @override + String get labelDeleteAllIndicators => 'Delete all indicators'; + + @override + String get infoDeleteAllIndicators => 'This will delete all active indicators.'; + + @override + String infoResetIndicators(Object indicator) { + return 'This will reset the $indicator indicator to its default settings.'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return 'Delete $indicator indicator'; + } + + @override + String labelResetIndicator(Object indicator) { + return 'Reset $indicator indicator'; + } + + @override + String get infoDeleteIndicator => 'Are you sure you want to delete this indicator?'; + + @override + String get labelCancel => 'Cancel'; + + @override + String get labelDelete => 'Delete'; + + @override + String get labelDeleteAll => 'Delete All'; + + @override + String get infoUpto3indicatorsAllowed => 'Up to 3 active indicators allowed.'; + + @override + String get infoNoActiveIndicators => 'No active indicators.'; + + @override + String get labelReset => 'Reset'; + + @override + String get labelApply => 'Apply'; + + @override + String get labelOK => 'OK'; + + @override + String get labelRSILine => 'RSI line'; + + @override + String get labelPeriod => 'Period'; + + @override + String get labelMinRange => 'Min range'; + + @override + String get labelMaxRange => 'Max range'; + + @override + String get labelSource => 'Source'; + + @override + String get labelClose => 'Close'; + + @override + String get labelOpen => 'Open'; + + @override + String get labelHigh => 'High'; + + @override + String get labelLow => 'Low'; + + @override + String get labelHl2 => 'Hl/2'; + + @override + String get labelHlc3 => 'Hlc/3'; + + @override + String get labelHlcc4 => 'Hlcc/4'; + + @override + String get labelOhlc4 => 'Ohlc/4'; + + @override + String get labelShowZones => 'Show Zones'; + + @override + String get labelOverbought => 'Overbought'; + + @override + String get labelOversold => 'Oversold'; + + @override + String get labelMinSize => 'Min size'; + + @override + String get labelMaxSize => 'Max size'; + + @override + String get labelRange => 'Range'; + + @override + String get labelOverboughtLine => 'Overbought line'; + + @override + String get labelOversoldLine => 'Oversold line'; + + @override + String get labelMACDLine => 'MACD line'; + + @override + String get labelFastMAPeriod => 'Fast MA period'; + + @override + String get labelSlowMAPeriod => 'Slow MA period'; + + @override + String get labelSignalLine => 'Signal line'; + + @override + String get labelSignalPeriod => 'Signal period'; + + @override + String get labelIncreasingBar => 'Increasing bar'; + + @override + String get labelDecreasingBar => 'Decreasing bar'; + + @override + String get labelBollingerBandsTop => 'Bollinger Bands top'; + + @override + String get labelBollingerBandsMedian => 'Bollinger Bands median'; + + @override + String get labelBollingerBandsBottom => 'Bollinger Bands bottom'; + + @override + String get labelChannelFill => 'Channel fill'; + + @override + String get labelFillColor => 'Fill color'; + + @override + String get labelStandardDeviations => 'Standard deviations'; + + @override + String get labelMovingAverageType => 'Moving Average Type'; + + @override + String get labelMALine => 'MA line'; + + @override + String get labelOffset => 'Offset'; + + @override + String get labelType => 'Type'; + + @override + String get labelSimple => 'Simple'; + + @override + String get labelExponential => 'Exponential'; + + @override + String get labelWeighted => 'Weighted'; + + @override + String get labelHull => 'Hull'; + + @override + String get labelZeroLag => 'Zero Lag'; + + @override + String get labelTimeSeries => 'Time Series'; + + @override + String get labelWellesWilder => 'Welles Wilder'; + + @override + String get labelVariable => 'Variable'; + + @override + String get labelTriangular => 'Triangular'; + + @override + String get label2Exponential => '2-Exponential'; + + @override + String get label3Exponential => '3-Exponential'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return 'Enter a value between $min and $max'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return 'Range $min - $max'; + } + + @override + String get labelDrawingTools => 'Drawing tools'; + + @override + String get labelTools => 'Tools'; + + @override + String get labelLine => 'Line'; + + @override + String get labelRay => 'Ray'; + + @override + String get informTapToSetFirstPoint => 'Tap to set first point'; + + @override + String get informTapToSetFinalPoint => 'Tap to set final point'; + + @override + String get informNoActiveDrawingTools => 'No active drawing tools.'; + + @override + String get actionAddDrawingTool => 'Add drawing tool'; + + @override + String get labelOf => 'of'; + + @override + String get labelDeleteAllDrawingTools => 'Delete all drawing tools'; + + @override + String get informDeleteAllDrawingTools => 'This will delete all active drawing tools.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_es.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_es.dart new file mode 100644 index 000000000..153c61778 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_es.dart @@ -0,0 +1,306 @@ +import 'deriv_mobile_chart_wrapper_localizations.dart'; + +/// The translations for Spanish Castilian (`es`). +class DerivMobileChartWrapperLocalizationsEs extends DerivMobileChartWrapperLocalizations { + DerivMobileChartWrapperLocalizationsEs([String locale = 'es']) : super(locale); + + @override + String get labelIndicators => 'Indicadores'; + + @override + String get labelActive => 'Activo'; + + @override + String get labelAll => 'Todos'; + + @override + String get labelMomentum => 'Impulso'; + + @override + String get labelVolatility => 'Volatilidad'; + + @override + String get labelMovingAverages => 'Medias móviles'; + + @override + String get labelMACD => 'MACD'; + + @override + String get labelRelativeStrengthIndex => 'Índice de fuerza relativa (RSI)'; + + @override + String get labelRSI => 'RSI'; + + @override + String get labelBollingerBands => 'Bandas de Bollinger (BB)'; + + @override + String get labelBB => 'BB'; + + @override + String get labelMovingAverage => 'Media móvil (MA)'; + + @override + String get labelMA => 'MA'; + + @override + String get infoMACD => 'El MACD es un indicador utilizado en el análisis técnico de las cotizaciones bursátiles. Se supone que revela los cambios en la fuerza, la dirección, el impulso y la duración de una tendencia en el precio de una acción.'; + + @override + String get infoRSI => 'El índice de fuerza relativa (RSI) fue publicado por J. Welles Wilder. El precio actual se normaliza como un porcentaje entre 0 y 100. El flutter_chart_id de este oscilador es engañoso porque no compara el instrumento en relación con otro instrumento o conjunto de instrumentos, sino que representa el precio actual en relación con otras piezas recientes dentro de la longitud de la ventana de retroceso seleccionada.'; + + @override + String get infoBB => 'Las Bandas de Bollinger (BB) pueden utilizarse para medir la altitud o la caída del precio en relación con operaciones anteriores.'; + + @override + String get infoMA => 'La media móvil (MA) ayuda a identificar la tendencia general del mercado filtrando las fluctuaciones de precios a corto plazo. Utilizando datos históricos, calcula el precio medio durante un periodo específico y traza una línea en el gráfico. Si la línea MA se mueve hacia arriba, es un indicador de una tendencia alcista, una tendencia bajista si se mueve hacia abajo. Una señal de compra se produce cuando el precio se mueve por encima de la línea MA.'; + + @override + String get infoMaximumActiveIndicatorsAdded => 'Ha añadido el número máximo de indicadores activos.'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return 'Añada $indicator'; + } + + @override + String get infoAddIndicator => 'Añadir indicador'; + + @override + String get labelDeleteAllIndicators => 'Borrar todos los indicadores'; + + @override + String get infoDeleteAllIndicators => 'Esto borrará todos los indicadores activos.'; + + @override + String infoResetIndicators(Object indicator) { + return 'Esto restablecerá el indicador $indicator a su configuración predeterminada.'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return 'Borrar el indicador $indicator'; + } + + @override + String labelResetIndicator(Object indicator) { + return 'Indicador Reset $indicator'; + } + + @override + String get infoDeleteIndicator => '¿Está seguro de que desea eliminar este indicador?'; + + @override + String get labelCancel => 'Cancelar'; + + @override + String get labelDelete => 'Borrar'; + + @override + String get labelDeleteAll => 'Borrar todo'; + + @override + String get infoUpto3indicatorsAllowed => 'Se permiten hasta 3 indicadores activos.'; + + @override + String get infoNoActiveIndicators => 'No hay indicadores activos.'; + + @override + String get labelReset => 'Reinicie'; + + @override + String get labelApply => 'Aplique'; + + @override + String get labelOK => 'OK'; + + @override + String get labelRSILine => 'Línea RSI'; + + @override + String get labelPeriod => 'Periodo'; + + @override + String get labelMinRange => 'Alcance mínimo'; + + @override + String get labelMaxRange => 'Alcance máximo'; + + @override + String get labelSource => 'Fuente'; + + @override + String get labelClose => 'Cerrar'; + + @override + String get labelOpen => 'Abra'; + + @override + String get labelHigh => 'Alta'; + + @override + String get labelLow => 'Bajo'; + + @override + String get labelHl2 => 'Hl/2'; + + @override + String get labelHlc3 => 'Hlc/3'; + + @override + String get labelHlcc4 => 'Hlcc/4'; + + @override + String get labelOhlc4 => 'Ohlc/4'; + + @override + String get labelShowZones => 'Mostrar zonas'; + + @override + String get labelOverbought => 'Sobrecompra'; + + @override + String get labelOversold => 'Sobreventa'; + + @override + String get labelMinSize => 'Tamaño mínimo'; + + @override + String get labelMaxSize => 'Tamaño máximo'; + + @override + String get labelRange => 'Gama'; + + @override + String get labelOverboughtLine => 'Línea de sobrecompra'; + + @override + String get labelOversoldLine => 'Línea de sobreventa'; + + @override + String get labelMACDLine => 'Línea MACD'; + + @override + String get labelFastMAPeriod => 'Periodo MA rápido'; + + @override + String get labelSlowMAPeriod => 'Periodo MA lento'; + + @override + String get labelSignalLine => 'Línea de señales'; + + @override + String get labelSignalPeriod => 'Periodo de señalización'; + + @override + String get labelIncreasingBar => 'Barra creciente'; + + @override + String get labelDecreasingBar => 'Barra decreciente'; + + @override + String get labelBollingerBandsTop => 'Parte superior de las bandas de Bollinger'; + + @override + String get labelBollingerBandsMedian => 'Mediana de las bandas de Bollinger'; + + @override + String get labelBollingerBandsBottom => 'Fondo de las Bandas de Bollinger'; + + @override + String get labelChannelFill => 'Relleno del canal'; + + @override + String get labelFillColor => 'Color de relleno'; + + @override + String get labelStandardDeviations => 'Desviaciones estándar'; + + @override + String get labelMovingAverageType => 'Tipo de media móvil'; + + @override + String get labelMALine => 'Línea MA'; + + @override + String get labelOffset => 'Desplazamiento'; + + @override + String get labelType => 'Tipo'; + + @override + String get labelSimple => 'Simple'; + + @override + String get labelExponential => 'Exponencial'; + + @override + String get labelWeighted => 'Ponderado'; + + @override + String get labelHull => 'Casco'; + + @override + String get labelZeroLag => 'Retraso cero'; + + @override + String get labelTimeSeries => 'Series temporales'; + + @override + String get labelWellesWilder => 'Welles Wilder'; + + @override + String get labelVariable => 'Variable'; + + @override + String get labelTriangular => 'Triangular'; + + @override + String get label2Exponential => '2-Exponencial'; + + @override + String get label3Exponential => '3-Exponencial'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return 'Introduzca un valor entre $min y $max'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return 'Gama $min - $max'; + } + + @override + String get labelDrawingTools => 'Herramientas de dibujo'; + + @override + String get labelTools => 'Herramientas'; + + @override + String get labelLine => 'Línea'; + + @override + String get labelRay => 'Rayo'; + + @override + String get informTapToSetFirstPoint => 'Pulsa para establecer el primer punto'; + + @override + String get informTapToSetFinalPoint => 'Pulsa para establecer el punto final'; + + @override + String get informNoActiveDrawingTools => 'No hay herramientas de dibujo activas.'; + + @override + String get actionAddDrawingTool => 'Añadir herramienta de dibujo'; + + @override + String get labelOf => 'de'; + + @override + String get labelDeleteAllDrawingTools => 'Eliminar todas las herramientas de dibujo'; + + @override + String get informDeleteAllDrawingTools => 'Esto borrará todas las herramientas de dibujo activas.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_fr.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_fr.dart new file mode 100644 index 000000000..7ae8afaf5 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_fr.dart @@ -0,0 +1,306 @@ +import 'deriv_mobile_chart_wrapper_localizations.dart'; + +/// The translations for French (`fr`). +class DerivMobileChartWrapperLocalizationsFr extends DerivMobileChartWrapperLocalizations { + DerivMobileChartWrapperLocalizationsFr([String locale = 'fr']) : super(locale); + + @override + String get labelIndicators => 'Indicateurs'; + + @override + String get labelActive => 'Actif'; + + @override + String get labelAll => 'Tous'; + + @override + String get labelMomentum => 'L\'élan'; + + @override + String get labelVolatility => 'Volatilité'; + + @override + String get labelMovingAverages => 'Moyennes mobiles'; + + @override + String get labelMACD => 'MACD'; + + @override + String get labelRelativeStrengthIndex => 'Indice de force relative (IFR)'; + + @override + String get labelRSI => 'RSI'; + + @override + String get labelBollingerBands => 'Bandes de Bollinger (BB)'; + + @override + String get labelBB => 'BB'; + + @override + String get labelMovingAverage => 'Moyenne mobile (MA)'; + + @override + String get labelMA => 'MA'; + + @override + String get infoMACD => 'Le MACD est un indicateur de trading utilisé dans l\'analyse technique des cours boursiers. Il est censé révéler les changements dans la force, la direction, l\'élan et la durée d\'une tendance dans le prix d\'une action.'; + + @override + String get infoRSI => 'L\'indice de force relative (RSI) a été publié par J. Welles Wilder. Le prix actuel est normalisé en pourcentage entre 0 et 100. Le flutter_chart_id de cet oscillateur est trompeur car il ne compare pas l\'instrument par rapport à un autre instrument ou à un ensemble d\'instruments, mais représente plutôt le prix actuel par rapport à d\'autres pièces récentes dans la longueur de la fenêtre d\'observation sélectionnée.'; + + @override + String get infoBB => 'Les bandes de Bollinger (BB) peuvent être utilisées pour mesurer la hausse ou la baisse du prix par rapport aux transactions précédentes.'; + + @override + String get infoMA => 'La moyenne mobile (MA) permet d\'identifier la tendance générale du marché en filtrant les fluctuations de prix à court terme. À l\'aide de données historiques, elle calcule le prix moyen sur une période donnée et trace une ligne sur le graphique. Si la ligne de la MA se déplace vers le haut, il s\'agit d\'un indicateur de tendance haussière, et si elle se déplace vers le bas, il s\'agit d\'un indicateur de tendance baissière. Un signal d\'achat se produit lorsque le prix passe au-dessus de la ligne MA.'; + + @override + String get infoMaximumActiveIndicatorsAdded => 'Vous avez ajouté le nombre maximum d\'indicateurs actifs.'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return 'Ajoutez $indicator'; + } + + @override + String get infoAddIndicator => 'Ajouter un indicateur'; + + @override + String get labelDeleteAllIndicators => 'Supprimer tous les indicateurs'; + + @override + String get infoDeleteAllIndicators => 'Cette opération supprime tous les indicateurs actifs.'; + + @override + String infoResetIndicators(Object indicator) { + return 'Cela réinitialisera les paramètres par défaut de l\'indicateur $indicator .'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return 'Indicateur de suppression $indicator'; + } + + @override + String labelResetIndicator(Object indicator) { + return 'Réinitialiser l\'indicateur $indicator'; + } + + @override + String get infoDeleteIndicator => 'Êtes-vous sûr de vouloir supprimer cet indicateur ?'; + + @override + String get labelCancel => 'Annuler'; + + @override + String get labelDelete => 'Supprimer'; + + @override + String get labelDeleteAll => 'Supprimer tout'; + + @override + String get infoUpto3indicatorsAllowed => 'Jusqu\'à 3 indicateurs actifs sont autorisés.'; + + @override + String get infoNoActiveIndicators => 'Aucun indicateur actif.'; + + @override + String get labelReset => 'Remise à zéro'; + + @override + String get labelApply => 'Appliquer'; + + @override + String get labelOK => 'OK'; + + @override + String get labelRSILine => 'Ligne RSI'; + + @override + String get labelPeriod => 'Période'; + + @override + String get labelMinRange => 'Plage minimale'; + + @override + String get labelMaxRange => 'Portée maximale'; + + @override + String get labelSource => 'Source'; + + @override + String get labelClose => 'Fermer'; + + @override + String get labelOpen => 'Ouvrir'; + + @override + String get labelHigh => 'Haut'; + + @override + String get labelLow => 'Faible'; + + @override + String get labelHl2 => 'Hl/2'; + + @override + String get labelHlc3 => 'Hlc/3'; + + @override + String get labelHlcc4 => 'Hlcc/4'; + + @override + String get labelOhlc4 => 'Ohlc/4'; + + @override + String get labelShowZones => 'Afficher les zones'; + + @override + String get labelOverbought => 'Surachat'; + + @override + String get labelOversold => 'Survente'; + + @override + String get labelMinSize => 'Taille minimale'; + + @override + String get labelMaxSize => 'Taille maximale'; + + @override + String get labelRange => 'Gamme'; + + @override + String get labelOverboughtLine => 'Ligne de surachat'; + + @override + String get labelOversoldLine => 'Ligne de survente'; + + @override + String get labelMACDLine => 'Ligne MACD'; + + @override + String get labelFastMAPeriod => 'Période d\'AM rapide'; + + @override + String get labelSlowMAPeriod => 'Période d\'AM lente'; + + @override + String get labelSignalLine => 'Ligne de signal'; + + @override + String get labelSignalPeriod => 'Période du signal'; + + @override + String get labelIncreasingBar => 'Augmentation de la barre'; + + @override + String get labelDecreasingBar => 'Barre décroissante'; + + @override + String get labelBollingerBandsTop => 'Sommet des bandes de Bollinger'; + + @override + String get labelBollingerBandsMedian => 'Bandes de Bollinger médianes'; + + @override + String get labelBollingerBandsBottom => 'Fond des bandes de Bollinger'; + + @override + String get labelChannelFill => 'Remplissage du canal'; + + @override + String get labelFillColor => 'Couleur de remplissage'; + + @override + String get labelStandardDeviations => 'Écarts types'; + + @override + String get labelMovingAverageType => 'Type de moyenne mobile'; + + @override + String get labelMALine => 'Ligne MA'; + + @override + String get labelOffset => 'Décalage'; + + @override + String get labelType => 'Type'; + + @override + String get labelSimple => 'Simple'; + + @override + String get labelExponential => 'Exponentiel'; + + @override + String get labelWeighted => 'Pondéré'; + + @override + String get labelHull => 'Coque'; + + @override + String get labelZeroLag => 'Zéro lag'; + + @override + String get labelTimeSeries => 'Séries chronologiques'; + + @override + String get labelWellesWilder => 'Welles Wilder'; + + @override + String get labelVariable => 'Variable'; + + @override + String get labelTriangular => 'Triangulaire'; + + @override + String get label2Exponential => '2-Exponentiel'; + + @override + String get label3Exponential => '3-Exponentiel'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return 'Saisissez une valeur comprise entre $min et $max'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return 'Gamme $min - $max'; + } + + @override + String get labelDrawingTools => 'Outils de dessin'; + + @override + String get labelTools => 'Outils'; + + @override + String get labelLine => 'Ligne'; + + @override + String get labelRay => 'Ray'; + + @override + String get informTapToSetFirstPoint => 'Touchez pour définir le premier point'; + + @override + String get informTapToSetFinalPoint => 'Touchez pour définir le point final'; + + @override + String get informNoActiveDrawingTools => 'Aucun outil de dessin actif.'; + + @override + String get actionAddDrawingTool => 'Ajouter un outil de dessin'; + + @override + String get labelOf => 'de'; + + @override + String get labelDeleteAllDrawingTools => 'Supprimer tous les outils de dessin'; + + @override + String get informDeleteAllDrawingTools => 'Cela supprimera tous les outils de dessin actifs.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_it.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_it.dart new file mode 100644 index 000000000..25b2c3f25 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_it.dart @@ -0,0 +1,306 @@ +import 'deriv_mobile_chart_wrapper_localizations.dart'; + +/// The translations for Italian (`it`). +class DerivMobileChartWrapperLocalizationsIt extends DerivMobileChartWrapperLocalizations { + DerivMobileChartWrapperLocalizationsIt([String locale = 'it']) : super(locale); + + @override + String get labelIndicators => 'Indicatori'; + + @override + String get labelActive => 'Attivo'; + + @override + String get labelAll => 'Tutti'; + + @override + String get labelMomentum => 'Momento'; + + @override + String get labelVolatility => 'Volatilità'; + + @override + String get labelMovingAverages => 'Medie mobili'; + + @override + String get labelMACD => 'MACD'; + + @override + String get labelRelativeStrengthIndex => 'Indice di forza relativa (RSI)'; + + @override + String get labelRSI => 'RSI'; + + @override + String get labelBollingerBands => 'Bande di Bollinger (BB)'; + + @override + String get labelBB => 'BB'; + + @override + String get labelMovingAverage => 'Media mobile (MA)'; + + @override + String get labelMA => 'MA'; + + @override + String get infoMACD => 'Il MACD è un indicatore di trading utilizzato nell\'analisi tecnica dei prezzi delle azioni. Si suppone che riveli i cambiamenti nella forza, nella direzione, nello slancio e nella durata di una tendenza nel prezzo di un\'azione.'; + + @override + String get infoRSI => 'L\'Indice di Forza Relativa (RSI) è stato pubblicato da J. Welles Wilder. Il prezzo corrente viene normalizzato come percentuale tra 0 e 100. Il flutter_chart_id di questo oscillatore è fuorviante perché non confronta lo strumento rispetto ad un altro strumento o ad un insieme di strumenti, ma rappresenta piuttosto il prezzo corrente rispetto ad altri pezzi recenti all\'interno della lunghezza della finestra di lookback selezionata.'; + + @override + String get infoBB => 'Le Bande di Bollinger (BB) possono essere utilizzate per misurare l\'altezza o la debolezza del prezzo rispetto alle contrattazioni precedenti.'; + + @override + String get infoMA => 'La media mobile (MA) aiuta a identificare la tendenza generale del mercato, filtrando le fluttuazioni di prezzo a breve termine. Utilizzando i dati storici, calcola il prezzo medio in un periodo specifico e traccia una linea sul grafico. Se la linea MA si muove verso l\'alto, è un indicatore di una tendenza al rialzo, di una tendenza al ribasso se si muove verso il basso. Un segnale di acquisto si verifica quando il prezzo si muove al di sopra della linea MA.'; + + @override + String get infoMaximumActiveIndicatorsAdded => 'Ha aggiunto il numero massimo di indicatori attivi.'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return 'Aggiungi $indicator'; + } + + @override + String get infoAddIndicator => 'Aggiungi indicatore'; + + @override + String get labelDeleteAllIndicators => 'Cancellare tutti gli indicatori'; + + @override + String get infoDeleteAllIndicators => 'Questo eliminerà tutti gli indicatori attivi.'; + + @override + String infoResetIndicators(Object indicator) { + return 'Ciò ripristinerà le impostazioni predefinite dell\'indicatore $indicator .'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return 'Cancellare l\'indicatore $indicator'; + } + + @override + String labelResetIndicator(Object indicator) { + return 'Indicatore Reset $indicator'; + } + + @override + String get infoDeleteIndicator => 'È sicuro di voler eliminare questo indicatore?'; + + @override + String get labelCancel => 'Annullamento'; + + @override + String get labelDelete => 'Cancellare'; + + @override + String get labelDeleteAll => 'Cancella tutto'; + + @override + String get infoUpto3indicatorsAllowed => 'Sono consentiti fino a 3 indicatori attivi.'; + + @override + String get infoNoActiveIndicators => 'Non ci sono indicatori attivi.'; + + @override + String get labelReset => 'Azzeramento'; + + @override + String get labelApply => 'Applicare'; + + @override + String get labelOK => 'OK'; + + @override + String get labelRSILine => 'Linea RSI'; + + @override + String get labelPeriod => 'Periodo'; + + @override + String get labelMinRange => 'Intervallo minimo'; + + @override + String get labelMaxRange => 'Gamma massima'; + + @override + String get labelSource => 'Fonte'; + + @override + String get labelClose => 'Chiudere'; + + @override + String get labelOpen => 'Aperto'; + + @override + String get labelHigh => 'Alto'; + + @override + String get labelLow => 'Basso'; + + @override + String get labelHl2 => 'Hl/2'; + + @override + String get labelHlc3 => 'Hlc/3'; + + @override + String get labelHlcc4 => 'Hlcc/4'; + + @override + String get labelOhlc4 => 'Ohlc/4'; + + @override + String get labelShowZones => 'Mostra le zone'; + + @override + String get labelOverbought => 'Sovracomprato'; + + @override + String get labelOversold => 'In ipervenduto'; + + @override + String get labelMinSize => 'Dimensione minima'; + + @override + String get labelMaxSize => 'Dimensione massima'; + + @override + String get labelRange => 'Gamma'; + + @override + String get labelOverboughtLine => 'Linea di ipercomprato'; + + @override + String get labelOversoldLine => 'Linea di ipervenduto'; + + @override + String get labelMACDLine => 'Linea MACD'; + + @override + String get labelFastMAPeriod => 'Periodo di MA veloce'; + + @override + String get labelSlowMAPeriod => 'Periodo di MA lento'; + + @override + String get labelSignalLine => 'Linea di segnale'; + + @override + String get labelSignalPeriod => 'Periodo del segnale'; + + @override + String get labelIncreasingBar => 'Barra di aumento'; + + @override + String get labelDecreasingBar => 'Barra decrescente'; + + @override + String get labelBollingerBandsTop => 'Bande di Bollinger top'; + + @override + String get labelBollingerBandsMedian => 'Bande di Bollinger mediane'; + + @override + String get labelBollingerBandsBottom => 'Fondo delle Bande di Bollinger'; + + @override + String get labelChannelFill => 'Riempimento del canale'; + + @override + String get labelFillColor => 'Colore di riempimento'; + + @override + String get labelStandardDeviations => 'Deviazioni standard'; + + @override + String get labelMovingAverageType => 'Tipo di media mobile'; + + @override + String get labelMALine => 'Linea MA'; + + @override + String get labelOffset => 'Offset'; + + @override + String get labelType => 'Tipo'; + + @override + String get labelSimple => 'Semplice'; + + @override + String get labelExponential => 'Esponenziale'; + + @override + String get labelWeighted => 'Ponderato'; + + @override + String get labelHull => 'Scafo'; + + @override + String get labelZeroLag => 'Zero Lag'; + + @override + String get labelTimeSeries => 'Serie temporale'; + + @override + String get labelWellesWilder => 'Welles Wilder'; + + @override + String get labelVariable => 'Variabile'; + + @override + String get labelTriangular => 'Triangolare'; + + @override + String get label2Exponential => '2-Esponenziale'; + + @override + String get label3Exponential => '3-Esponenziale'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return 'Inserisca un valore compreso tra $min e $max'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return 'Gamma $min - $max'; + } + + @override + String get labelDrawingTools => 'Strumenti di disegno'; + + @override + String get labelTools => 'Utensili'; + + @override + String get labelLine => 'Linea'; + + @override + String get labelRay => 'raggio'; + + @override + String get informTapToSetFirstPoint => 'Tocca per impostare il primo punto'; + + @override + String get informTapToSetFinalPoint => 'Tocca per impostare il punto finale'; + + @override + String get informNoActiveDrawingTools => 'Nessuno strumento di disegno attivo.'; + + @override + String get actionAddDrawingTool => 'Aggiungi strumento di disegno'; + + @override + String get labelOf => 'di'; + + @override + String get labelDeleteAllDrawingTools => 'Eliminare tutti gli strumenti di disegno'; + + @override + String get informDeleteAllDrawingTools => 'Questo eliminerà tutti gli strumenti di disegno attivi.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_km.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_km.dart new file mode 100644 index 000000000..8b1b60885 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_km.dart @@ -0,0 +1,306 @@ +import 'deriv_mobile_chart_wrapper_localizations.dart'; + +/// The translations for Khmer Central Khmer (`km`). +class DerivMobileChartWrapperLocalizationsKm extends DerivMobileChartWrapperLocalizations { + DerivMobileChartWrapperLocalizationsKm([String locale = 'km']) : super(locale); + + @override + String get labelIndicators => 'សូចនាករ'; + + @override + String get labelActive => 'សក្តិសម'; + + @override + String get labelAll => 'ទាំងអស់'; + + @override + String get labelMomentum => 'សន្ទុះ'; + + @override + String get labelVolatility => 'ប្រែប្រួល'; + + @override + String get labelMovingAverages => 'មធ្យមភាគចល័ត'; + + @override + String get labelMACD => 'MACD'; + + @override + String get labelRelativeStrengthIndex => 'សន្ទស្សន៍កម្លាំងទាក់ទង (RSI)'; + + @override + String get labelRSI => 'RSI'; + + @override + String get labelBollingerBands => 'ក្រុមតន្រ្តី Bollinger (BB)'; + + @override + String get labelBB => 'BB'; + + @override + String get labelMovingAverage => 'មធ្យមភាគចល័ត (MA)'; + + @override + String get labelMA => 'MA'; + + @override + String get infoMACD => 'MACD គឺជាសូចនាករជួញដូរដែលត្រូវបានប្រើក្នុងការវិភាគបច្ចេកទេសនៃតម្លៃភាគហ៊ុន។ វាត្រូវបានគេសន្មត់ថាដើម្បីបង្ហាញពីការផ្លាស់ប្តូរកម្លាំង ទិសដៅ សន្ទុះ និងរយៈពេលនៃនិន្នាការក្នុងតម្លៃភាគហ៊ុន។'; + + @override + String get infoRSI => 'សន្ទស្សន៍កម្លាំងទាក់ទង (RSI) ត្រូវបានបោះពុម្ពផ្សាយដោយ J. Welles Wilder ។ តម្លៃបច្ចុប្បន្នត្រូវបានធ្វើឱ្យមានលក្ខណៈធម្មតាជាភាគរយរវាង 0 និង 100 ។ flutter_chart_id នៃលំយោលនេះគឺជាការបំភាន់ពីព្រោះវាមិនប្រៀបធៀបឧបករណ៍ទាក់ទងទៅនឹងឧបករណ៍ផ្សេងទៀត ឬសំណុំនៃឧបករណ៍នោះទេ ប៉ុន្តែផ្ទុយទៅវិញតំណាងឱ្យតម្លៃបច្ចុប្បន្នទាក់ទងទៅនឹងបំណែកថ្មីៗផ្សេងទៀតនៅក្នុងប្រវែងបង្អួចមើលត្រឡប់មកវិញដែលបានជ្រើសរើស។'; + + @override + String get infoBB => 'ក្រុមតន្រ្តី Bollinger (BB) អាចត្រូវបានប្រើដើម្បីវាស់ស្ទង់កម្ពស់ ឬកម្រិតទាបនៃតម្លៃទាក់ទងទៅនឹងការជួញដូរពីមុន។'; + + @override + String get infoMA => 'មធ្យមភាគចល័ត (MA) ជួយកំណត់អត្តសញ្ញាណនិន្នាការទីផ្សារទូទៅដោយការច្រោះការប្រងប្រួលតម្លៃរយៈពេលខ្លី។ ដោយប្រើទិន្នន័យប្រវត្តិសាស្ត្រ វាគណនាតម្លៃជាមធ្យមក្នុងរយៈពេលជាក់លាក់មួយ ហើយគូសបន្ទាត់នៅលើតារាង។ ប្រសិនបើបន្ទាត់ MA ផ្លាស់ទីឡើងលើ វាជាសូចនាករនៃនិន្នាការឡើងលើ និន្នាការធ្លាក់ចុះ ប្រសិនបើវាផ្លាស់ទីចុះក្រោម។ សញ្ញាទិញកើតឡើងនៅពេលដែលតម្លៃផ្លាស់ទីខ្ពស់ជាងបន្ទាត់ MA ។'; + + @override + String get infoMaximumActiveIndicatorsAdded => 'អ្នកបានបន្ថែមចំនួនអតិបរមានៃសូចនាករសកម្ម។'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return 'បន្ថែម $indicator'; + } + + @override + String get infoAddIndicator => 'បន្ថែមសូចនាករ'; + + @override + String get labelDeleteAllIndicators => 'លុបសូចនាករទាំងអស់។'; + + @override + String get infoDeleteAllIndicators => 'នេះនឹងលុបសូចនាករសកម្មទាំងអស់។'; + + @override + String infoResetIndicators(Object indicator) { + return 'នេះនឹងកំណត់សូចនាករ $indicator ឡើងវិញទៅការកំណត់លំនាំដើមរបស់វា។'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return 'លុបសូចនាករ $indicator'; + } + + @override + String labelResetIndicator(Object indicator) { + return 'កំណត់សូចនាករ $indicator ឡើងវិញ'; + } + + @override + String get infoDeleteIndicator => 'តើអ្នកពិតជាចង់លុបសូចនាករនេះមែនទេ?'; + + @override + String get labelCancel => 'បោះបង់'; + + @override + String get labelDelete => 'លុប'; + + @override + String get labelDeleteAll => 'លុបទាំងអស់'; + + @override + String get infoUpto3indicatorsAllowed => 'អនុញ្ញាតឱ្យមានសូចនាករសកម្មរហូតដល់ 3 ។'; + + @override + String get infoNoActiveIndicators => 'មគ្គុទ្ទេសក៍សកម្មទេ។'; + + @override + String get labelReset => 'កំណត់ឡើងវិញ'; + + @override + String get labelApply => 'ដាក់ពាក្យ'; + + @override + String get labelOK => 'យល់ព្រម'; + + @override + String get labelRSILine => 'បន្ទាត់ RSI'; + + @override + String get labelPeriod => 'រយៈពេល'; + + @override + String get labelMinRange => 'ចន្លោះអប្បបរមា'; + + @override + String get labelMaxRange => 'ចន្លោះអតិបរមា'; + + @override + String get labelSource => 'ប្រភព'; + + @override + String get labelClose => 'បិទ'; + + @override + String get labelOpen => 'បើក'; + + @override + String get labelHigh => 'ខ្ពស់'; + + @override + String get labelLow => 'ទាប'; + + @override + String get labelHl2 => 'Hl/2'; + + @override + String get labelHlc3 => 'Hlc/3'; + + @override + String get labelHlcc4 => 'Hlcc/4'; + + @override + String get labelOhlc4 => 'Ohlc/4'; + + @override + String get labelShowZones => 'បង្ហាញតំបន់'; + + @override + String get labelOverbought => 'ទិញលើស'; + + @override + String get labelOversold => 'លក់លើស'; + + @override + String get labelMinSize => 'ទំហំអប្បបរមា'; + + @override + String get labelMaxSize => 'ទំហំអតិបរមា'; + + @override + String get labelRange => 'ចន្លោះ'; + + @override + String get labelOverboughtLine => 'បន្ទាត់ទិញលើស'; + + @override + String get labelOversoldLine => 'បន្ទាត់លក់លើស'; + + @override + String get labelMACDLine => 'បន្ទាត់ MACD'; + + @override + String get labelFastMAPeriod => 'រយៈពេល MA លឿន'; + + @override + String get labelSlowMAPeriod => 'រយៈពេល MA យឺត'; + + @override + String get labelSignalLine => 'បន្ទាត់សញ្ញា'; + + @override + String get labelSignalPeriod => 'រយៈពេលសញ្ញា'; + + @override + String get labelIncreasingBar => 'បារកើនឡើង'; + + @override + String get labelDecreasingBar => 'បារថយចុះ'; + + @override + String get labelBollingerBandsTop => 'កំពូលក្រុមតន្រ្តី Bollinger'; + + @override + String get labelBollingerBandsMedian => 'មធ្យមក្រុមតន្រ្តី Bollinger'; + + @override + String get labelBollingerBandsBottom => 'បាតក្រុមតន្រ្តី Bollinger'; + + @override + String get labelChannelFill => 'បំពេញឆានែល'; + + @override + String get labelFillColor => 'បំពេញពណ៌'; + + @override + String get labelStandardDeviations => 'គម្លាតស្តង់ដារ'; + + @override + String get labelMovingAverageType => 'ប្រភេទមធ្យមភាគចល័ត'; + + @override + String get labelMALine => 'បន្ទាត់ MA'; + + @override + String get labelOffset => 'ផ្លាស់ទី'; + + @override + String get labelType => 'ប្រភេទ'; + + @override + String get labelSimple => 'សាមញ្ញ'; + + @override + String get labelExponential => 'អិចស្ប៉ូណង់ស្យែល'; + + @override + String get labelWeighted => 'ទម្ងន់'; + + @override + String get labelHull => 'ស្រោមសំបុត្រ'; + + @override + String get labelZeroLag => 'យឺតសូន្យ'; + + @override + String get labelTimeSeries => 'ស៊េរីពេលវេលា'; + + @override + String get labelWellesWilder => 'Welles Wilder'; + + @override + String get labelVariable => 'អថេរ'; + + @override + String get labelTriangular => 'ត្រីកោណ'; + + @override + String get label2Exponential => 'អិចស្ប៉ូណង់ស្យែល 2'; + + @override + String get label3Exponential => 'អិចស្ប៉ូណង់ស្យែល 3'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return 'បញ្ចូលតម្លៃរវាង $min និង $max'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return 'ចន្លោះ $min - $max'; + } + + @override + String get labelDrawingTools => 'ឧបករណ៍គូរ'; + + @override + String get labelTools => 'ឧបករណ៍'; + + @override + String get labelLine => 'បន្ទាត់'; + + @override + String get labelRay => 'កាំរស្មី'; + + @override + String get informTapToSetFirstPoint => 'ប៉ះដើម្បីកំណត់ចំណុចដំបូង'; + + @override + String get informTapToSetFinalPoint => 'ប៉ះដើម្បីកំណត់ចំណុចចុងក្រោយ'; + + @override + String get informNoActiveDrawingTools => 'មិនមានឧបករណ៍គូរសកម្មទេ។'; + + @override + String get actionAddDrawingTool => 'បន្ថែមឧបករណ៍គូរ'; + + @override + String get labelOf => 'នៃ'; + + @override + String get labelDeleteAllDrawingTools => 'លុបឧបករណ៍គូរទាំងអស់។'; + + @override + String get informDeleteAllDrawingTools => 'នេះនឹងលុបឧបករណ៍គូរសកម្មទាំងអស់។'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_ko.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_ko.dart new file mode 100644 index 000000000..06edeed89 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_ko.dart @@ -0,0 +1,306 @@ +import 'deriv_mobile_chart_wrapper_localizations.dart'; + +/// The translations for Korean (`ko`). +class DerivMobileChartWrapperLocalizationsKo extends DerivMobileChartWrapperLocalizations { + DerivMobileChartWrapperLocalizationsKo([String locale = 'ko']) : super(locale); + + @override + String get labelIndicators => '지표'; + + @override + String get labelActive => '활성'; + + @override + String get labelAll => '모두'; + + @override + String get labelMomentum => '모멘텀'; + + @override + String get labelVolatility => '변동성'; + + @override + String get labelMovingAverages => '이동 평균'; + + @override + String get labelMACD => 'MACD'; + + @override + String get labelRelativeStrengthIndex => '상대 강도 지수(RSI)'; + + @override + String get labelRSI => 'RSI'; + + @override + String get labelBollingerBands => '볼린저 밴드(BB)'; + + @override + String get labelBB => 'BB'; + + @override + String get labelMovingAverage => '이동 평균(MA)'; + + @override + String get labelMA => 'MA'; + + @override + String get infoMACD => 'MACD는 주가의 기술적 분석에 사용되는 트레이딩 지표입니다. 이는 주식 가격 추세의 강도, 방향, 모멘텀, 지속 기간의 변화를 나타내는 지표입니다.'; + + @override + String get infoRSI => '상대강도지수(RSI)는 J. 웰즈 와일더가 발표했습니다. 현재 가격은 0에서 100 사이의 백분율로 정규화됩니다. 이 오실레이터의 flutter_chart_id는 다른 상품 또는 상품 세트와 비교하는 것이 아니라 선택한 룩백 윈도우 길이 내의 다른 최근 상품과 비교한 현재 가격을 나타내므로 오해의 소지가 있습니다.'; + + @override + String get infoBB => '볼린저 밴드(BB)는 이전 거래 대비 가격의 고점 또는 저점을 측정하는 데 사용할 수 있습니다.'; + + @override + String get infoMA => '이동평균(MA)은 단기적인 가격 변동을 걸러내어 전체 시장 추세를 파악하는 데 도움이 됩니다. 과거 데이터를 사용하여 특정 기간 동안의 평균 가격을 계산하고 차트에 선을 표시합니다. MA 선이 위로 이동하면 상승 추세, 아래로 이동하면 하락 추세의 지표입니다. 가격이 MA 선 위로 이동하면 매수 신호가 발생합니다.'; + + @override + String get infoMaximumActiveIndicatorsAdded => '활성 지표의 최대 개수를 추가했습니다.'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return '$indicator추가'; + } + + @override + String get infoAddIndicator => '표시기 추가'; + + @override + String get labelDeleteAllIndicators => '모든 지표 삭제'; + + @override + String get infoDeleteAllIndicators => '이렇게 하면 모든 활성 표시기가 삭제됩니다.'; + + @override + String infoResetIndicators(Object indicator) { + return '이렇게 하면 $indicator 표시기가 기본 설정으로 재설정됩니다.'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return '$indicator 표시기 삭제'; + } + + @override + String labelResetIndicator(Object indicator) { + return '$indicator 표시기 재설정'; + } + + @override + String get infoDeleteIndicator => '이 표시기를 삭제하시겠습니까?'; + + @override + String get labelCancel => '취소'; + + @override + String get labelDelete => '삭제'; + + @override + String get labelDeleteAll => '모두 삭제'; + + @override + String get infoUpto3indicatorsAllowed => '활성 표시기는 최대 3개까지 허용됩니다.'; + + @override + String get infoNoActiveIndicators => '활성 표시기가 없습니다.'; + + @override + String get labelReset => '초기화'; + + @override + String get labelApply => '신청하기'; + + @override + String get labelOK => '확인'; + + @override + String get labelRSILine => 'RSI 라인'; + + @override + String get labelPeriod => '기간'; + + @override + String get labelMinRange => '최소 범위'; + + @override + String get labelMaxRange => '최대 범위'; + + @override + String get labelSource => '출처'; + + @override + String get labelClose => '닫기'; + + @override + String get labelOpen => '열기'; + + @override + String get labelHigh => '높음'; + + @override + String get labelLow => '낮음'; + + @override + String get labelHl2 => 'Hl/2'; + + @override + String get labelHlc3 => 'Hlc/3'; + + @override + String get labelHlcc4 => 'Hlcc/4'; + + @override + String get labelOhlc4 => 'Ohlc/4'; + + @override + String get labelShowZones => '영역 표시'; + + @override + String get labelOverbought => '과매수'; + + @override + String get labelOversold => '과매도'; + + @override + String get labelMinSize => '최소 크기'; + + @override + String get labelMaxSize => '최대 크기'; + + @override + String get labelRange => '범위'; + + @override + String get labelOverboughtLine => '과매수 라인'; + + @override + String get labelOversoldLine => '과매도 라인'; + + @override + String get labelMACDLine => 'MACD 라인'; + + @override + String get labelFastMAPeriod => '빠른 MA 기간'; + + @override + String get labelSlowMAPeriod => '느린 MA 기간'; + + @override + String get labelSignalLine => '신호 라인'; + + @override + String get labelSignalPeriod => '신호 기간'; + + @override + String get labelIncreasingBar => '바 증가'; + + @override + String get labelDecreasingBar => '바 감소'; + + @override + String get labelBollingerBandsTop => '볼린저 밴드 상단'; + + @override + String get labelBollingerBandsMedian => '볼린저 밴드 중앙값'; + + @override + String get labelBollingerBandsBottom => '볼린저 밴드 하단'; + + @override + String get labelChannelFill => '채널 채우기'; + + @override + String get labelFillColor => '채우기 색상'; + + @override + String get labelStandardDeviations => '표준 편차'; + + @override + String get labelMovingAverageType => '이동 평균 유형'; + + @override + String get labelMALine => 'MA 라인'; + + @override + String get labelOffset => '오프셋'; + + @override + String get labelType => '유형'; + + @override + String get labelSimple => 'Simple'; + + @override + String get labelExponential => '지수'; + + @override + String get labelWeighted => '가중치'; + + @override + String get labelHull => 'Hull'; + + @override + String get labelZeroLag => '제로 랙'; + + @override + String get labelTimeSeries => '시계열'; + + @override + String get labelWellesWilder => '웰즈 와일더'; + + @override + String get labelVariable => '변수'; + + @override + String get labelTriangular => '삼각형'; + + @override + String get label2Exponential => '2-지수'; + + @override + String get label3Exponential => '3-지수'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return '$min ~ $max사이의 값을 입력합니다.'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return '범위 $min - $max'; + } + + @override + String get labelDrawingTools => '드로잉 툴'; + + @override + String get labelTools => '도구'; + + @override + String get labelLine => '선'; + + @override + String get labelRay => '광선'; + + @override + String get informTapToSetFirstPoint => '첫 번째 포인트를 설정하려면 누릅니다.'; + + @override + String get informTapToSetFinalPoint => '탭하여 최종 지점을 설정합니다.'; + + @override + String get informNoActiveDrawingTools => '활성 드로잉 도구가 없습니다.'; + + @override + String get actionAddDrawingTool => '드로잉 도구 추가'; + + @override + String get labelOf => '의'; + + @override + String get labelDeleteAllDrawingTools => '모든 그리기 도구 삭제'; + + @override + String get informDeleteAllDrawingTools => '이렇게 하면 활성 드로잉 도구가 모두 삭제됩니다.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_mn.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_mn.dart new file mode 100644 index 000000000..8ba9f7963 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_mn.dart @@ -0,0 +1,306 @@ +import 'deriv_mobile_chart_wrapper_localizations.dart'; + +/// The translations for Mongolian (`mn`). +class DerivMobileChartWrapperLocalizationsMn extends DerivMobileChartWrapperLocalizations { + DerivMobileChartWrapperLocalizationsMn([String locale = 'mn']) : super(locale); + + @override + String get labelIndicators => 'Indicators'; + + @override + String get labelActive => 'Active'; + + @override + String get labelAll => 'All'; + + @override + String get labelMomentum => 'Momentum'; + + @override + String get labelVolatility => 'Volatility'; + + @override + String get labelMovingAverages => 'Moving averages'; + + @override + String get labelMACD => 'MACD'; + + @override + String get labelRelativeStrengthIndex => 'Relative Strength Index (RSI)'; + + @override + String get labelRSI => 'RSI'; + + @override + String get labelBollingerBands => 'Bollinger Bands (BB)'; + + @override + String get labelBB => 'BB'; + + @override + String get labelMovingAverage => 'Moving Average (MA)'; + + @override + String get labelMA => 'MA'; + + @override + String get infoMACD => 'MACD is a trading indicator used in technical analysis of stock prices. It is supposed to reveal changes in the strength, direction, momentum, and duration of a trend in a stock\'s price.'; + + @override + String get infoRSI => 'The Relative Strength Index (RSI) was published by J. Welles Wilder. The current price is normalized as a percentage between 0 and 100. The flutter_chart_id of this oscillator is misleading because it does not compare the instrument relative to another instrument or set of instruments, but rather represents the current price relative to other recent pieces within the selected lookback window length.'; + + @override + String get infoBB => 'Bollinger Bands (BB) can be used to measure the highness or lowness of the price relative to previous trades.'; + + @override + String get infoMA => 'The Moving Average (MA) helps to identify the overall market trend by filtering out short-term price fluctuations. Using historical data, it calculates the average price over a specific period and plots a line on the chart. If the MA line moves upwards, it’s an indicator of an uptrend, a downtrend if it moves downwards. A buy signal occurs when the price moves above the MA line.'; + + @override + String get infoMaximumActiveIndicatorsAdded => 'You\'ve added the maximum number of active indicators.'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return 'Add $indicator'; + } + + @override + String get infoAddIndicator => 'Add indicator'; + + @override + String get labelDeleteAllIndicators => 'Delete all indicators'; + + @override + String get infoDeleteAllIndicators => 'This will delete all active indicators.'; + + @override + String infoResetIndicators(Object indicator) { + return 'This will reset the $indicator indicator to its default settings.'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return 'Delete $indicator indicator'; + } + + @override + String labelResetIndicator(Object indicator) { + return 'Reset $indicator indicator'; + } + + @override + String get infoDeleteIndicator => 'Are you sure you want to delete this indicator?'; + + @override + String get labelCancel => 'Cancel'; + + @override + String get labelDelete => 'Delete'; + + @override + String get labelDeleteAll => 'Delete All'; + + @override + String get infoUpto3indicatorsAllowed => 'Up to 3 active indicators allowed.'; + + @override + String get infoNoActiveIndicators => 'No active indicators.'; + + @override + String get labelReset => 'Reset'; + + @override + String get labelApply => 'Apply'; + + @override + String get labelOK => 'OK'; + + @override + String get labelRSILine => 'RSI line'; + + @override + String get labelPeriod => 'Period'; + + @override + String get labelMinRange => 'Min range'; + + @override + String get labelMaxRange => 'Max range'; + + @override + String get labelSource => 'Source'; + + @override + String get labelClose => 'Close'; + + @override + String get labelOpen => 'Open'; + + @override + String get labelHigh => 'High'; + + @override + String get labelLow => 'Low'; + + @override + String get labelHl2 => 'Hl/2'; + + @override + String get labelHlc3 => 'Hlc/3'; + + @override + String get labelHlcc4 => 'Hlcc/4'; + + @override + String get labelOhlc4 => 'Ohlc/4'; + + @override + String get labelShowZones => 'Show Zones'; + + @override + String get labelOverbought => 'Overbought'; + + @override + String get labelOversold => 'Oversold'; + + @override + String get labelMinSize => 'Min size'; + + @override + String get labelMaxSize => 'Max size'; + + @override + String get labelRange => 'Range'; + + @override + String get labelOverboughtLine => 'Overbought line'; + + @override + String get labelOversoldLine => 'Oversold line'; + + @override + String get labelMACDLine => 'MACD line'; + + @override + String get labelFastMAPeriod => 'Fast MA period'; + + @override + String get labelSlowMAPeriod => 'Slow MA period'; + + @override + String get labelSignalLine => 'Signal line'; + + @override + String get labelSignalPeriod => 'Signal period'; + + @override + String get labelIncreasingBar => 'Increasing bar'; + + @override + String get labelDecreasingBar => 'Decreasing bar'; + + @override + String get labelBollingerBandsTop => 'Bollinger Bands top'; + + @override + String get labelBollingerBandsMedian => 'Bollinger Bands median'; + + @override + String get labelBollingerBandsBottom => 'Bollinger Bands bottom'; + + @override + String get labelChannelFill => 'Channel fill'; + + @override + String get labelFillColor => 'Fill color'; + + @override + String get labelStandardDeviations => 'Standard deviations'; + + @override + String get labelMovingAverageType => 'Moving Average Type'; + + @override + String get labelMALine => 'MA line'; + + @override + String get labelOffset => 'Offset'; + + @override + String get labelType => 'Type'; + + @override + String get labelSimple => 'Simple'; + + @override + String get labelExponential => 'Exponential'; + + @override + String get labelWeighted => 'Weighted'; + + @override + String get labelHull => 'Hull'; + + @override + String get labelZeroLag => 'Zero Lag'; + + @override + String get labelTimeSeries => 'Time Series'; + + @override + String get labelWellesWilder => 'Welles Wilder'; + + @override + String get labelVariable => 'Variable'; + + @override + String get labelTriangular => 'Triangular'; + + @override + String get label2Exponential => '2-Exponential'; + + @override + String get label3Exponential => '3-Exponential'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return 'Enter a value between $min and $max'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return 'Range $min - $max'; + } + + @override + String get labelDrawingTools => 'Drawing tools'; + + @override + String get labelTools => 'Tools'; + + @override + String get labelLine => 'Line'; + + @override + String get labelRay => 'Ray'; + + @override + String get informTapToSetFirstPoint => 'Tap to set first point'; + + @override + String get informTapToSetFinalPoint => 'Tap to set final point'; + + @override + String get informNoActiveDrawingTools => 'No active drawing tools.'; + + @override + String get actionAddDrawingTool => 'Add drawing tool'; + + @override + String get labelOf => '-ийн'; + + @override + String get labelDeleteAllDrawingTools => 'Бүх зургийн хэрэгслийг устгах'; + + @override + String get informDeleteAllDrawingTools => 'Энэ нь бүх идэвхтэй зургийн хэрэгслийг устгах болно.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_pl.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_pl.dart new file mode 100644 index 000000000..7f299b4f8 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_pl.dart @@ -0,0 +1,306 @@ +import 'deriv_mobile_chart_wrapper_localizations.dart'; + +/// The translations for Polish (`pl`). +class DerivMobileChartWrapperLocalizationsPl extends DerivMobileChartWrapperLocalizations { + DerivMobileChartWrapperLocalizationsPl([String locale = 'pl']) : super(locale); + + @override + String get labelIndicators => 'Wskaźniki'; + + @override + String get labelActive => 'Aktywny'; + + @override + String get labelAll => 'Wszystko'; + + @override + String get labelMomentum => 'Pęd'; + + @override + String get labelVolatility => 'Zmienność'; + + @override + String get labelMovingAverages => 'Średnie kroczące'; + + @override + String get labelMACD => 'MACD'; + + @override + String get labelRelativeStrengthIndex => 'Względny wskaźnik siły (RSI)'; + + @override + String get labelRSI => 'RSI'; + + @override + String get labelBollingerBands => 'Zespoły Bollingera (BB)'; + + @override + String get labelBB => 'BB'; + + @override + String get labelMovingAverage => 'Średnia ruchoma (MA)'; + + @override + String get labelMA => 'MA'; + + @override + String get infoMACD => 'MACD jest wskaźnikiem handlowym wykorzystywanym w technicznej analizie cen akcji. Ma ujawniać zmiany w sile, kierunku, pędzie i czasie trwania trendu w cenie akcji.'; + + @override + String get infoRSI => 'Relative Strength Index (RSI) został opublikowany przez J. Wellesa Wildera. Aktualna cena jest znormalizowana jako procent od 0 do 100. Flutter_chart_id tego oscylatora jest mylący, ponieważ nie porównuje instrumentu względem innego instrumentu lub zestawu instrumentów, ale reprezentuje aktualną cenę w stosunku do innych ostatnich elementów w wybranej długości okna przeglądającego.'; + + @override + String get infoBB => 'Pasma Bollingera (BB) można wykorzystać do pomiaru wysokości lub niskiej ceny w stosunku do poprzednich transakcji.'; + + @override + String get infoMA => 'Średnia krocząca (MA) pomaga zidentyfikować ogólny trend rynkowy poprzez filtrowanie krótkoterminowych wahań cen. Korzystając z danych historycznych, oblicza średnią cenę w określonym okresie i wykreśla linię na wykresie. Jeśli linia MA porusza się w górę, jest to wskaźnik trendu wzrostowego, trendu spadkowego, jeśli porusza się w dół. Sygnał zakupu pojawia się, gdy cena przesuwa się powyżej linii MA.'; + + @override + String get infoMaximumActiveIndicatorsAdded => 'Dodano maksymalną liczbę aktywnych wskaźników.'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return 'Dodaj $indicator'; + } + + @override + String get infoAddIndicator => 'Dodaj wskaźnik'; + + @override + String get labelDeleteAllIndicators => 'Usuń wszystkie wskaźniki'; + + @override + String get infoDeleteAllIndicators => 'Spowoduje to usunięcie wszystkich aktywnych wskaźników.'; + + @override + String infoResetIndicators(Object indicator) { + return 'Spowoduje to zresetowanie wskaźnika $indicator do ustawień domyślnych.'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return 'Usuń wskaźnik $indicator'; + } + + @override + String labelResetIndicator(Object indicator) { + return 'Zresetuj wskaźnik $indicator'; + } + + @override + String get infoDeleteIndicator => 'Czy na pewno chcesz usunąć ten wskaźnik?'; + + @override + String get labelCancel => 'Anuluj'; + + @override + String get labelDelete => 'Usuń'; + + @override + String get labelDeleteAll => 'Usuń wszystko'; + + @override + String get infoUpto3indicatorsAllowed => 'Dozwolone jest do 3 aktywnych wskaźników.'; + + @override + String get infoNoActiveIndicators => 'Brak aktywnych wskaźników.'; + + @override + String get labelReset => 'Resetuj'; + + @override + String get labelApply => 'Zastosuj'; + + @override + String get labelOK => 'DOBRZE'; + + @override + String get labelRSILine => 'Linia RSI'; + + @override + String get labelPeriod => 'Kropka'; + + @override + String get labelMinRange => 'Minimalny zasięg'; + + @override + String get labelMaxRange => 'Maksymalny zasięg'; + + @override + String get labelSource => 'Źródło'; + + @override + String get labelClose => 'Zamknij'; + + @override + String get labelOpen => 'Otwórz'; + + @override + String get labelHigh => 'Wysoki'; + + @override + String get labelLow => 'Niski'; + + @override + String get labelHl2 => 'Hl/2'; + + @override + String get labelHlc3 => 'HLC/3'; + + @override + String get labelHlcc4 => 'Hlcc/4'; + + @override + String get labelOhlc4 => 'Ohlc/4'; + + @override + String get labelShowZones => 'Pokaż strefy'; + + @override + String get labelOverbought => 'Wykupione'; + + @override + String get labelOversold => 'Wyprzedane'; + + @override + String get labelMinSize => 'Minimalny rozmiar'; + + @override + String get labelMaxSize => 'Maksymalny rozmiar'; + + @override + String get labelRange => 'Zasięg'; + + @override + String get labelOverboughtLine => 'Linia wykupiona'; + + @override + String get labelOversoldLine => 'Linia wyprzedana'; + + @override + String get labelMACDLine => 'Linia MACD'; + + @override + String get labelFastMAPeriod => 'Szybki okres MA'; + + @override + String get labelSlowMAPeriod => 'Powolny okres MA'; + + @override + String get labelSignalLine => 'Linia sygnałowa'; + + @override + String get labelSignalPeriod => 'Okres sygnału'; + + @override + String get labelIncreasingBar => 'Zwiększanie paska'; + + @override + String get labelDecreasingBar => 'Malejący pasek'; + + @override + String get labelBollingerBandsTop => 'Bollinger Bands Top'; + + @override + String get labelBollingerBandsMedian => 'Mediana pasm Bollingera'; + + @override + String get labelBollingerBandsBottom => 'Dolne paski Bollingera'; + + @override + String get labelChannelFill => 'Wypełnienie kanału'; + + @override + String get labelFillColor => 'Kolor wypełnienia'; + + @override + String get labelStandardDeviations => 'Odchylenia standardowe'; + + @override + String get labelMovingAverageType => 'Typ średniej ruchomej'; + + @override + String get labelMALine => 'Linia MA'; + + @override + String get labelOffset => 'Przesunięcie'; + + @override + String get labelType => 'Typ'; + + @override + String get labelSimple => 'Prosty'; + + @override + String get labelExponential => 'wykładniczy'; + + @override + String get labelWeighted => 'ważony'; + + @override + String get labelHull => 'Kadłub'; + + @override + String get labelZeroLag => 'Zero opóźnienia'; + + @override + String get labelTimeSeries => 'Szereg czasowy'; + + @override + String get labelWellesWilder => 'Welles Wilder'; + + @override + String get labelVariable => 'Zmienna'; + + @override + String get labelTriangular => 'Trójkątne'; + + @override + String get label2Exponential => '2-wykładniczy'; + + @override + String get label3Exponential => '3-wykładniczy'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return 'Wprowadź wartość między $min i $max'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return 'Zakres $min - $max'; + } + + @override + String get labelDrawingTools => 'Narzędzia do rysowania'; + + @override + String get labelTools => 'Narzędzia'; + + @override + String get labelLine => 'Linia'; + + @override + String get labelRay => 'Ray'; + + @override + String get informTapToSetFirstPoint => 'Stuknij, aby ustawić pierwszy punkt'; + + @override + String get informTapToSetFinalPoint => 'Stuknij, aby ustawić punkt końcowy'; + + @override + String get informNoActiveDrawingTools => 'Brak aktywnych narzędzi do rysowania.'; + + @override + String get actionAddDrawingTool => 'Dodaj narzędzie do rysowania'; + + @override + String get labelOf => 'z'; + + @override + String get labelDeleteAllDrawingTools => 'Usuń wszystkie narzędzia do rysowania'; + + @override + String get informDeleteAllDrawingTools => 'Spowoduje to usunięcie wszystkich aktywnych narzędzi do rysowania.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_pt.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_pt.dart new file mode 100644 index 000000000..95438e61a --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_pt.dart @@ -0,0 +1,306 @@ +import 'deriv_mobile_chart_wrapper_localizations.dart'; + +/// The translations for Portuguese (`pt`). +class DerivMobileChartWrapperLocalizationsPt extends DerivMobileChartWrapperLocalizations { + DerivMobileChartWrapperLocalizationsPt([String locale = 'pt']) : super(locale); + + @override + String get labelIndicators => 'Indicadores'; + + @override + String get labelActive => 'Ativo'; + + @override + String get labelAll => 'Todos'; + + @override + String get labelMomentum => 'Momentum'; + + @override + String get labelVolatility => 'Volatilidade'; + + @override + String get labelMovingAverages => 'Médias móveis'; + + @override + String get labelMACD => 'MACD'; + + @override + String get labelRelativeStrengthIndex => 'Índice de Força Relativa (RSI)'; + + @override + String get labelRSI => 'RSI'; + + @override + String get labelBollingerBands => 'Bandas de Bollinger (BB)'; + + @override + String get labelBB => 'BB'; + + @override + String get labelMovingAverage => 'Média móvel (MA)'; + + @override + String get labelMA => 'MA'; + + @override + String get infoMACD => 'A Convergência/Divergência das Médias Móveis (MACD) é um indicador de negociação utilizado na análise técnica dos preços das ações. É suposto revelar alterações na força, direção, momentum e duração de uma tendência no preço de uma ação.'; + + @override + String get infoRSI => 'O Índice de Força Relativa (RSI) foi publicado por J. Welles Wilder. O preço atual é normalizado como uma percentagem entre 0 e 100. O flutter_chart_id deste oscilador é enganador porque não compara o instrumento com outro instrumento ou conjunto de instrumentos, mas sim representa o preço atual em relação a outras peças recentes dentro do comprimento da janela de lookback selecionada.'; + + @override + String get infoBB => 'As Bandas de Bollinger (BB) podem ser usadas para medir se o preço está alto ou baixo em relação a negociações anteriores.'; + + @override + String get infoMA => 'A média móvel (MA) ajuda a identificar a tendência geral do mercado ao filtrar as flutuações de preços a curto prazo. Utilizando o histórico de dados, calcula o preço médio durante um período específico e traça uma linha no gráfico. Se a linha MA se mover para cima, indica uma tendência ascendente; se se mover para baixo, indica uma tendência descendente. Um sinal de compra ocorre quando o preço ultrapassa a linha MA.'; + + @override + String get infoMaximumActiveIndicatorsAdded => 'Adicionou o número máximo de indicadores ativos.'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return 'Adicionar $indicator'; + } + + @override + String get infoAddIndicator => 'Adicionar indicador'; + + @override + String get labelDeleteAllIndicators => 'Eliminar todos os indicadores'; + + @override + String get infoDeleteAllIndicators => 'Esta ação elimina todos os indicadores ativos.'; + + @override + String infoResetIndicators(Object indicator) { + return 'Esta ação irá repor o indicador $indicator para as suas definições predefinidas.'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return 'Eliminar o indicador $indicator'; + } + + @override + String labelResetIndicator(Object indicator) { + return 'Repor o indicador $indicator'; + } + + @override + String get infoDeleteIndicator => 'Tem a certeza de que pretende eliminar este indicador?'; + + @override + String get labelCancel => 'Cancelar'; + + @override + String get labelDelete => 'Eliminar'; + + @override + String get labelDeleteAll => 'Eliminar tudo'; + + @override + String get infoUpto3indicatorsAllowed => 'São permitidos até 3 indicadores ativos.'; + + @override + String get infoNoActiveIndicators => 'Não existem indicadores ativos.'; + + @override + String get labelReset => 'Reiniciação'; + + @override + String get labelApply => 'Aplicar'; + + @override + String get labelOK => 'OK'; + + @override + String get labelRSILine => 'Linha do RSI'; + + @override + String get labelPeriod => 'Período'; + + @override + String get labelMinRange => 'Intervalo mín.'; + + @override + String get labelMaxRange => 'Intervalo máx.'; + + @override + String get labelSource => 'Fonte'; + + @override + String get labelClose => 'Fechado'; + + @override + String get labelOpen => 'Aberto'; + + @override + String get labelHigh => 'High'; + + @override + String get labelLow => 'Low'; + + @override + String get labelHl2 => 'Hl/2'; + + @override + String get labelHlc3 => 'Hlc/3'; + + @override + String get labelHlcc4 => 'Hlcc/4'; + + @override + String get labelOhlc4 => 'Ohlc/4'; + + @override + String get labelShowZones => 'Mostrar zonas'; + + @override + String get labelOverbought => 'Sobrecompra'; + + @override + String get labelOversold => 'Sobrevenda'; + + @override + String get labelMinSize => 'Tamanho mín.'; + + @override + String get labelMaxSize => 'Tamanho máx.'; + + @override + String get labelRange => 'Intervalo'; + + @override + String get labelOverboughtLine => 'Linha de sobrecompra'; + + @override + String get labelOversoldLine => 'Linha de sobrevenda'; + + @override + String get labelMACDLine => 'Linha do MACD'; + + @override + String get labelFastMAPeriod => 'Período da média móvel rápida'; + + @override + String get labelSlowMAPeriod => 'Período da média móvel lenta'; + + @override + String get labelSignalLine => 'Linha de sinal'; + + @override + String get labelSignalPeriod => 'Período de sinal'; + + @override + String get labelIncreasingBar => 'Barra crescente'; + + @override + String get labelDecreasingBar => 'Barra decrescente'; + + @override + String get labelBollingerBandsTop => 'Limite superior das Bandas de Bollinger'; + + @override + String get labelBollingerBandsMedian => 'Limite médio das Bandas de Bollinger'; + + @override + String get labelBollingerBandsBottom => 'Limite inferior das Bandas de Bollinger'; + + @override + String get labelChannelFill => 'Preenchimento do canal'; + + @override + String get labelFillColor => 'Cor de preenchimento'; + + @override + String get labelStandardDeviations => 'Desvios padrão'; + + @override + String get labelMovingAverageType => 'Tipo de média móvel'; + + @override + String get labelMALine => 'Linha da MA'; + + @override + String get labelOffset => 'Deslocamento'; + + @override + String get labelType => 'Tipo'; + + @override + String get labelSimple => 'Simples'; + + @override + String get labelExponential => 'Exponencial'; + + @override + String get labelWeighted => 'Ponderada'; + + @override + String get labelHull => 'Hull'; + + @override + String get labelZeroLag => 'Zero Lag (sem atraso)'; + + @override + String get labelTimeSeries => 'Séries temporais'; + + @override + String get labelWellesWilder => 'Welles Wilder'; + + @override + String get labelVariable => 'Variável'; + + @override + String get labelTriangular => 'Triangular'; + + @override + String get label2Exponential => '2-Exponencial'; + + @override + String get label3Exponential => '3-Exponencial'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return 'Introduza um valor entre $min e $max'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return 'Intervalo $min - $max'; + } + + @override + String get labelDrawingTools => 'Ferramentas de desenho'; + + @override + String get labelTools => 'Ferramentas'; + + @override + String get labelLine => 'Linha'; + + @override + String get labelRay => 'Linha de raio'; + + @override + String get informTapToSetFirstPoint => 'Toque para definir o primeiro ponto'; + + @override + String get informTapToSetFinalPoint => 'Toque para definir o ponto final'; + + @override + String get informNoActiveDrawingTools => 'Não existem ferramentas de desenho ativas.'; + + @override + String get actionAddDrawingTool => 'Adicionar ferramenta de desenho'; + + @override + String get labelOf => 'de'; + + @override + String get labelDeleteAllDrawingTools => 'Eliminar todas as ferramentas de desenho'; + + @override + String get informDeleteAllDrawingTools => 'Isto irá eliminar todas as ferramentas de desenho activas.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_ru.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_ru.dart new file mode 100644 index 000000000..8021d4d40 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_ru.dart @@ -0,0 +1,306 @@ +import 'deriv_mobile_chart_wrapper_localizations.dart'; + +/// The translations for Russian (`ru`). +class DerivMobileChartWrapperLocalizationsRu extends DerivMobileChartWrapperLocalizations { + DerivMobileChartWrapperLocalizationsRu([String locale = 'ru']) : super(locale); + + @override + String get labelIndicators => 'Индикаторы'; + + @override + String get labelActive => 'Активный'; + + @override + String get labelAll => 'Все'; + + @override + String get labelMomentum => 'Momentum'; + + @override + String get labelVolatility => 'Волатильность'; + + @override + String get labelMovingAverages => 'Скользящие средние'; + + @override + String get labelMACD => 'MACD'; + + @override + String get labelRelativeStrengthIndex => 'Индекс относительной силы (RSI)'; + + @override + String get labelRSI => 'RSI'; + + @override + String get labelBollingerBands => 'Полосы Боллинджера (BB)'; + + @override + String get labelBB => 'BB'; + + @override + String get labelMovingAverage => 'Скользящая средняя (MA)'; + + @override + String get labelMA => 'MA'; + + @override + String get infoMACD => 'MACD - это торговый индикатор, используемый в техническом анализе цен на акции. Он призван выявлять изменения в силе, направлении, импульсе и продолжительности тренда в цене акции.'; + + @override + String get infoRSI => 'Индекс относительной силы (RSI) был опубликован Дж. Уэллсом Уайлдером. Текущая цена нормируется в процентах от 0 до 100. Flutter_chart_id этого осциллятора вводит в заблуждение, поскольку он не сравнивает инструмент относительно другого инструмента или набора инструментов, а скорее представляет текущую цену относительно других недавних фигур в пределах выбранной длины окна обратного просмотра.'; + + @override + String get infoBB => 'Полосы Боллинджера (Bollinger Bands, BB) можно использовать для измерения высоты или низкости цены по отношению к предыдущим сделкам.'; + + @override + String get infoMA => 'Скользящая средняя (MA) помогает определить общую тенденцию рынка, отсеивая краткосрочные колебания цен. Используя исторические данные, она рассчитывает среднюю цену за определенный период и наносит линию на график. Если линия MA движется вверх, это индикатор восходящего тренда, если вниз - нисходящего. Сигнал к покупке возникает, когда цена движется выше линии MA.'; + + @override + String get infoMaximumActiveIndicatorsAdded => 'Вы добавили максимальное количество активных индикаторов.'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return 'Добавить $indicator'; + } + + @override + String get infoAddIndicator => 'Добавьте индикатор'; + + @override + String get labelDeleteAllIndicators => 'Удалите все индикаторы'; + + @override + String get infoDeleteAllIndicators => 'Это удалит все активные индикаторы.'; + + @override + String infoResetIndicators(Object indicator) { + return 'Это вернет настройки индикатора $indicator по умолчанию.'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return 'Индикатор удаления $indicator'; + } + + @override + String labelResetIndicator(Object indicator) { + return 'Сбросить индикатор $indicator'; + } + + @override + String get infoDeleteIndicator => 'Вы уверены, что хотите удалить этот индикатор?'; + + @override + String get labelCancel => 'Отмена'; + + @override + String get labelDelete => 'Удалить'; + + @override + String get labelDeleteAll => 'Удалить все'; + + @override + String get infoUpto3indicatorsAllowed => 'Допускается до 3 активных индикаторов.'; + + @override + String get infoNoActiveIndicators => 'Нет активных индикаторов.'; + + @override + String get labelReset => 'Сброс'; + + @override + String get labelApply => 'Применить'; + + @override + String get labelOK => 'OK'; + + @override + String get labelRSILine => 'Линия RSI'; + + @override + String get labelPeriod => 'Период'; + + @override + String get labelMinRange => 'Минимальный диапазон'; + + @override + String get labelMaxRange => 'Максимальный диапазон'; + + @override + String get labelSource => 'Источник'; + + @override + String get labelClose => 'Закрыть'; + + @override + String get labelOpen => 'Открыть'; + + @override + String get labelHigh => 'Высокий'; + + @override + String get labelLow => 'Низкий'; + + @override + String get labelHl2 => 'Hl/2'; + + @override + String get labelHlc3 => 'Hlc/3'; + + @override + String get labelHlcc4 => 'Hlcc/4'; + + @override + String get labelOhlc4 => 'Ohlc/4'; + + @override + String get labelShowZones => 'Показать зоны'; + + @override + String get labelOverbought => 'Перекупленность'; + + @override + String get labelOversold => 'Перепроданность'; + + @override + String get labelMinSize => 'Минимальный размер'; + + @override + String get labelMaxSize => 'Максимальный размер'; + + @override + String get labelRange => 'Диапазон'; + + @override + String get labelOverboughtLine => 'Линия перекупленности'; + + @override + String get labelOversoldLine => 'Линия перепроданности'; + + @override + String get labelMACDLine => 'Линия MACD'; + + @override + String get labelFastMAPeriod => 'Быстрый период MA'; + + @override + String get labelSlowMAPeriod => 'Медленный период MA'; + + @override + String get labelSignalLine => 'Сигнальная линия'; + + @override + String get labelSignalPeriod => 'Период сигнала'; + + @override + String get labelIncreasingBar => 'Увеличивающаяся планка'; + + @override + String get labelDecreasingBar => 'Уменьшающаяся планка'; + + @override + String get labelBollingerBandsTop => 'Вершина Bollinger Bands'; + + @override + String get labelBollingerBandsMedian => 'Медиана полос Боллинджера'; + + @override + String get labelBollingerBandsBottom => 'Дно полос Боллинджера'; + + @override + String get labelChannelFill => 'Заполнение канала'; + + @override + String get labelFillColor => 'Цвет заливки'; + + @override + String get labelStandardDeviations => 'Стандартные отклонения'; + + @override + String get labelMovingAverageType => 'Тип скользящей средней'; + + @override + String get labelMALine => 'Линия MA'; + + @override + String get labelOffset => 'Смещение'; + + @override + String get labelType => 'Тип'; + + @override + String get labelSimple => 'Простой'; + + @override + String get labelExponential => 'Экспоненциальный'; + + @override + String get labelWeighted => 'Взвешенный'; + + @override + String get labelHull => 'Корпус'; + + @override + String get labelZeroLag => 'Нулевая задержка'; + + @override + String get labelTimeSeries => 'Временные ряды'; + + @override + String get labelWellesWilder => 'Уэллс Уайлдер'; + + @override + String get labelVariable => 'Переменная'; + + @override + String get labelTriangular => 'Треугольный'; + + @override + String get label2Exponential => '2-Экспоненциальный'; + + @override + String get label3Exponential => '3-экспоненциальный'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return 'Введите значение между $min и $max.'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return 'Ассортимент $min - $max'; + } + + @override + String get labelDrawingTools => 'Инструменты для рисования'; + + @override + String get labelTools => 'Инструменты'; + + @override + String get labelLine => 'Линия'; + + @override + String get labelRay => 'Рэй'; + + @override + String get informTapToSetFirstPoint => 'Нажмите, чтобы установить первую точку'; + + @override + String get informTapToSetFinalPoint => 'Нажмите, чтобы установить конечную точку'; + + @override + String get informNoActiveDrawingTools => 'Нет активных инструментов рисования.'; + + @override + String get actionAddDrawingTool => 'Добавить инструмент для рисования'; + + @override + String get labelOf => 'из'; + + @override + String get labelDeleteAllDrawingTools => 'Удалить все инструменты рисования'; + + @override + String get informDeleteAllDrawingTools => 'Это приведет к удалению всех активных инструментов рисования.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_si.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_si.dart new file mode 100644 index 000000000..e35adaf99 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_si.dart @@ -0,0 +1,306 @@ +import 'deriv_mobile_chart_wrapper_localizations.dart'; + +/// The translations for Sinhala Sinhalese (`si`). +class DerivMobileChartWrapperLocalizationsSi extends DerivMobileChartWrapperLocalizations { + DerivMobileChartWrapperLocalizationsSi([String locale = 'si']) : super(locale); + + @override + String get labelIndicators => 'දර්ශක'; + + @override + String get labelActive => 'ක්රියාකාරී'; + + @override + String get labelAll => 'සියල්ලම'; + + @override + String get labelMomentum => 'ගම්යතාව'; + + @override + String get labelVolatility => 'අස්ථාවරත්වය'; + + @override + String get labelMovingAverages => 'චලනය වන සාමාන්යයන්'; + + @override + String get labelMACD => 'මැක්ඩී'; + + @override + String get labelRelativeStrengthIndex => 'සාපේක්ෂ ශක්ති දර්ශකය (RSI)'; + + @override + String get labelRSI => 'ආර්එස්අයි'; + + @override + String get labelBollingerBands => 'බොලින්ගර් බෑන්ඩ් (බීබී)'; + + @override + String get labelBB => 'BB'; + + @override + String get labelMovingAverage => 'චලනය වන සාමාන්යය (MA)'; + + @override + String get labelMA => 'මා'; + + @override + String get infoMACD => 'MACD යනු කොටස් මිල ගණන් පිළිබඳ තාක්ෂණික විශ්ලේෂණයේදී භාවිතා කරන වෙළඳ දර්ශකයකි. එය කොටස් මිලෙහි ප්රවණතාවයේ ශක්තිය, දිශාව, ගම්යතාව සහ කාලසීමාවේ වෙනස්කම් හෙළි කිරීමට නියමිතය.'; + + @override + String get infoRSI => 'සාපේක්ෂ ශක්ති දර්ශකය (RSI) ප්රකාශයට පත් කරන ලද්දේ ජේ වෙල්ස් වයිල්ඩර් විසිනි. වර්තමාන මිල 0 සහ 100 අතර ප්රතිශතයක් ලෙස සාමාන්යකරණය කර ඇත. මෙම ඔස්කිලේටරයේ flutter_chart_id නොමඟ යන්නේ එය වෙනත් උපකරණයක් හෝ උපකරණ කට්ටලයකට සාපේක්ෂව උපකරණය සංසන්දනය නොකරන නිසා, ඒ වෙනුවට තෝරාගත් lookback කවුළුව දිග තුළ අනෙකුත් මෑත කෑලිවලට සාපේක්ෂව වත්මන් මිල නියෝජනය කරයි.'; + + @override + String get infoBB => 'පෙර වෙළඳාම් වලට සාපේක්ෂව මිලෙහි උසස්කම හෝ පහත් බව මැනීම සඳහා බොලින්ගර් බෑන්ඩ් (බීබී) භාවිතා කළ හැකිය.'; + + @override + String get infoMA => 'චලනය වන සාමාන්යය (MA) කෙටි කාලීන මිල උච්චාවචනයන් පෙරීම මගින් සමස්ත වෙළඳපල ප්රවණතාව හඳුනා ගැනීමට උපකාරී වේ. ඓතිහාසික දත්ත භාවිතා කරමින්, එය නිශ්චිත කාල පරිච්ඡේදයක් පුරා සාමාන්ය මිල ගණනය කර ප්රස්ථාරයේ රේඛාවක් සකස් කරයි. MA රේඛාව ඉහළට ගමන් කරන්නේ නම් එය uptrend ක දර්ශකයක්, පහළට ගමන් කරන්නේ නම් downtrend එකක්. මිල MA රේඛාවට ඉහළින් ගමන් කරන විට මිලදී ගැනීමේ සංඥාවක් සිදු වේ.'; + + @override + String get infoMaximumActiveIndicatorsAdded => 'ඔබ උපරිම ක්රියාකාරී දර්ශක සංඛ්යාව එකතු කර ඇත.'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return '$indicatorඑකතු කරන්න'; + } + + @override + String get infoAddIndicator => 'දර්ශකය එක් කරන්න'; + + @override + String get labelDeleteAllIndicators => 'සියලුම දර්ශක මකා දමන්න'; + + @override + String get infoDeleteAllIndicators => 'මෙය සියලුම ක්රියාකාරී දර්ශක මකා දමනු ඇත.'; + + @override + String infoResetIndicators(Object indicator) { + return 'මෙය $indicator දර්ශකය එහි පෙරනිමි සැකසුම් වලට යළි පිහිටුවනු ඇත.'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return '$indicator දර්ශකය මකන්න'; + } + + @override + String labelResetIndicator(Object indicator) { + return '$indicator දර්ශකය නැවත සකසන්න'; + } + + @override + String get infoDeleteIndicator => 'ඔබට මෙම දර්ශකය මකා දැමීමට අවශ්ය බව ඔබට විශ්වාසද?'; + + @override + String get labelCancel => 'අවලංගු කරන්න'; + + @override + String get labelDelete => 'මකන්න'; + + @override + String get labelDeleteAll => 'සියල්ල මකා දමන්න'; + + @override + String get infoUpto3indicatorsAllowed => 'ක්රියාකාරී දර්ශක 3 ක් දක්වා අවසර ඇත.'; + + @override + String get infoNoActiveIndicators => 'ක්රියාකාරී දර්ශක නොමැත.'; + + @override + String get labelReset => 'යළි පිහිටුවන්න'; + + @override + String get labelApply => 'අයදුම් කරන්න'; + + @override + String get labelOK => 'හරි'; + + @override + String get labelRSILine => 'ආර්එස්අයි රේඛාව'; + + @override + String get labelPeriod => 'කාල සීමාව'; + + @override + String get labelMinRange => 'අවම පරාසය'; + + @override + String get labelMaxRange => 'උපරිම පරාසය'; + + @override + String get labelSource => 'මූලාශ්රය'; + + @override + String get labelClose => 'වසා දමන්න'; + + @override + String get labelOpen => 'විවෘත කරන්න'; + + @override + String get labelHigh => 'ඉහළ'; + + @override + String get labelLow => 'අඩුයි'; + + @override + String get labelHl2 => 'එච්එල්/2'; + + @override + String get labelHlc3 => 'එච්එල්සී/3'; + + @override + String get labelHlcc4 => 'එච්එල්සී/4'; + + @override + String get labelOhlc4 => 'ඕඑච්එල්සී/4'; + + @override + String get labelShowZones => 'කලාප පෙන්වන්න'; + + @override + String get labelOverbought => 'අධික ලෙස මිල දී ගනු ලැබේ'; + + @override + String get labelOversold => 'අධික ලෙස විකුණා ඇත'; + + @override + String get labelMinSize => 'අවම ප්රමාණය'; + + @override + String get labelMaxSize => 'උපරිම ප්රමාණය'; + + @override + String get labelRange => 'පරාසය'; + + @override + String get labelOverboughtLine => 'අධික ලෙස මිලදී ගත් රේඛාව'; + + @override + String get labelOversoldLine => 'අධි විකුණුම් රේඛාව'; + + @override + String get labelMACDLine => 'MACD රේඛාව'; + + @override + String get labelFastMAPeriod => 'වේගවත් MA කාල පරිච්ඡේදය'; + + @override + String get labelSlowMAPeriod => 'මන්දගාමී MA කාලය'; + + @override + String get labelSignalLine => 'සංඥා රේඛාව'; + + @override + String get labelSignalPeriod => 'සංඥා කාලය'; + + @override + String get labelIncreasingBar => 'තීරුව වැඩි කිරීම'; + + @override + String get labelDecreasingBar => 'බාර් අඩුවීම'; + + @override + String get labelBollingerBandsTop => 'බොලින්ගර් බෑන්ඩ් ටොප්'; + + @override + String get labelBollingerBandsMedian => 'බොලින්ගර් බෑන්ඩ් මධ්යන්ය'; + + @override + String get labelBollingerBandsBottom => 'බොලින්ගර් බෑන්ඩ්ස් පහළ'; + + @override + String get labelChannelFill => 'නාලිකා පිරවීම'; + + @override + String get labelFillColor => 'වර්ණය පුරවන්න'; + + @override + String get labelStandardDeviations => 'සම්මත අපගමනය'; + + @override + String get labelMovingAverageType => 'චලනය වන සාමාන්ය වර්ගය'; + + @override + String get labelMALine => 'එම්ඒ රේඛාව'; + + @override + String get labelOffset => 'ඕෆ්සෙට්'; + + @override + String get labelType => 'වර්ගය'; + + @override + String get labelSimple => 'සරල'; + + @override + String get labelExponential => 'එක්ස්පේන්ෂල්'; + + @override + String get labelWeighted => 'බර බර'; + + @override + String get labelHull => 'හල්'; + + @override + String get labelZeroLag => 'ශුරෝ ලැග්'; + + @override + String get labelTimeSeries => 'කාල මාලාව'; + + @override + String get labelWellesWilder => 'වෙල්ස් වයිල්ඩර්'; + + @override + String get labelVariable => 'විචල්ය'; + + @override + String get labelTriangular => 'ත්රිකෝණාකාර'; + + @override + String get label2Exponential => '2-එක්ස්පෙන්ෂනල්'; + + @override + String get label3Exponential => '3-එක්ස්පෙන්ෂනල්'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return '$min සහ $maxඅතර අගයක් ඇතුළත් කරන්න'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return 'පරාසය $min - $max'; + } + + @override + String get labelDrawingTools => 'ඇඳීමේ මෙවලම්'; + + @override + String get labelTools => 'මෙවලම්'; + + @override + String get labelLine => 'රේඛාව'; + + @override + String get labelRay => 'රේ'; + + @override + String get informTapToSetFirstPoint => 'පළමු ලක්ෂ්යය සැකසීමට තට්ටු කරන්න'; + + @override + String get informTapToSetFinalPoint => 'අවසාන ලක්ෂ්යය සැකසීමට තට්ටු කරන්න'; + + @override + String get informNoActiveDrawingTools => 'ක්රියාකාරී ඇඳීමේ මෙවලම් නොමැත.'; + + @override + String get actionAddDrawingTool => 'ඇඳීමේ මෙවලම එක් කරන්න'; + + @override + String get labelOf => 'හි'; + + @override + String get labelDeleteAllDrawingTools => 'සියලුම ඇඳීමේ මෙවලම් මකන්න'; + + @override + String get informDeleteAllDrawingTools => 'මෙය සියලුම ක්රියාකාරී ඇඳීමේ මෙවලම් මකා දමනු ඇත.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_sw.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_sw.dart new file mode 100644 index 000000000..b7e34d44b --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_sw.dart @@ -0,0 +1,306 @@ +import 'deriv_mobile_chart_wrapper_localizations.dart'; + +/// The translations for Swahili (`sw`). +class DerivMobileChartWrapperLocalizationsSw extends DerivMobileChartWrapperLocalizations { + DerivMobileChartWrapperLocalizationsSw([String locale = 'sw']) : super(locale); + + @override + String get labelIndicators => 'Viashiria'; + + @override + String get labelActive => 'Inafanya kazi'; + + @override + String get labelAll => 'Wote'; + + @override + String get labelMomentum => 'Mwendo'; + + @override + String get labelVolatility => 'Ubadilishaji'; + + @override + String get labelMovingAverages => 'Wastani wa kusonga'; + + @override + String get labelMACD => 'MACD'; + + @override + String get labelRelativeStrengthIndex => 'Kielelezo cha Nguvu ya Uhusiano (RSI)'; + + @override + String get labelRSI => 'RSI'; + + @override + String get labelBollingerBands => 'Bendi za Bollinger (BB)'; + + @override + String get labelBB => 'BB'; + + @override + String get labelMovingAverage => 'Wastani wa kusonga (MA)'; + + @override + String get labelMA => 'MA'; + + @override + String get infoMACD => 'MACD ni kiashiria cha biashara kinachotumiwa katika uchambuzi wa kiufundi wa bei za hisa. Inapaswa kufichua mabadiliko katika nguvu, mwelekeo, kasi, na muda wa mwenendo wa bei ya hisa.'; + + @override + String get infoRSI => 'Fahirisi ya Nguvu ya Relay (RSI) ilichapishwa na J. Welles Wilder. Bei ya sasa imeorodheshwa kama asilimia kati ya 0 na 100. Flutter_chart_id ya oscillator hii inatoa kwa sababu hailinganishi chombo kinachohusiana na chombo kingine au seti ya vyombo, lakini badala yake inawakilisha bei ya sasa ikilinganishwa na vipande vingine vya hivi karibuni ndani ya urefu wa dirisha lililochaguliwa.'; + + @override + String get infoBB => 'Bendi za Bollinger (BB) zinaweza kutumika kupima juu au chini wa bei kulinganishwa na biashara zilizopita.'; + + @override + String get infoMA => 'Wastani wa Kusonga (MA) husaidia kutambua mwenendo wa jumla wa soko kwa kuchuja mabadiliko ya bei ya muda mfupi. Kutumia data ya kihistoria, inahesabu bei ya wastani kwa kipindi maalum na inapanga mstari kwenye chati. Ikiwa mstari wa MA unasonga juu, ni kiashiria cha kuongezeka, mwenendo wa kushuka ikiwa unasonga chini. Ishara ya ununuzi hutokea wakati bei inapohamia juu ya mstari wa MA.'; + + @override + String get infoMaximumActiveIndicatorsAdded => 'Umeongeza idadi kubwa ya viashiria vya kazi.'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return 'Ongeza $indicator'; + } + + @override + String get infoAddIndicator => 'Ongeza kiashiria'; + + @override + String get labelDeleteAllIndicators => 'Futa viashiria vyote'; + + @override + String get infoDeleteAllIndicators => 'Hii itafuta viashiria vyote vya kazi.'; + + @override + String infoResetIndicators(Object indicator) { + return 'Hii itaweka upya kiashiria cha $indicator kwa mipangilio yake ya default.'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return 'Futa kiashiria cha $indicator'; + } + + @override + String labelResetIndicator(Object indicator) { + return 'Weka upya kiashiria cha $indicator'; + } + + @override + String get infoDeleteIndicator => 'Je! Una hakika unataka kufuta kiashiria hiki?'; + + @override + String get labelCancel => 'Ghairi'; + + @override + String get labelDelete => 'Futa'; + + @override + String get labelDeleteAll => 'Futa Yote'; + + @override + String get infoUpto3indicatorsAllowed => 'Hadi viashiria 3 vya kazi zinaruhusiwa.'; + + @override + String get infoNoActiveIndicators => 'Hakuna viashiria vya kazi.'; + + @override + String get labelReset => 'Weka upya'; + + @override + String get labelApply => 'Tumia'; + + @override + String get labelOK => 'SAWA'; + + @override + String get labelRSILine => 'Mstari wa RSI'; + + @override + String get labelPeriod => 'Kipindi'; + + @override + String get labelMinRange => 'Kiwango cha chini'; + + @override + String get labelMaxRange => 'Kiwango cha juu'; + + @override + String get labelSource => 'chanzo'; + + @override + String get labelClose => 'Kufunga'; + + @override + String get labelOpen => 'Fungua'; + + @override + String get labelHigh => 'Juu'; + + @override + String get labelLow => 'Ndogo'; + + @override + String get labelHl2 => 'Hl/2'; + + @override + String get labelHlc3 => 'HLC/3'; + + @override + String get labelHlcc4 => 'Hlcc/4'; + + @override + String get labelOhlc4 => 'Ohlc/4'; + + @override + String get labelShowZones => 'Onyesha Maeneo'; + + @override + String get labelOverbought => 'Kununua kupita kiasi'; + + @override + String get labelOversold => 'Uuzwa kupita kiasi'; + + @override + String get labelMinSize => 'Ukubwa wa chini'; + + @override + String get labelMaxSize => 'Ukubwa wa juu'; + + @override + String get labelRange => 'Mbalimbali'; + + @override + String get labelOverboughtLine => 'Mstari wa kununuliwa kupita'; + + @override + String get labelOversoldLine => 'Mstari uliouzwa zaidi'; + + @override + String get labelMACDLine => 'Mstari wa MACD'; + + @override + String get labelFastMAPeriod => 'Kipindi cha haraka cha MA'; + + @override + String get labelSlowMAPeriod => 'Kipindi cha polepole cha MA'; + + @override + String get labelSignalLine => 'Mstari wa ishara'; + + @override + String get labelSignalPeriod => 'Kipindi cha ishara'; + + @override + String get labelIncreasingBar => 'Kuongezeka kwa bar'; + + @override + String get labelDecreasingBar => 'Baa ya kupungua'; + + @override + String get labelBollingerBandsTop => 'Bollinger Bands juu'; + + @override + String get labelBollingerBandsMedian => 'Bollinger Bands wastani'; + + @override + String get labelBollingerBandsBottom => 'Bandi za Bollinger chini'; + + @override + String get labelChannelFill => 'Kujaza kituo'; + + @override + String get labelFillColor => 'Jaza rangi'; + + @override + String get labelStandardDeviations => 'Upungufu wa kawaida'; + + @override + String get labelMovingAverageType => 'Aina ya wastani ya kusonga'; + + @override + String get labelMALine => 'Mstari wa MA'; + + @override + String get labelOffset => 'Kidhibiti'; + + @override + String get labelType => 'Aina'; + + @override + String get labelSimple => 'Rahisi'; + + @override + String get labelExponential => 'Mwanganyiko'; + + @override + String get labelWeighted => 'Uzito'; + + @override + String get labelHull => 'Hull'; + + @override + String get labelZeroLag => 'Zero Lag'; + + @override + String get labelTimeSeries => 'Mfululizo wa Wakati'; + + @override + String get labelWellesWilder => 'Welles Wilder'; + + @override + String get labelVariable => 'Tofauti'; + + @override + String get labelTriangular => 'Pembetatu'; + + @override + String get label2Exponential => '2-Exponential'; + + @override + String get label3Exponential => '3-Mfanyiko'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return 'Ingiza thamani kati ya $min na $max'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return 'Kiwango cha $min - $max'; + } + + @override + String get labelDrawingTools => 'Zana za kuchora'; + + @override + String get labelTools => 'Vyombo'; + + @override + String get labelLine => 'Mstari'; + + @override + String get labelRay => 'taa'; + + @override + String get informTapToSetFirstPoint => 'Gonga ili kuweka hatua ya kwanza'; + + @override + String get informTapToSetFinalPoint => 'Gonga ili kuweka hatua ya mwisho'; + + @override + String get informNoActiveDrawingTools => 'Hakuna zana zinazofaa za kuchora.'; + + @override + String get actionAddDrawingTool => 'Ongeza zana ya kuchora'; + + @override + String get labelOf => 'ya'; + + @override + String get labelDeleteAllDrawingTools => 'Futa zana zote za kuchora'; + + @override + String get informDeleteAllDrawingTools => 'Hii itafuta zana zote za kuchora zinazotumika.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_th.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_th.dart new file mode 100644 index 000000000..b319a8e20 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_th.dart @@ -0,0 +1,306 @@ +import 'deriv_mobile_chart_wrapper_localizations.dart'; + +/// The translations for Thai (`th`). +class DerivMobileChartWrapperLocalizationsTh extends DerivMobileChartWrapperLocalizations { + DerivMobileChartWrapperLocalizationsTh([String locale = 'th']) : super(locale); + + @override + String get labelIndicators => 'ตัวบ่งชี้'; + + @override + String get labelActive => 'ใช้งานอยู่'; + + @override + String get labelAll => 'ทั้งหมด'; + + @override + String get labelMomentum => 'โมเมนตัม'; + + @override + String get labelVolatility => 'ความผันผวน'; + + @override + String get labelMovingAverages => 'ค่าเฉลี่ยเคลื่อนที่'; + + @override + String get labelMACD => 'MACD'; + + @override + String get labelRelativeStrengthIndex => 'ดัชนีความแข็งแรงสัมพัทธ์ (RSI)'; + + @override + String get labelRSI => 'RSI'; + + @override + String get labelBollingerBands => 'Bollinger Bands (BB)'; + + @override + String get labelBB => 'BB'; + + @override + String get labelMovingAverage => 'ค่าเฉลี่ยเคลื่อนที่ (MA)'; + + @override + String get labelMA => 'MA'; + + @override + String get infoMACD => 'MACD เป็นตัวบ่งชี้ที่ใช้ในการวิเคราะห์ทางเทคนิคของราคาหุ้น มีวัตถุประสงค์เพื่อแสดงการเปลี่ยนแปลงในความแข็งแกร่ง ทิศทาง โมเมนตัม และระยะเวลาของแนวโน้มในราคาหุ้น'; + + @override + String get infoRSI => 'ดัชนีความแข็งแรงสัมพัทธ์หรือ Relative Strength Index (RSI) ถูกตีพิมพ์โดย J. Welles Wilder ราคาปัจจุบันถูกแปลงเป็นเปอร์เซ็นต์ระหว่าง 0 ถึง 100 ค่า flutter_chart_id ของตัวบ่งชี้นี้อาจทำให้เข้าใจผิดได้ เนื่องจากไม่ได้เปรียบเทียบตราสารอันหนึ่งกับตราสารอื่นหรือกลุ่มสินทรัพย์อื่น แต่จะแสดงราคาปัจจุบันเทียบกับราคาล่าสุดในช่วงระยะเวลาที่เลือกมองย้อนกลับไป'; + + @override + String get infoBB => 'Bollinger Bands (BB) สามารถใช้วัดความสูงหรือต่ำของราคาเมื่อเทียบกับการซื้อขายก่อนหน้านี้'; + + @override + String get infoMA => 'ค่าเฉลี่ยเคลื่อนที่หรือ Moving Average (MA) ช่วยในการระบุแนวโน้มตลาดโดยรวมโดยการกรองความผันผวนของราคาระยะสั้นออกไป โดยใช้ข้อมูลในอดีตเพื่อคำนวณราคาที่เฉลี่ยในช่วงเวลาที่กำหนดและแสดงเป็นเส้นบนกราฟ หากเส้น MA เคลื่อนที่ขึ้นแสดงถึงแนวโน้มขาขึ้น หากเคลื่อนที่ลงแสดงถึงแนวโน้มขาลง สัญญาณการซื้อเกิดขึ้นเมื่อราคาขยับเหนือเส้น MA'; + + @override + String get infoMaximumActiveIndicatorsAdded => 'คุณได้เพิ่มตัวบ่งชี้ที่ใช้งานอยู่จนถึงจำนวนสูงสุดแล้ว'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return 'เพิ่ม $indicator'; + } + + @override + String get infoAddIndicator => 'เพิ่มตัวบ่งชี้'; + + @override + String get labelDeleteAllIndicators => 'ลบตัวบ่งชี้ทั้งหมด'; + + @override + String get infoDeleteAllIndicators => 'สิ่งนี้จะลบตัวบ่งชี้ที่ใช้งานอยู่ทั้งหมด'; + + @override + String infoResetIndicators(Object indicator) { + return 'สิ่งนี้จะรีเซ็ตตัวบ่งชี้ $indicator ไปเป็นตามค่าเริ่มต้นของมัน'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return 'ลบตัวบ่งชี้ $indicator'; + } + + @override + String labelResetIndicator(Object indicator) { + return 'รีเซ็ตตัวบ่งชี้ $indicator'; + } + + @override + String get infoDeleteIndicator => 'คุณแน่ใจหรือไม่ว่าต้องการลบตัวบ่งชี้นี้?'; + + @override + String get labelCancel => 'ยกเลิก'; + + @override + String get labelDelete => 'ลบ'; + + @override + String get labelDeleteAll => 'ลบทั้งหมด'; + + @override + String get infoUpto3indicatorsAllowed => 'อนุญาตให้ใช้ตัวบ่งชี้ที่ใช้งานอยู่สูงสุด 3 ตัว'; + + @override + String get infoNoActiveIndicators => 'ไม่มีตัวบ่งชี้ที่ใช้งานอยู่'; + + @override + String get labelReset => 'รีเซ็ท'; + + @override + String get labelApply => 'ใช้งาน'; + + @override + String get labelOK => 'OK'; + + @override + String get labelRSILine => 'เส้น RSI'; + + @override + String get labelPeriod => 'ระยะเวลา'; + + @override + String get labelMinRange => 'ช่วงขั้นต่ำ'; + + @override + String get labelMaxRange => 'ช่วงสูงสุด'; + + @override + String get labelSource => 'แหล่งที่มา'; + + @override + String get labelClose => 'Close'; + + @override + String get labelOpen => 'Open'; + + @override + String get labelHigh => 'High'; + + @override + String get labelLow => 'Low'; + + @override + String get labelHl2 => 'Hl/2'; + + @override + String get labelHlc3 => 'Hlc/3'; + + @override + String get labelHlcc4 => 'Hlcc/4'; + + @override + String get labelOhlc4 => 'Ohlc/4'; + + @override + String get labelShowZones => 'แสดงโซน'; + + @override + String get labelOverbought => 'การซื้อมากเกินไป'; + + @override + String get labelOversold => 'การขายมากเกินไป'; + + @override + String get labelMinSize => 'ขนาดขั้นต่ำ'; + + @override + String get labelMaxSize => 'ขนาดสูงสุด'; + + @override + String get labelRange => 'ช่วง'; + + @override + String get labelOverboughtLine => 'เส้นการซื้อมากเกินไป'; + + @override + String get labelOversoldLine => 'เส้นการขายมากเกินไป'; + + @override + String get labelMACDLine => 'เส้น MACD'; + + @override + String get labelFastMAPeriod => 'ระยะเวลา MA ที่เร็ว'; + + @override + String get labelSlowMAPeriod => 'ระยะเวลา MA ที่ช้า'; + + @override + String get labelSignalLine => 'เส้นสัญญาณ'; + + @override + String get labelSignalPeriod => 'ระยะเวลาสัญญาณ'; + + @override + String get labelIncreasingBar => 'แถบที่เพิ่มขึ้น'; + + @override + String get labelDecreasingBar => 'แถบที่ลดลง'; + + @override + String get labelBollingerBandsTop => 'Bollinger Bands อันบน'; + + @override + String get labelBollingerBandsMedian => 'Bollinger Bands อันกลาง'; + + @override + String get labelBollingerBandsBottom => 'Bollinger Bands อันล่าง'; + + @override + String get labelChannelFill => 'เติม Channel'; + + @override + String get labelFillColor => 'เติมสี'; + + @override + String get labelStandardDeviations => 'ค่าเบี่ยงเบนมาตรฐาน'; + + @override + String get labelMovingAverageType => 'ประเภทค่าเฉลี่ยเคลื่อนที่ (MA)'; + + @override + String get labelMALine => 'เส้น MA'; + + @override + String get labelOffset => 'ออฟเซ็ท'; + + @override + String get labelType => 'ประเภท'; + + @override + String get labelSimple => 'Simple'; + + @override + String get labelExponential => 'Exponential'; + + @override + String get labelWeighted => 'Weighted'; + + @override + String get labelHull => 'Hull'; + + @override + String get labelZeroLag => 'Zero Lag'; + + @override + String get labelTimeSeries => 'ชุดข้อมูลตามลำดับเวลา'; + + @override + String get labelWellesWilder => 'Welles Wilder'; + + @override + String get labelVariable => 'แบบแปรผัน'; + + @override + String get labelTriangular => 'แบบสามเหลี่ยม'; + + @override + String get label2Exponential => '2-เลขชี้กำลัง'; + + @override + String get label3Exponential => '3- เลขชี้กำลัง'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return 'ป้อนค่าระหว่าง $min และ $max'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return 'ช่วง $min - $max'; + } + + @override + String get labelDrawingTools => 'เครื่องมือวาด'; + + @override + String get labelTools => 'เครื่องมือ'; + + @override + String get labelLine => 'เส้น'; + + @override + String get labelRay => 'รังสี'; + + @override + String get informTapToSetFirstPoint => 'แตะเพื่อตั้งค่าจุดแรก'; + + @override + String get informTapToSetFinalPoint => 'แตะเพื่อตั้งค่าจุดสุดท้าย'; + + @override + String get informNoActiveDrawingTools => 'ไม่มีเครื่องมือวาดภาพที่ใช้งานอยู่'; + + @override + String get actionAddDrawingTool => 'เพิ่มเครื่องมือวาดภาพ'; + + @override + String get labelOf => 'ของ'; + + @override + String get labelDeleteAllDrawingTools => 'ลบเครื่องมือวาดภาพทั้งหมด'; + + @override + String get informDeleteAllDrawingTools => 'สิ่งนี้จะลบเครื่องมือวาดภาพที่ใช้งานอยู่ทั้งหมด'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_tr.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_tr.dart new file mode 100644 index 000000000..4c3849e2f --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_tr.dart @@ -0,0 +1,306 @@ +import 'deriv_mobile_chart_wrapper_localizations.dart'; + +/// The translations for Turkish (`tr`). +class DerivMobileChartWrapperLocalizationsTr extends DerivMobileChartWrapperLocalizations { + DerivMobileChartWrapperLocalizationsTr([String locale = 'tr']) : super(locale); + + @override + String get labelIndicators => 'Göstergeler'; + + @override + String get labelActive => 'Aktif'; + + @override + String get labelAll => 'Tümü'; + + @override + String get labelMomentum => 'Momentum'; + + @override + String get labelVolatility => 'Volatilite'; + + @override + String get labelMovingAverages => 'Hareketli ortalamalar'; + + @override + String get labelMACD => 'MACD'; + + @override + String get labelRelativeStrengthIndex => 'Göreceli Güç Endeksi (RSI)'; + + @override + String get labelRSI => 'RSI'; + + @override + String get labelBollingerBands => 'Bollinger Bantları (BB)'; + + @override + String get labelBB => 'BB'; + + @override + String get labelMovingAverage => 'Hareketli Ortalama (MA)'; + + @override + String get labelMA => 'MA'; + + @override + String get infoMACD => 'MACD, hisse senedi fiyatlarının teknik analizinde kullanılan bir alım satım göstergesidir. Bir hisse senedinin fiyatındaki bir trendin gücü, yönü, momentumu ve süresindeki değişiklikleri ortaya çıkarması beklenir.'; + + @override + String get infoRSI => 'Göreceli Güç Endeksi (RSI) J. Welles Wilder tarafından yayınlanmıştır. Mevcut fiyat 0 ile 100 arasında bir yüzde olarak normalleştirilir. Bu osilatörün flutter_chart_id\'si yanıltıcıdır çünkü enstrümanı başka bir enstrümanla veya enstrüman setiyle karşılaştırmaz, bunun yerine seçilen geriye dönük pencere uzunluğu içindeki diğer son parçalara göre mevcut fiyatı temsil eder.'; + + @override + String get infoBB => 'Bollinger Bantları (BB), önceki işlemlere göre fiyatın yüksekliğini veya düşüklüğünü ölçmek için kullanılabilir.'; + + @override + String get infoMA => 'Hareketli Ortalama (MA), kısa vadeli fiyat dalgalanmalarını filtreleyerek genel piyasa trendini belirlemeye yardımcı olur. Geçmiş verileri kullanarak belirli bir dönemdeki ortalama fiyatı hesaplar ve grafik üzerinde bir çizgi çizer. MA çizgisi yukarı doğru hareket ederse, bu bir yükseliş trendinin, aşağı doğru hareket ederse bir düşüş trendinin göstergesidir. Fiyat MA çizgisinin üzerine çıktığında bir alım sinyali oluşur.'; + + @override + String get infoMaximumActiveIndicatorsAdded => 'Maksimum sayıda aktif gösterge eklediniz.'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return '${indicator}adresini ekleyin'; + } + + @override + String get infoAddIndicator => 'Gösterge ekleyin'; + + @override + String get labelDeleteAllIndicators => 'Tüm göstergeleri sil'; + + @override + String get infoDeleteAllIndicators => 'Bu işlem tüm aktif göstergeleri silecektir.'; + + @override + String infoResetIndicators(Object indicator) { + return 'Bu, $indicator göstergesini varsayılan ayarlarına sıfırlar.'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return 'Sil $indicator göstergesi'; + } + + @override + String labelResetIndicator(Object indicator) { + return '$indicator göstergesini sıfırla'; + } + + @override + String get infoDeleteIndicator => 'Bu göstergeyi silmek istediğinizden emin misiniz?'; + + @override + String get labelCancel => 'İptal'; + + @override + String get labelDelete => 'Silme'; + + @override + String get labelDeleteAll => 'Tümünü Sil'; + + @override + String get infoUpto3indicatorsAllowed => 'En fazla 3 aktif göstergeye izin verilir.'; + + @override + String get infoNoActiveIndicators => 'Aktif gösterge yok.'; + + @override + String get labelReset => 'Sıfırla'; + + @override + String get labelApply => 'Başvurmak'; + + @override + String get labelOK => 'TAMAM.'; + + @override + String get labelRSILine => 'RSI çizgisi'; + + @override + String get labelPeriod => 'Dönem'; + + @override + String get labelMinRange => 'Min aralık'; + + @override + String get labelMaxRange => 'Maksimum aralık'; + + @override + String get labelSource => 'Kaynak'; + + @override + String get labelClose => 'Kapat'; + + @override + String get labelOpen => 'Açık'; + + @override + String get labelHigh => 'Yüksek'; + + @override + String get labelLow => 'Düşük'; + + @override + String get labelHl2 => 'Hl/2'; + + @override + String get labelHlc3 => 'Hlc/3'; + + @override + String get labelHlcc4 => 'Hlcc/4'; + + @override + String get labelOhlc4 => 'Ohlc/4'; + + @override + String get labelShowZones => 'Bölgeleri Göster'; + + @override + String get labelOverbought => 'Aşırı alım'; + + @override + String get labelOversold => 'Aşırı satım'; + + @override + String get labelMinSize => 'Min boyut'; + + @override + String get labelMaxSize => 'Maksimum boyut'; + + @override + String get labelRange => 'Menzil'; + + @override + String get labelOverboughtLine => 'Aşırı alım çizgisi'; + + @override + String get labelOversoldLine => 'Aşırı satım çizgisi'; + + @override + String get labelMACDLine => 'MACD çizgisi'; + + @override + String get labelFastMAPeriod => 'Hızlı MA dönemi'; + + @override + String get labelSlowMAPeriod => 'Yavaş MA dönemi'; + + @override + String get labelSignalLine => 'Sinyal hattı'; + + @override + String get labelSignalPeriod => 'Sinyal süresi'; + + @override + String get labelIncreasingBar => 'Artan çubuk'; + + @override + String get labelDecreasingBar => 'Azalan çubuk'; + + @override + String get labelBollingerBandsTop => 'Bollinger Bantları üst'; + + @override + String get labelBollingerBandsMedian => 'Bollinger Bantları medyan'; + + @override + String get labelBollingerBandsBottom => 'Bollinger Bantları alt'; + + @override + String get labelChannelFill => 'Kanal dolgusu'; + + @override + String get labelFillColor => 'Dolgu rengi'; + + @override + String get labelStandardDeviations => 'Standart sapmalar'; + + @override + String get labelMovingAverageType => 'Hareketli Ortalama Türü'; + + @override + String get labelMALine => 'Ana hat'; + + @override + String get labelOffset => 'Ofset'; + + @override + String get labelType => 'Tip'; + + @override + String get labelSimple => 'Basit'; + + @override + String get labelExponential => 'Üstel'; + + @override + String get labelWeighted => 'Ağırlıklı'; + + @override + String get labelHull => 'Gövde'; + + @override + String get labelZeroLag => 'Sıfır Gecikme'; + + @override + String get labelTimeSeries => 'Zaman Serisi'; + + @override + String get labelWellesWilder => 'Welles Wilder'; + + @override + String get labelVariable => 'Değişken'; + + @override + String get labelTriangular => 'Üçgen'; + + @override + String get label2Exponential => '2-Eksponansiyel'; + + @override + String get label3Exponential => '3-Eksponansiyel'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return '$min ile ${max}arasında bir değer girin.'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return 'Aralık $min - $max'; + } + + @override + String get labelDrawingTools => 'Çizim araçları'; + + @override + String get labelTools => 'Araçlar'; + + @override + String get labelLine => 'Çizgi'; + + @override + String get labelRay => 'ışın'; + + @override + String get informTapToSetFirstPoint => 'İlk noktayı ayarlamak için dokunun'; + + @override + String get informTapToSetFinalPoint => 'Son noktayı ayarlamak için dokunun'; + + @override + String get informNoActiveDrawingTools => 'Etkin çizim aracı yok.'; + + @override + String get actionAddDrawingTool => 'Çizim aracı ekle'; + + @override + String get labelOf => 'dan'; + + @override + String get labelDeleteAllDrawingTools => 'Tüm çizim araçlarını sil'; + + @override + String get informDeleteAllDrawingTools => 'Bu, tüm aktif çizim araçlarını silecektir.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_uz.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_uz.dart new file mode 100644 index 000000000..0ce767b09 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_uz.dart @@ -0,0 +1,306 @@ +import 'deriv_mobile_chart_wrapper_localizations.dart'; + +/// The translations for Uzbek (`uz`). +class DerivMobileChartWrapperLocalizationsUz extends DerivMobileChartWrapperLocalizations { + DerivMobileChartWrapperLocalizationsUz([String locale = 'uz']) : super(locale); + + @override + String get labelIndicators => 'Indicators'; + + @override + String get labelActive => 'Active'; + + @override + String get labelAll => 'All'; + + @override + String get labelMomentum => 'Momentum'; + + @override + String get labelVolatility => 'Volatility'; + + @override + String get labelMovingAverages => 'Moving averages'; + + @override + String get labelMACD => 'MACD'; + + @override + String get labelRelativeStrengthIndex => 'Relative Strength Index (RSI)'; + + @override + String get labelRSI => 'RSI'; + + @override + String get labelBollingerBands => 'Bollinger Bands (BB)'; + + @override + String get labelBB => 'BB'; + + @override + String get labelMovingAverage => 'Moving Average (MA)'; + + @override + String get labelMA => 'MA'; + + @override + String get infoMACD => 'MACD is a trading indicator used in technical analysis of stock prices. It is supposed to reveal changes in the strength, direction, momentum, and duration of a trend in a stock\'s price.'; + + @override + String get infoRSI => 'The Relative Strength Index (RSI) was published by J. Welles Wilder. The current price is normalized as a percentage between 0 and 100. The flutter_chart_id of this oscillator is misleading because it does not compare the instrument relative to another instrument or set of instruments, but rather represents the current price relative to other recent pieces within the selected lookback window length.'; + + @override + String get infoBB => 'Bollinger Bands (BB) can be used to measure the highness or lowness of the price relative to previous trades.'; + + @override + String get infoMA => 'The Moving Average (MA) helps to identify the overall market trend by filtering out short-term price fluctuations. Using historical data, it calculates the average price over a specific period and plots a line on the chart. If the MA line moves upwards, it’s an indicator of an uptrend, a downtrend if it moves downwards. A buy signal occurs when the price moves above the MA line.'; + + @override + String get infoMaximumActiveIndicatorsAdded => 'You\'ve added the maximum number of active indicators.'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return 'Add $indicator'; + } + + @override + String get infoAddIndicator => 'Add indicator'; + + @override + String get labelDeleteAllIndicators => 'Delete all indicators'; + + @override + String get infoDeleteAllIndicators => 'This will delete all active indicators.'; + + @override + String infoResetIndicators(Object indicator) { + return 'This will reset the $indicator indicator to its default settings.'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return 'Delete $indicator indicator'; + } + + @override + String labelResetIndicator(Object indicator) { + return 'Reset $indicator indicator'; + } + + @override + String get infoDeleteIndicator => 'Are you sure you want to delete this indicator?'; + + @override + String get labelCancel => 'Cancel'; + + @override + String get labelDelete => 'Delete'; + + @override + String get labelDeleteAll => 'Delete All'; + + @override + String get infoUpto3indicatorsAllowed => 'Up to 3 active indicators allowed.'; + + @override + String get infoNoActiveIndicators => 'No active indicators.'; + + @override + String get labelReset => 'Reset'; + + @override + String get labelApply => 'Apply'; + + @override + String get labelOK => 'OK'; + + @override + String get labelRSILine => 'RSI line'; + + @override + String get labelPeriod => 'Period'; + + @override + String get labelMinRange => 'Min range'; + + @override + String get labelMaxRange => 'Max range'; + + @override + String get labelSource => 'Source'; + + @override + String get labelClose => 'Close'; + + @override + String get labelOpen => 'Open'; + + @override + String get labelHigh => 'High'; + + @override + String get labelLow => 'Low'; + + @override + String get labelHl2 => 'Hl/2'; + + @override + String get labelHlc3 => 'Hlc/3'; + + @override + String get labelHlcc4 => 'Hlcc/4'; + + @override + String get labelOhlc4 => 'Ohlc/4'; + + @override + String get labelShowZones => 'Show Zones'; + + @override + String get labelOverbought => 'Overbought'; + + @override + String get labelOversold => 'Oversold'; + + @override + String get labelMinSize => 'Min size'; + + @override + String get labelMaxSize => 'Max size'; + + @override + String get labelRange => 'Range'; + + @override + String get labelOverboughtLine => 'Overbought line'; + + @override + String get labelOversoldLine => 'Oversold line'; + + @override + String get labelMACDLine => 'MACD line'; + + @override + String get labelFastMAPeriod => 'Fast MA period'; + + @override + String get labelSlowMAPeriod => 'Slow MA period'; + + @override + String get labelSignalLine => 'Signal line'; + + @override + String get labelSignalPeriod => 'Signal period'; + + @override + String get labelIncreasingBar => 'Increasing bar'; + + @override + String get labelDecreasingBar => 'Decreasing bar'; + + @override + String get labelBollingerBandsTop => 'Bollinger Bands top'; + + @override + String get labelBollingerBandsMedian => 'Bollinger Bands median'; + + @override + String get labelBollingerBandsBottom => 'Bollinger Bands bottom'; + + @override + String get labelChannelFill => 'Channel fill'; + + @override + String get labelFillColor => 'Fill color'; + + @override + String get labelStandardDeviations => 'Standard deviations'; + + @override + String get labelMovingAverageType => 'Moving Average Type'; + + @override + String get labelMALine => 'MA line'; + + @override + String get labelOffset => 'Offset'; + + @override + String get labelType => 'Type'; + + @override + String get labelSimple => 'Simple'; + + @override + String get labelExponential => 'Exponential'; + + @override + String get labelWeighted => 'Weighted'; + + @override + String get labelHull => 'Hull'; + + @override + String get labelZeroLag => 'Zero Lag'; + + @override + String get labelTimeSeries => 'Time Series'; + + @override + String get labelWellesWilder => 'Welles Wilder'; + + @override + String get labelVariable => 'Variable'; + + @override + String get labelTriangular => 'Triangular'; + + @override + String get label2Exponential => '2-Exponential'; + + @override + String get label3Exponential => '3-Exponential'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return 'Enter a value between $min and $max'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return 'Range $min - $max'; + } + + @override + String get labelDrawingTools => 'Drawing tools'; + + @override + String get labelTools => 'Tools'; + + @override + String get labelLine => 'Line'; + + @override + String get labelRay => 'Ray'; + + @override + String get informTapToSetFirstPoint => 'Tap to set first point'; + + @override + String get informTapToSetFinalPoint => 'Tap to set final point'; + + @override + String get informNoActiveDrawingTools => 'No active drawing tools.'; + + @override + String get actionAddDrawingTool => 'Add drawing tool'; + + @override + String get labelOf => 'ning'; + + @override + String get labelDeleteAllDrawingTools => 'Barcha chizish vositalarini o\'chirish'; + + @override + String get informDeleteAllDrawingTools => 'Bu barcha faol chizish vositalarini o\'chirib tashlaydi.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_vi.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_vi.dart new file mode 100644 index 000000000..2b943e712 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_vi.dart @@ -0,0 +1,306 @@ +import 'deriv_mobile_chart_wrapper_localizations.dart'; + +/// The translations for Vietnamese (`vi`). +class DerivMobileChartWrapperLocalizationsVi extends DerivMobileChartWrapperLocalizations { + DerivMobileChartWrapperLocalizationsVi([String locale = 'vi']) : super(locale); + + @override + String get labelIndicators => 'Các chỉ số'; + + @override + String get labelActive => 'Hoạt động'; + + @override + String get labelAll => 'Tất cả'; + + @override + String get labelMomentum => 'Đà'; + + @override + String get labelVolatility => 'Có trọng lượng'; + + @override + String get labelMovingAverages => 'Đường trung bình động'; + + @override + String get labelMACD => 'MACD'; + + @override + String get labelRelativeStrengthIndex => 'Chỉ số sức mạnh tương đối (RSI)'; + + @override + String get labelRSI => 'RSI'; + + @override + String get labelBollingerBands => 'Dải Bollinger (BB)'; + + @override + String get labelBB => 'BB'; + + @override + String get labelMovingAverage => 'Đường trung bình động (MA)'; + + @override + String get labelMA => 'MA'; + + @override + String get infoMACD => 'MACD là một chỉ báo giao dịch được sử dụng trong phân tích kỹ thuật giá cổ phiếu. Nó được cho là tiết lộ những thay đổi về sức mạnh, hướng, đà và thời gian của xu hướng trong giá cổ phiếu.'; + + @override + String get infoRSI => 'Chỉ số sức mạnh tương đối (RSI) được công bố bởi J. Welles Wilder. Giá hiện tại được chuẩn hóa theo tỷ lệ phần trăm từ 0 đến 100. Flutter_chart_id của bộ dao động này gây hiểu nhầm vì nó không so sánh công cụ so với một công cụ hoặc bộ công cụ khác, mà đại diện cho giá hiện tại so với các phần gần đây khác trong độ dài cửa sổ nhìn lại đã chọn.'; + + @override + String get infoBB => 'Dải Bollinger (BB) có thể được sử dụng để đo mức cao hoặc thấp của giá so với các giao dịch trước đó.'; + + @override + String get infoMA => 'Đường trung bình động (MA) giúp xác định xu hướng thị trường tổng thể bằng cách lọc ra các biến động giá ngắn hạn. Sử dụng dữ liệu lịch sử, nó tính toán giá trung bình trong một khoảng thời gian cụ thể và vẽ một đường trên biểu đồ. Nếu đường MA di chuyển lên, đó là một chỉ báo của xu hướng tăng, xu hướng giảm nếu nó di chuyển xuống. Tín hiệu mua xảy ra khi giá di chuyển trên đường MA.'; + + @override + String get infoMaximumActiveIndicatorsAdded => 'Bạn đã thêm số lượng chỉ báo hoạt động tối đa.'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return 'Thêm $indicator'; + } + + @override + String get infoAddIndicator => 'Thêm chỉ báo'; + + @override + String get labelDeleteAllIndicators => 'Xóa tất cả các chỉ số'; + + @override + String get infoDeleteAllIndicators => 'Điều này sẽ xóa tất cả các chỉ số hoạt động.'; + + @override + String infoResetIndicators(Object indicator) { + return 'Thao tác này sẽ đặt lại chỉ báo $indicator về cài đặt mặc định của nó.'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return 'Xóa chỉ báo $indicator'; + } + + @override + String labelResetIndicator(Object indicator) { + return 'Đặt lại chỉ báo $indicator'; + } + + @override + String get infoDeleteIndicator => 'Bạn có chắc chắn muốn xóa chỉ báo này không?'; + + @override + String get labelCancel => 'Hủy'; + + @override + String get labelDelete => 'Xóa'; + + @override + String get labelDeleteAll => 'Xóa tất cả'; + + @override + String get infoUpto3indicatorsAllowed => 'Cho phép tối đa 3 chỉ số hoạt động.'; + + @override + String get infoNoActiveIndicators => 'Không có chỉ số hoạt động.'; + + @override + String get labelReset => 'Đặt lại'; + + @override + String get labelApply => 'Áp dụng'; + + @override + String get labelOK => 'ĐƯỢC'; + + @override + String get labelRSILine => 'Dòng RSI'; + + @override + String get labelPeriod => 'Thời kỳ'; + + @override + String get labelMinRange => 'Phạm vi tối thiểu'; + + @override + String get labelMaxRange => 'Phạm vi tối đa'; + + @override + String get labelSource => 'Nguồn'; + + @override + String get labelClose => 'Đóng'; + + @override + String get labelOpen => 'Mở'; + + @override + String get labelHigh => 'Cao'; + + @override + String get labelLow => 'Thấp'; + + @override + String get labelHl2 => 'Hl/2'; + + @override + String get labelHlc3 => 'Hlc/3'; + + @override + String get labelHlcc4 => 'Hlcc/4'; + + @override + String get labelOhlc4 => 'Ohlc/4'; + + @override + String get labelShowZones => 'Hiển thị khu vực'; + + @override + String get labelOverbought => 'Mua quá mức'; + + @override + String get labelOversold => 'Bán quá mức'; + + @override + String get labelMinSize => 'Kích thước tối thiểu'; + + @override + String get labelMaxSize => 'Kích thước tối đa'; + + @override + String get labelRange => 'Phạm vi'; + + @override + String get labelOverboughtLine => 'Dòng quá mua'; + + @override + String get labelOversoldLine => 'Dòng bán quá mức'; + + @override + String get labelMACDLine => 'Dòng MACD'; + + @override + String get labelFastMAPeriod => 'Thời gian MA nhanh'; + + @override + String get labelSlowMAPeriod => 'Thời gian MA chậm'; + + @override + String get labelSignalLine => 'Đường tín hiệu'; + + @override + String get labelSignalPeriod => 'Thời gian tín hiệu'; + + @override + String get labelIncreasingBar => 'Tăng thanh'; + + @override + String get labelDecreasingBar => 'Thanh giảm'; + + @override + String get labelBollingerBandsTop => 'Bollinger Bands hàng đầu'; + + @override + String get labelBollingerBandsMedian => 'Dải Bollinger trung bình'; + + @override + String get labelBollingerBandsBottom => 'Dải Bollinger phía dưới'; + + @override + String get labelChannelFill => 'Điền kênh'; + + @override + String get labelFillColor => 'Tô màu'; + + @override + String get labelStandardDeviations => 'Độ lệch chuẩn'; + + @override + String get labelMovingAverageType => 'Loại trung bình di chuyển'; + + @override + String get labelMALine => 'Dòng MA'; + + @override + String get labelOffset => 'Bù đắp'; + + @override + String get labelType => 'Kiểu'; + + @override + String get labelSimple => 'Đơn giản'; + + @override + String get labelExponential => 'Theo cấp số nhân'; + + @override + String get labelWeighted => 'Có trọng số'; + + @override + String get labelHull => 'Thân tàu'; + + @override + String get labelZeroLag => 'Không có độ trễ'; + + @override + String get labelTimeSeries => 'Chuỗi thời gian'; + + @override + String get labelWellesWilder => 'Welles Wilder'; + + @override + String get labelVariable => 'Biến'; + + @override + String get labelTriangular => 'Tam giác'; + + @override + String get label2Exponential => '2-cấp số nhân'; + + @override + String get label3Exponential => '3-cấp số nhân'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return 'Nhập giá trị giữa $min và $max'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return 'Phạm vi $min - $max'; + } + + @override + String get labelDrawingTools => 'Công cụ vẽ'; + + @override + String get labelTools => 'Dụng cụ'; + + @override + String get labelLine => 'Dòng'; + + @override + String get labelRay => 'cá đuối'; + + @override + String get informTapToSetFirstPoint => 'Nhấn để đặt điểm đầu tiên'; + + @override + String get informTapToSetFinalPoint => 'Nhấn để đặt điểm cuối cùng'; + + @override + String get informNoActiveDrawingTools => 'Không có công cụ vẽ hoạt động.'; + + @override + String get actionAddDrawingTool => 'Thêm công cụ vẽ'; + + @override + String get labelOf => 'của'; + + @override + String get labelDeleteAllDrawingTools => 'Xóa tất cả các công cụ vẽ'; + + @override + String get informDeleteAllDrawingTools => 'Thao tác này sẽ xóa tất cả các công cụ vẽ đang hoạt động.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_zh.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_zh.dart new file mode 100644 index 000000000..31db309b1 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations_zh.dart @@ -0,0 +1,916 @@ +import 'deriv_mobile_chart_wrapper_localizations.dart'; + +/// The translations for Chinese (`zh`). +class DerivMobileChartWrapperLocalizationsZh extends DerivMobileChartWrapperLocalizations { + DerivMobileChartWrapperLocalizationsZh([String locale = 'zh']) : super(locale); + + @override + String get labelIndicators => '指標'; + + @override + String get labelActive => '活躍'; + + @override + String get labelAll => '全部'; + + @override + String get labelMomentum => '動量'; + + @override + String get labelVolatility => '波動性'; + + @override + String get labelMovingAverages => '移動平均線'; + + @override + String get labelMACD => 'MACD'; + + @override + String get labelRelativeStrengthIndex => '相對強度指數(RSI)'; + + @override + String get labelRSI => 'RSI'; + + @override + String get labelBollingerBands => '布林格帶(BB)'; + + @override + String get labelBB => 'BB'; + + @override + String get labelMovingAverage => '移動平均值 (MA)'; + + @override + String get labelMA => 'MA'; + + @override + String get infoMACD => 'MACD 是交易指標,用於股票價格的技術分析。可顯示股票價格中趨勢的強度、方向、動量和持續時間的變化。'; + + @override + String get infoRSI => '相對強度指數(RSI)由 J. Welles Wilder 發布。將目前價格以 0 到 100 之間的百分比標準化。此振盪器的 flutter_chart_id 具有誤導性,因為它不與其他工具或一組工具相比較,而是代表所選回顧窗口長度內與其他最近的股票相對的目前價格。'; + + @override + String get infoBB => '布林格帶(BB)可用於測量與之前交易相對於價格的高度或低度。'; + + @override + String get infoMA => '移動平均線(MA)通過過濾短期價格波動來幫助識別整體市場趨勢。它使用歷史資料,計算特定期間的平均價格,並在圖表繪線。如果 MA 線向上移動,則是上升趨勢的指標,如果向下移動則是下降趨勢的指標。當價格走在 MA 線上方時,就會出現買入信號。'; + + @override + String get infoMaximumActiveIndicatorsAdded => '已新增使用中指標數目上限。'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return '新增 $indicator'; + } + + @override + String get infoAddIndicator => '新增指標'; + + @override + String get labelDeleteAllIndicators => '刪除所有指標'; + + @override + String get infoDeleteAllIndicators => '這將刪除所有活躍指標。'; + + @override + String infoResetIndicators(Object indicator) { + return '這將把 $indicator 指標重設為其預設設定。'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return '刪除 $indicator 指標'; + } + + @override + String labelResetIndicator(Object indicator) { + return '重設 $indicator 指標'; + } + + @override + String get infoDeleteIndicator => '確定要刪除此指標嗎?'; + + @override + String get labelCancel => '取消'; + + @override + String get labelDelete => '刪除'; + + @override + String get labelDeleteAll => '刪除全部'; + + @override + String get infoUpto3indicatorsAllowed => '最多允許 3 個活躍指標。'; + + @override + String get infoNoActiveIndicators => '沒有活躍指標。'; + + @override + String get labelReset => '重設'; + + @override + String get labelApply => '申請'; + + @override + String get labelOK => '好'; + + @override + String get labelRSILine => 'RSI 線'; + + @override + String get labelPeriod => '週期'; + + @override + String get labelMinRange => '最小範圍'; + + @override + String get labelMaxRange => '最大範圍'; + + @override + String get labelSource => '來源'; + + @override + String get labelClose => '關閉'; + + @override + String get labelOpen => '開啟'; + + @override + String get labelHigh => 'High'; + + @override + String get labelLow => 'Low'; + + @override + String get labelHl2 => 'Hl/2'; + + @override + String get labelHlc3 => 'Hlc/3'; + + @override + String get labelHlcc4 => 'Hlcc/4'; + + @override + String get labelOhlc4 => 'Ohlc/4'; + + @override + String get labelShowZones => '顯示區域'; + + @override + String get labelOverbought => '超買'; + + @override + String get labelOversold => '超賣'; + + @override + String get labelMinSize => '最小尺寸'; + + @override + String get labelMaxSize => '最大尺寸'; + + @override + String get labelRange => '範圍'; + + @override + String get labelOverboughtLine => '超買線'; + + @override + String get labelOversoldLine => '超賣線'; + + @override + String get labelMACDLine => 'MACD 線'; + + @override + String get labelFastMAPeriod => '快速 MA 週期'; + + @override + String get labelSlowMAPeriod => '慢速 MA 週期'; + + @override + String get labelSignalLine => '信號線'; + + @override + String get labelSignalPeriod => '信號週期'; + + @override + String get labelIncreasingBar => '增加條型線'; + + @override + String get labelDecreasingBar => '減少條型線'; + + @override + String get labelBollingerBandsTop => '布林格帶頂部'; + + @override + String get labelBollingerBandsMedian => '布林格帶中位數'; + + @override + String get labelBollingerBandsBottom => '布林格帶底部'; + + @override + String get labelChannelFill => '通道填充'; + + @override + String get labelFillColor => '填色'; + + @override + String get labelStandardDeviations => '標準偏差'; + + @override + String get labelMovingAverageType => '移動平均線類型'; + + @override + String get labelMALine => 'MA 線'; + + @override + String get labelOffset => '偏移'; + + @override + String get labelType => '類型'; + + @override + String get labelSimple => '簡單'; + + @override + String get labelExponential => '指數式'; + + @override + String get labelWeighted => '加權'; + + @override + String get labelHull => 'Hull'; + + @override + String get labelZeroLag => '零延遲'; + + @override + String get labelTimeSeries => '時間序列'; + + @override + String get labelWellesWilder => 'Welles Wilder'; + + @override + String get labelVariable => '變量'; + + @override + String get labelTriangular => '三角形'; + + @override + String get label2Exponential => '2-指數式'; + + @override + String get label3Exponential => '3-指數式'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return '輸入介於 $min 和 $max 之間的值'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return '範圍 $min - $max'; + } + + @override + String get labelDrawingTools => '繪圖工具'; + + @override + String get labelTools => '工具'; + + @override + String get labelLine => '線'; + + @override + String get labelRay => '射線'; + + @override + String get informTapToSetFirstPoint => '輕觸以設定第一點'; + + @override + String get informTapToSetFinalPoint => '輕觸以設定最終點'; + + @override + String get informNoActiveDrawingTools => '沒有可用的繪圖工具。'; + + @override + String get actionAddDrawingTool => '新增繪圖工具'; + + @override + String get labelOf => '的'; + + @override + String get labelDeleteAllDrawingTools => '刪除所有繪圖工具'; + + @override + String get informDeleteAllDrawingTools => '這將刪除所有作用中的繪圖工具。'; +} + +/// The translations for Chinese, as used in China (`zh_CN`). +class DerivMobileChartWrapperLocalizationsZhCn extends DerivMobileChartWrapperLocalizationsZh { + DerivMobileChartWrapperLocalizationsZhCn(): super('zh_CN'); + + @override + String get labelIndicators => '指标'; + + @override + String get labelActive => '活跃'; + + @override + String get labelAll => '全部'; + + @override + String get labelMomentum => '动量'; + + @override + String get labelVolatility => '波动率'; + + @override + String get labelMovingAverages => '移动平均线'; + + @override + String get labelMACD => 'MACD'; + + @override + String get labelRelativeStrengthIndex => '相对强弱指数 (RSI)'; + + @override + String get labelRSI => 'RSI'; + + @override + String get labelBollingerBands => '布林带 (BB)'; + + @override + String get labelBB => 'BB'; + + @override + String get labelMovingAverage => '移动平均线 (MA)'; + + @override + String get labelMA => 'MA'; + + @override + String get infoMACD => 'MACD 是用于股价技术分析的交易指标。可揭示股票价格走势的强度、方向、动量和持续时间的变化。'; + + @override + String get infoRSI => '相对强度指数(RSI)由 J. Welles Wilder 发布。当前价格按介于 0 到 100 之间的百分比进行标准化。该振荡器的 flutter_chart_id 具有误导性,因为它不将该工具与另一种工具或一组工具相比较,而是代表选定回顾窗口长度内相对于其他近期产品的当前价格。'; + + @override + String get infoBB => '布林带(BB)可用于衡量价格相对于先前交易的高点或低点。'; + + @override + String get infoMA => '移动平均线(MA)通过过滤短期价格波动来帮助识别整体市场趋势。它使用历史数据计算特定时期内的平均价格,并在图表上绘线。如果 MA 线向上移动,则表示上升趋势,如果向下移动,则为下降趋势。当价格升至均线上方时,就会出现买入信号。'; + + @override + String get infoMaximumActiveIndicatorsAdded => '已经添加了最大数量的活跃指标。'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return '添加 $indicator'; + } + + @override + String get infoAddIndicator => '添加指标'; + + @override + String get labelDeleteAllIndicators => '删除所有指标'; + + @override + String get infoDeleteAllIndicators => '这将删除所有活跃指标。'; + + @override + String infoResetIndicators(Object indicator) { + return '这会将$indicator 指标重置为其默认设置。'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return '删除 $indicator 指标'; + } + + @override + String labelResetIndicator(Object indicator) { + return '重置 $indicator 指标'; + } + + @override + String get infoDeleteIndicator => '确定要删除此指标吗?'; + + @override + String get labelCancel => '取消'; + + @override + String get labelDelete => '删除'; + + @override + String get labelDeleteAll => '全部删除'; + + @override + String get infoUpto3indicatorsAllowed => '最多允许 3 个活跃指标。'; + + @override + String get infoNoActiveIndicators => '没有活跃指标。'; + + @override + String get labelReset => '重置'; + + @override + String get labelApply => '申请'; + + @override + String get labelOK => 'OK'; + + @override + String get labelRSILine => 'RSI 线'; + + @override + String get labelPeriod => '周期'; + + @override + String get labelMinRange => '最小范围'; + + @override + String get labelMaxRange => '最大范围'; + + @override + String get labelSource => '来源'; + + @override + String get labelClose => '关闭'; + + @override + String get labelOpen => '打开'; + + @override + String get labelHigh => 'High'; + + @override + String get labelLow => 'Low'; + + @override + String get labelHl2 => 'Hl/2'; + + @override + String get labelHlc3 => 'Hlc/3'; + + @override + String get labelHlcc4 => 'Hlcc/4'; + + @override + String get labelOhlc4 => 'Ohlc/4'; + + @override + String get labelShowZones => '显示区域'; + + @override + String get labelOverbought => '超买'; + + @override + String get labelOversold => '超卖'; + + @override + String get labelMinSize => '最小尺寸'; + + @override + String get labelMaxSize => '最大尺寸'; + + @override + String get labelRange => '范围'; + + @override + String get labelOverboughtLine => '超买线'; + + @override + String get labelOversoldLine => '超卖线'; + + @override + String get labelMACDLine => 'MACD 线'; + + @override + String get labelFastMAPeriod => '快速均线周期'; + + @override + String get labelSlowMAPeriod => '慢速均线周期'; + + @override + String get labelSignalLine => '信号线'; + + @override + String get labelSignalPeriod => '信号周期'; + + @override + String get labelIncreasingBar => '增加柱线'; + + @override + String get labelDecreasingBar => '减小柱线'; + + @override + String get labelBollingerBandsTop => '布林带顶部'; + + @override + String get labelBollingerBandsMedian => '布林带中位数'; + + @override + String get labelBollingerBandsBottom => '布林带底部'; + + @override + String get labelChannelFill => '频道填充'; + + @override + String get labelFillColor => '填充颜色'; + + @override + String get labelStandardDeviations => '标准偏差'; + + @override + String get labelMovingAverageType => '移动平均线类型'; + + @override + String get labelMALine => 'MA 线'; + + @override + String get labelOffset => '抵消'; + + @override + String get labelType => '类型'; + + @override + String get labelSimple => '简单'; + + @override + String get labelExponential => '指数'; + + @override + String get labelWeighted => '加权'; + + @override + String get labelHull => 'Hull'; + + @override + String get labelZeroLag => '零延迟'; + + @override + String get labelTimeSeries => '时间序列'; + + @override + String get labelWellesWilder => 'Welles Wilder'; + + @override + String get labelVariable => '变量'; + + @override + String get labelTriangular => '三角形'; + + @override + String get label2Exponential => '2-指数'; + + @override + String get label3Exponential => '3-指数'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return '输入介于 $min 和 $max 之间的值'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return '范围 $min - $max'; + } + + @override + String get labelDrawingTools => '绘图工具'; + + @override + String get labelTools => '工具'; + + @override + String get labelLine => '线'; + + @override + String get labelRay => '射线'; + + @override + String get informTapToSetFirstPoint => '轻触即可设置第一个点'; + + @override + String get informTapToSetFinalPoint => '轻触即可设置终点'; + + @override + String get informNoActiveDrawingTools => '没有有效的绘图工具。'; + + @override + String get actionAddDrawingTool => '添加绘图工具'; + + @override + String get labelOf => '的'; + + @override + String get labelDeleteAllDrawingTools => '删除所有绘图工具'; + + @override + String get informDeleteAllDrawingTools => '这将删除所有活跃的绘图工具。'; +} + +/// The translations for Chinese, as used in Taiwan (`zh_TW`). +class DerivMobileChartWrapperLocalizationsZhTw extends DerivMobileChartWrapperLocalizationsZh { + DerivMobileChartWrapperLocalizationsZhTw(): super('zh_TW'); + + @override + String get labelIndicators => '指標'; + + @override + String get labelActive => '活躍'; + + @override + String get labelAll => '全部'; + + @override + String get labelMomentum => '動量'; + + @override + String get labelVolatility => '波動性'; + + @override + String get labelMovingAverages => '移動平均線'; + + @override + String get labelMACD => 'MACD'; + + @override + String get labelRelativeStrengthIndex => '相對強度指數(RSI)'; + + @override + String get labelRSI => 'RSI'; + + @override + String get labelBollingerBands => '布林格帶(BB)'; + + @override + String get labelBB => 'BB'; + + @override + String get labelMovingAverage => '移動平均值 (MA)'; + + @override + String get labelMA => 'MA'; + + @override + String get infoMACD => 'MACD 是交易指標,用於股票價格的技術分析。可顯示股票價格中趨勢的強度、方向、動量和持續時間的變化。'; + + @override + String get infoRSI => '相對強度指數(RSI)由 J. Welles Wilder 發布。將目前價格以 0 到 100 之間的百分比標準化。此振盪器的 flutter_chart_id 具有誤導性,因為它不與其他工具或一組工具相比較,而是代表所選回顧窗口長度內與其他最近的股票相對的目前價格。'; + + @override + String get infoBB => '布林格帶(BB)可用於測量與之前交易相對於價格的高度或低度。'; + + @override + String get infoMA => '移動平均線(MA)通過過濾短期價格波動來幫助識別整體市場趨勢。它使用歷史資料,計算特定期間的平均價格,並在圖表繪線。如果 MA 線向上移動,則是上升趨勢的指標,如果向下移動則是下降趨勢的指標。當價格走在 MA 線上方時,就會出現買入信號。'; + + @override + String get infoMaximumActiveIndicatorsAdded => '已新增使用中指標數目上限。'; + + @override + String infoAddSelectedIndicator(Object indicator) { + return '新增 $indicator'; + } + + @override + String get infoAddIndicator => '新增指標'; + + @override + String get labelDeleteAllIndicators => '刪除所有指標'; + + @override + String get infoDeleteAllIndicators => '這將刪除所有活躍指標。'; + + @override + String infoResetIndicators(Object indicator) { + return '這將把 $indicator 指標重設為其預設設定。'; + } + + @override + String labelDeleteIndicator(Object indicator) { + return '刪除 $indicator 指標'; + } + + @override + String labelResetIndicator(Object indicator) { + return '重設 $indicator 指標'; + } + + @override + String get infoDeleteIndicator => '確定要刪除此指標嗎?'; + + @override + String get labelCancel => '取消'; + + @override + String get labelDelete => '刪除'; + + @override + String get labelDeleteAll => '刪除全部'; + + @override + String get infoUpto3indicatorsAllowed => '最多允許 3 個活躍指標。'; + + @override + String get infoNoActiveIndicators => '沒有活躍指標。'; + + @override + String get labelReset => '重設'; + + @override + String get labelApply => '申請'; + + @override + String get labelOK => '好'; + + @override + String get labelRSILine => 'RSI 線'; + + @override + String get labelPeriod => '週期'; + + @override + String get labelMinRange => '最小範圍'; + + @override + String get labelMaxRange => '最大範圍'; + + @override + String get labelSource => '來源'; + + @override + String get labelClose => '關閉'; + + @override + String get labelOpen => '開啟'; + + @override + String get labelHigh => 'High'; + + @override + String get labelLow => 'Low'; + + @override + String get labelHl2 => 'Hl/2'; + + @override + String get labelHlc3 => 'Hlc/3'; + + @override + String get labelHlcc4 => 'Hlcc/4'; + + @override + String get labelOhlc4 => 'Ohlc/4'; + + @override + String get labelShowZones => '顯示區域'; + + @override + String get labelOverbought => '超買'; + + @override + String get labelOversold => '超賣'; + + @override + String get labelMinSize => '最小尺寸'; + + @override + String get labelMaxSize => '最大尺寸'; + + @override + String get labelRange => '範圍'; + + @override + String get labelOverboughtLine => '超買線'; + + @override + String get labelOversoldLine => '超賣線'; + + @override + String get labelMACDLine => 'MACD 線'; + + @override + String get labelFastMAPeriod => '快速 MA 週期'; + + @override + String get labelSlowMAPeriod => '慢速 MA 週期'; + + @override + String get labelSignalLine => '信號線'; + + @override + String get labelSignalPeriod => '信號週期'; + + @override + String get labelIncreasingBar => '增加條型線'; + + @override + String get labelDecreasingBar => '減少條型線'; + + @override + String get labelBollingerBandsTop => '布林格帶頂部'; + + @override + String get labelBollingerBandsMedian => '布林格帶中位數'; + + @override + String get labelBollingerBandsBottom => '布林格帶底部'; + + @override + String get labelChannelFill => '通道填充'; + + @override + String get labelFillColor => '填色'; + + @override + String get labelStandardDeviations => '標準偏差'; + + @override + String get labelMovingAverageType => '移動平均線類型'; + + @override + String get labelMALine => 'MA 線'; + + @override + String get labelOffset => '偏移'; + + @override + String get labelType => '類型'; + + @override + String get labelSimple => '簡單'; + + @override + String get labelExponential => '指數式'; + + @override + String get labelWeighted => '加權'; + + @override + String get labelHull => 'Hull'; + + @override + String get labelZeroLag => '零延遲'; + + @override + String get labelTimeSeries => '時間序列'; + + @override + String get labelWellesWilder => 'Welles Wilder'; + + @override + String get labelVariable => '變量'; + + @override + String get labelTriangular => '三角形'; + + @override + String get label2Exponential => '2-指數式'; + + @override + String get label3Exponential => '3-指數式'; + + @override + String warnEnterValueBetweenMinMax(Object max, Object min) { + return '輸入介於 $min 和 $max 之間的值'; + } + + @override + String warnRangeMinMax(Object max, Object min) { + return '範圍 $min - $max'; + } + + @override + String get labelDrawingTools => '繪圖工具'; + + @override + String get labelTools => '工具'; + + @override + String get labelLine => '線'; + + @override + String get labelRay => '射線'; + + @override + String get informTapToSetFirstPoint => '輕觸以設定第一點'; + + @override + String get informTapToSetFinalPoint => '輕觸以設定最終點'; + + @override + String get informNoActiveDrawingTools => '沒有可用的繪圖工具。'; + + @override + String get actionAddDrawingTool => '新增繪圖工具'; + + @override + String get labelOf => '的'; + + @override + String get labelDeleteAllDrawingTools => '刪除所有繪圖工具'; + + @override + String get informDeleteAllDrawingTools => '這將刪除所有作用中的繪圖工具。'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations.dart new file mode 100644 index 000000000..537a85d3a --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations.dart @@ -0,0 +1,523 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'deriv_passkeys_localizations_ar.dart'; +import 'deriv_passkeys_localizations_bn.dart'; +import 'deriv_passkeys_localizations_de.dart'; +import 'deriv_passkeys_localizations_en.dart'; +import 'deriv_passkeys_localizations_es.dart'; +import 'deriv_passkeys_localizations_fr.dart'; +import 'deriv_passkeys_localizations_it.dart'; +import 'deriv_passkeys_localizations_km.dart'; +import 'deriv_passkeys_localizations_ko.dart'; +import 'deriv_passkeys_localizations_mn.dart'; +import 'deriv_passkeys_localizations_pl.dart'; +import 'deriv_passkeys_localizations_pt.dart'; +import 'deriv_passkeys_localizations_ru.dart'; +import 'deriv_passkeys_localizations_si.dart'; +import 'deriv_passkeys_localizations_sw.dart'; +import 'deriv_passkeys_localizations_th.dart'; +import 'deriv_passkeys_localizations_tr.dart'; +import 'deriv_passkeys_localizations_uz.dart'; +import 'deriv_passkeys_localizations_vi.dart'; +import 'deriv_passkeys_localizations_zh.dart'; + +/// Callers can lookup localized strings with an instance of DerivPasskeysLocalizations +/// returned by `DerivPasskeysLocalizations.of(context)`. +/// +/// Applications need to include `DerivPasskeysLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'deriv_passkeys/deriv_passkeys_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: DerivPasskeysLocalizations.localizationsDelegates, +/// supportedLocales: DerivPasskeysLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the DerivPasskeysLocalizations.supportedLocales +/// property. +abstract class DerivPasskeysLocalizations { + DerivPasskeysLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static DerivPasskeysLocalizations of(BuildContext context) { + return Localizations.of(context, DerivPasskeysLocalizations)!; + } + + static const LocalizationsDelegate delegate = _DerivPasskeysLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [ + Locale('ar'), + Locale('bn'), + Locale('de'), + Locale('en'), + Locale('es'), + Locale('fr'), + Locale('it'), + Locale('km'), + Locale('ko'), + Locale('mn'), + Locale('pl'), + Locale('pt'), + Locale('ru'), + Locale('si'), + Locale('sw'), + Locale('th'), + Locale('tr'), + Locale('uz'), + Locale('vi'), + Locale('zh', 'CN'), + Locale('zh', 'TW'), + Locale('zh') + ]; + + /// No description provided for @passkeyCreatedSuccessTitle. + /// + /// In en, this message translates to: + /// **'Success!'** + String get passkeyCreatedSuccessTitle; + + /// No description provided for @passkeyCreatedSuccessMessage. + /// + /// In en, this message translates to: + /// **'Your account is now secured with a passkey. Manage your passkey through your {platformName} account settings.'** + String passkeyCreatedSuccessMessage(String platformName); + + /// No description provided for @continueButtonText. + /// + /// In en, this message translates to: + /// **'Continue'** + String get continueButtonText; + + /// No description provided for @unexpectedError. + /// + /// In en, this message translates to: + /// **'An unexpected error occurred!'** + String get unexpectedError; + + /// No description provided for @unexpectedErrorDescription. + /// + /// In en, this message translates to: + /// **'Please try again later.'** + String get unexpectedErrorDescription; + + /// No description provided for @ok. + /// + /// In en, this message translates to: + /// **'Ok'** + String get ok; + + /// No description provided for @experienceSaferLogins. + /// + /// In en, this message translates to: + /// **'Experience safer logins'** + String get experienceSaferLogins; + + /// No description provided for @enhanceSecurity. + /// + /// In en, this message translates to: + /// **'Enhanced security is just a tap away.'** + String get enhanceSecurity; + + /// No description provided for @here. + /// + /// In en, this message translates to: + /// **'here'** + String get here; + + /// No description provided for @effortlessLogin. + /// + /// In en, this message translates to: + /// **'Effortless login with passkeys'** + String get effortlessLogin; + + /// No description provided for @whatArePasskeys. + /// + /// In en, this message translates to: + /// **'What are passkeys?'** + String get whatArePasskeys; + + /// No description provided for @whatArePasskeysDescriptionPoint1. + /// + /// In en, this message translates to: + /// **'Secure alternative to passwords.'** + String get whatArePasskeysDescriptionPoint1; + + /// No description provided for @whatArePasskeysDescriptionPoint2. + /// + /// In en, this message translates to: + /// **'Unlock your account like your phone - with biometrics, face scan or PIN.'** + String get whatArePasskeysDescriptionPoint2; + + /// No description provided for @whyPasskeys. + /// + /// In en, this message translates to: + /// **'Why passkeys?'** + String get whyPasskeys; + + /// No description provided for @whyPasskeysDescription1. + /// + /// In en, this message translates to: + /// **'Extra security layer.'** + String get whyPasskeysDescription1; + + /// No description provided for @whyPasskeysDescription2. + /// + /// In en, this message translates to: + /// **'Shields against unauthorised access and phishing.'** + String get whyPasskeysDescription2; + + /// No description provided for @howToCreatePasskey. + /// + /// In en, this message translates to: + /// **'How to create a passkey?'** + String get howToCreatePasskey; + + /// No description provided for @howToCreatePasskeyDescription1. + /// + /// In en, this message translates to: + /// **'Go to ‘Account Settings’ on Deriv.'** + String get howToCreatePasskeyDescription1; + + /// No description provided for @howToCreatePasskeyDescription2. + /// + /// In en, this message translates to: + /// **'You can create one passkey per device.'** + String get howToCreatePasskeyDescription2; + + /// No description provided for @p2pHowToCreatePasskey. + /// + /// In en, this message translates to: + /// **'How to create passkey?'** + String get p2pHowToCreatePasskey; + + /// No description provided for @p2pHowToCreatePasskeyDescription1. + /// + /// In en, this message translates to: + /// **'Go to ‘Profile‘ in your Deriv P2P app.'** + String get p2pHowToCreatePasskeyDescription1; + + /// No description provided for @p2pHowToCreatePasskeyDescription2. + /// + /// In en, this message translates to: + /// **'Tap ‘Passkeys‘ to create your passkey.'** + String get p2pHowToCreatePasskeyDescription2; + + /// No description provided for @whereArePasskeysSaved. + /// + /// In en, this message translates to: + /// **'Where are passkeys saved?'** + String get whereArePasskeysSaved; + + /// No description provided for @whereArePasskeysSavedDescriptionAndroid. + /// + /// In en, this message translates to: + /// **'Android: Google password manager.'** + String get whereArePasskeysSavedDescriptionAndroid; + + /// No description provided for @whereArePasskeysSavedDescriptionIOS. + /// + /// In en, this message translates to: + /// **'iOS: iCloud keychain.'** + String get whereArePasskeysSavedDescriptionIOS; + + /// No description provided for @whatHappensIfEmailChanged. + /// + /// In en, this message translates to: + /// **'What happens if my Deriv account email is changed?'** + String get whatHappensIfEmailChanged; + + /// No description provided for @whatHappensIfEmailChangedDescription1. + /// + /// In en, this message translates to: + /// **'No problem! Your passkey still works.'** + String get whatHappensIfEmailChangedDescription1; + + /// No description provided for @whatHappensIfEmailChangedDescription2. + /// + /// In en, this message translates to: + /// **'Sign in to Deriv with your existing passkey.'** + String get whatHappensIfEmailChangedDescription2; + + /// No description provided for @tips. + /// + /// In en, this message translates to: + /// **'Tips'** + String get tips; + + /// No description provided for @beforeUsingPasskeys. + /// + /// In en, this message translates to: + /// **'Before using passkeys'** + String get beforeUsingPasskeys; + + /// No description provided for @enableScreenLock. + /// + /// In en, this message translates to: + /// **'Enable screen lock on your device.'** + String get enableScreenLock; + + /// No description provided for @signInGoogleOrIcloud. + /// + /// In en, this message translates to: + /// **'Sign in to your Google or iCloud account.'** + String get signInGoogleOrIcloud; + + /// No description provided for @enableBluetooth. + /// + /// In en, this message translates to: + /// **'Enable Bluetooth.'** + String get enableBluetooth; + + /// No description provided for @noPasskeyFound. + /// + /// In en, this message translates to: + /// **'No passkey found!'** + String get noPasskeyFound; + + /// No description provided for @noPasskeyFoundDescription. + /// + /// In en, this message translates to: + /// **'Please create a passkey to use this feature.'** + String get noPasskeyFoundDescription; + + /// No description provided for @maybeLater. + /// + /// In en, this message translates to: + /// **'Maybe later'** + String get maybeLater; + + /// No description provided for @effortlessLoginWithPasskeys. + /// + /// In en, this message translates to: + /// **'Effortless login with passkeys'** + String get effortlessLoginWithPasskeys; + + /// No description provided for @learnMoreAboutPasskeys. + /// + /// In en, this message translates to: + /// **'Learn more about passkeys'** + String get learnMoreAboutPasskeys; + + /// No description provided for @noNeedToRememberPassword. + /// + /// In en, this message translates to: + /// **'No need to remember a password'** + String get noNeedToRememberPassword; + + /// No description provided for @useYourBiometrics. + /// + /// In en, this message translates to: + /// **'Enhanced security with biometrics or screen lock'** + String get useYourBiometrics; + + /// No description provided for @syncAcrossDevices. + /// + /// In en, this message translates to: + /// **'Sync across devices'** + String get syncAcrossDevices; + + /// No description provided for @createPasskey. + /// + /// In en, this message translates to: + /// **'Create passkey'** + String get createPasskey; + + /// No description provided for @unsupportedPlatform. + /// + /// In en, this message translates to: + /// **'Unsupported Platform'** + String get unsupportedPlatform; + + /// No description provided for @storedOn. + /// + /// In en, this message translates to: + /// **'Stored on'** + String get storedOn; + + /// No description provided for @lastUsed. + /// + /// In en, this message translates to: + /// **'Last used'** + String get lastUsed; + + /// No description provided for @rename. + /// + /// In en, this message translates to: + /// **'Rename'** + String get rename; + + /// No description provided for @revoke. + /// + /// In en, this message translates to: + /// **'Revoke'** + String get revoke; + + /// No description provided for @continueTradingButtonText. + /// + /// In en, this message translates to: + /// **'Continue trading'** + String get continueTradingButtonText; + + /// No description provided for @addMorePasskeysButtonText. + /// + /// In en, this message translates to: + /// **'Add more passkeys'** + String get addMorePasskeysButtonText; + + /// No description provided for @unableToSetupPasskey. + /// + /// In en, this message translates to: + /// **'Unable to setup passkey'** + String get unableToSetupPasskey; + + /// No description provided for @unableToSetupPasskeyDescription. + /// + /// In en, this message translates to: + /// **'We encountered an issue while setting up your passkey. The process might have been interrupted, or the session timed out. Please try again.'** + String get unableToSetupPasskeyDescription; + + /// No description provided for @passkeysOffErrorTitle. + /// + /// In en, this message translates to: + /// **'The Passkeys service is unavailable'** + String get passkeysOffErrorTitle; + + /// No description provided for @never. + /// + /// In en, this message translates to: + /// **'Never'** + String get never; + + /// No description provided for @unable_to_process_your_request. + /// + /// In en, this message translates to: + /// **'Unable to process your request'** + String get unable_to_process_your_request; + + /// No description provided for @unable_to_process_your_request_description. + /// + /// In en, this message translates to: + /// **'We’re experiencing a temporary issue in processing your request. Please try again later.'** + String get unable_to_process_your_request_description; +} + +class _DerivPasskeysLocalizationsDelegate extends LocalizationsDelegate { + const _DerivPasskeysLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupDerivPasskeysLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => ['ar', 'bn', 'de', 'en', 'es', 'fr', 'it', 'km', 'ko', 'mn', 'pl', 'pt', 'ru', 'si', 'sw', 'th', 'tr', 'uz', 'vi', 'zh'].contains(locale.languageCode); + + @override + bool shouldReload(_DerivPasskeysLocalizationsDelegate old) => false; +} + +DerivPasskeysLocalizations lookupDerivPasskeysLocalizations(Locale locale) { + + // Lookup logic when language+country codes are specified. + switch (locale.languageCode) { + case 'zh': { + switch (locale.countryCode) { + case 'CN': return DerivPasskeysLocalizationsZhCn(); +case 'TW': return DerivPasskeysLocalizationsZhTw(); + } + break; + } + } + + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'ar': return DerivPasskeysLocalizationsAr(); + case 'bn': return DerivPasskeysLocalizationsBn(); + case 'de': return DerivPasskeysLocalizationsDe(); + case 'en': return DerivPasskeysLocalizationsEn(); + case 'es': return DerivPasskeysLocalizationsEs(); + case 'fr': return DerivPasskeysLocalizationsFr(); + case 'it': return DerivPasskeysLocalizationsIt(); + case 'km': return DerivPasskeysLocalizationsKm(); + case 'ko': return DerivPasskeysLocalizationsKo(); + case 'mn': return DerivPasskeysLocalizationsMn(); + case 'pl': return DerivPasskeysLocalizationsPl(); + case 'pt': return DerivPasskeysLocalizationsPt(); + case 'ru': return DerivPasskeysLocalizationsRu(); + case 'si': return DerivPasskeysLocalizationsSi(); + case 'sw': return DerivPasskeysLocalizationsSw(); + case 'th': return DerivPasskeysLocalizationsTh(); + case 'tr': return DerivPasskeysLocalizationsTr(); + case 'uz': return DerivPasskeysLocalizationsUz(); + case 'vi': return DerivPasskeysLocalizationsVi(); + case 'zh': return DerivPasskeysLocalizationsZh(); + } + + throw FlutterError( + 'DerivPasskeysLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.' + ); +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_ar.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_ar.dart new file mode 100644 index 000000000..125ab7043 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_ar.dart @@ -0,0 +1,173 @@ +import 'deriv_passkeys_localizations.dart'; + +/// The translations for Arabic (`ar`). +class DerivPasskeysLocalizationsAr extends DerivPasskeysLocalizations { + DerivPasskeysLocalizationsAr([String locale = 'ar']) : super(locale); + + @override + String get passkeyCreatedSuccessTitle => 'النجاح!'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return 'تم تأمين حسابك الآن باستخدام Passkey. قم بإدارة Passkey الخاص بك من خلال إعدادات حساب $platformName الخاص بك.'; + } + + @override + String get continueButtonText => 'استمر'; + + @override + String get unexpectedError => 'حدث خطأ غير متوقع!'; + + @override + String get unexpectedErrorDescription => 'يرجى المحاولة لاحقا.'; + + @override + String get ok => 'Ok'; + + @override + String get experienceSaferLogins => 'اختبر عمليات تسجيل دخول أكثر أمانًا'; + + @override + String get enhanceSecurity => 'الأمان المحسّن على بُعد نقرة واحدة فقط.'; + + @override + String get here => 'هنا'; + + @override + String get effortlessLogin => 'تسجيل الدخول بسهولة باستخدام Passkeys'; + + @override + String get whatArePasskeys => 'ما هي passkeys؟'; + + @override + String get whatArePasskeysDescriptionPoint1 => 'بديل آمن لكلمات المرور.'; + + @override + String get whatArePasskeysDescriptionPoint2 => 'قم بإلغاء قفل حسابك مثل هاتفك - باستخدام القياسات الحيوية أو مسح الوجه أو رقم التعريف الشخصي.'; + + @override + String get whyPasskeys => 'لماذا Passkey؟'; + + @override + String get whyPasskeysDescription1 => 'طبقة أمان إضافية.'; + + @override + String get whyPasskeysDescription2 => 'الحماية من الوصول غير المصرح به والتصيد الاحتيالي.'; + + @override + String get howToCreatePasskey => 'كيفية إنشاء passkey؟'; + + @override + String get howToCreatePasskeyDescription1 => 'انتقل إلى \"إعدادات الحساب\" على المشتقات.'; + + @override + String get howToCreatePasskeyDescription2 => 'يمكنك إنشاء Passkey واحد لكل جهاز.'; + + @override + String get p2pHowToCreatePasskey => 'كيفية إنشاء مفتاح مرور؟'; + + @override + String get p2pHowToCreatePasskeyDescription1 => 'انتقل إلى «الملف الشخصي» في تطبيق Deriv P2P الخاص بك.'; + + @override + String get p2pHowToCreatePasskeyDescription2 => 'انقر فوق «مفاتيح المرور» لإنشاء مفتاح المرور الخاص بك.'; + + @override + String get whereArePasskeysSaved => 'أين يتم حفظ passkeys؟'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'أندرويد: مدير كلمات مرور Google.'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS: سلسلة مفاتيح iCloud.'; + + @override + String get whatHappensIfEmailChanged => 'ماذا يحدث إذا تم تغيير البريد الإلكتروني لحساب Deriv الخاص بي؟'; + + @override + String get whatHappensIfEmailChangedDescription1 => 'لا توجد مشكلة! لا يزال مفتاح Passkey بك يعمل.'; + + @override + String get whatHappensIfEmailChangedDescription2 => 'سجّل الدخول إلى Deriv باستخدام مفتاح Passkey لديك.'; + + @override + String get tips => 'نصائح'; + + @override + String get beforeUsingPasskeys => 'قبل استخدام Passkey'; + + @override + String get enableScreenLock => 'قم بتمكين قفل الشاشة على جهازك.'; + + @override + String get signInGoogleOrIcloud => 'قم بتسجيل الدخول إلى حساب Google أو iCloud الخاص بك.'; + + @override + String get enableBluetooth => 'قم بتمكين البلوتوث.'; + + @override + String get noPasskeyFound => 'لم يتم العثور على passkey!'; + + @override + String get noPasskeyFoundDescription => 'يرجى إنشاء passkey لاستخدام هذه الميزة.'; + + @override + String get maybeLater => 'ربما في وقت لاحق'; + + @override + String get effortlessLoginWithPasskeys => 'تسجيل الدخول بسهولة باستخدام Passkeys'; + + @override + String get learnMoreAboutPasskeys => 'تعرف على المزيد حول passkeys'; + + @override + String get noNeedToRememberPassword => 'لا حاجة لتذكر كلمة المرور'; + + @override + String get useYourBiometrics => 'أمان محسّن باستخدام القياسات الحيوية أو قفل الشاشة'; + + @override + String get syncAcrossDevices => 'المزامنة عبر الأجهزة'; + + @override + String get createPasskey => 'إنشاء Passkey'; + + @override + String get unsupportedPlatform => 'منصة غير مدعومة'; + + @override + String get storedOn => 'تم تخزينها على'; + + @override + String get lastUsed => 'آخر استخدام'; + + @override + String get rename => 'إعادة تسمية'; + + @override + String get revoke => 'إلغاء'; + + @override + String get continueTradingButtonText => 'استمر في التداول'; + + @override + String get addMorePasskeysButtonText => 'أضف المزيد من passkeys'; + + @override + String get unableToSetupPasskey => 'تعذر إعداد مفتاح Passkey'; + + @override + String get unableToSetupPasskeyDescription => 'لقد واجهنا مشكلة أثناء إعداد Passkey الخاص بك. ربما تمت مقاطعة العملية، أو ربما انتهت مهلة الجلسة. يرجى المحاولة مرة أخرى.'; + + @override + String get passkeysOffErrorTitle => 'خدمة Passkeys غير متوفرة'; + + @override + String get never => 'أبداً'; + + @override + String get unable_to_process_your_request => 'غير قادر على معالجة طلبك'; + + @override + String get unable_to_process_your_request_description => 'نحن نواجه مشكلة مؤقتة في معالجة طلبك. يرجى المحاولة مرة أخرى لاحقًا.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_bn.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_bn.dart new file mode 100644 index 000000000..35a823892 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_bn.dart @@ -0,0 +1,173 @@ +import 'deriv_passkeys_localizations.dart'; + +/// The translations for Bengali Bangla (`bn`). +class DerivPasskeysLocalizationsBn extends DerivPasskeysLocalizations { + DerivPasskeysLocalizationsBn([String locale = 'bn']) : super(locale); + + @override + String get passkeyCreatedSuccessTitle => 'সফলতা!'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return 'আপনার অ্যাকাউন্ট এখন একটি পাসকি দিয়ে সুরক্ষিত৷ আপনার $platformName অ্যাকাউন্ট সেটিংসের মাধ্যমে আপনার পাসকি পরিচালনা করুন।'; + } + + @override + String get continueButtonText => 'চালিয়ে যান'; + + @override + String get unexpectedError => 'একটি অপ্রত্যাশিত ত্রুটি ঘটেছে!'; + + @override + String get unexpectedErrorDescription => 'দয়া করে পরে চেষ্টা করুন।'; + + @override + String get ok => 'Ok'; + + @override + String get experienceSaferLogins => 'নিরাপদ লগইন অভিজ্ঞতা'; + + @override + String get enhanceSecurity => 'উন্নত সুরক্ষা মাত্র এক ট্যাপ দূরে।'; + + @override + String get here => 'এখানে'; + + @override + String get effortlessLogin => 'Passkeys সাথে অনায়াসে লগইন করুন'; + + @override + String get whatArePasskeys => 'Passkeys কী কী?'; + + @override + String get whatArePasskeysDescriptionPoint1 => 'পাসওয়ার্ডের সুরক্ষিত বিকল্প।'; + + @override + String get whatArePasskeysDescriptionPoint2 => 'আপনার ফোনের মতো আপনার অ্যাকাউন্টটি আনলক করুন - বায়োমেট্রিক্স, ফেস স্ক্যান বা পিন সহ।'; + + @override + String get whyPasskeys => 'Passkeys কেন?'; + + @override + String get whyPasskeysDescription1 => 'অতিরিক্ত সুরক্ষা স্তর।'; + + @override + String get whyPasskeysDescription2 => 'অননুমোদিত অ্যাক্সেস এবং ফিশিং বিরুদ্ধে শিল্ড।'; + + @override + String get howToCreatePasskey => 'Passkey একটি পাসকি তৈরি করবেন?'; + + @override + String get howToCreatePasskeyDescription1 => 'Deriv \'অ্যাকাউন্ট সেটিংস\' এ যান।'; + + @override + String get howToCreatePasskeyDescription2 => 'Passkey প্রতি ডিভাইসে একটি পাসকি তৈরি করতে পারেন।'; + + @override + String get p2pHowToCreatePasskey => 'কিভাবে পাসকি তৈরি করবেন?'; + + @override + String get p2pHowToCreatePasskeyDescription1 => 'আপনার ডেরিভ পি 2 পি অ্যাপের \'প্রোফাইল\' এ যান।'; + + @override + String get p2pHowToCreatePasskeyDescription2 => 'আপনার পাসকি তৈরি করতে \'পাসকিস\' এ আলতো চাপুন।'; + + @override + String get whereArePasskeysSaved => 'Passkeys কোথায় সংরক্ষণ করা হয়?'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'Android: Google পাসওয়ার্ড ম্যানেজার।'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS: iCloud কীচেন।'; + + @override + String get whatHappensIfEmailChanged => 'আমার Deriv অ্যাকাউন্ট ইমেইল পরিবর্তন হলে কি হবে?'; + + @override + String get whatHappensIfEmailChangedDescription1 => 'কোন সমস্যা নেই! আপনার Passkey এখনও কাজ করে।'; + + @override + String get whatHappensIfEmailChangedDescription2 => 'আপনার বিদ্যমান Passkey দিয়ে Deriv সাইন ইন করুন।'; + + @override + String get tips => 'টিপস'; + + @override + String get beforeUsingPasskeys => 'Passkeys ব্যবহারের পূর্বে'; + + @override + String get enableScreenLock => 'আপনার ডিভাইসে স্ক্রিন লক সক্ষম করুন।'; + + @override + String get signInGoogleOrIcloud => 'আপনার Google বা iCloud অ্যাকাউন্টে সাইন ইন করুন।'; + + @override + String get enableBluetooth => 'ব্লুটুথ সক্ষম করুন।'; + + @override + String get noPasskeyFound => 'কোনো Passkey পাওয়া যায়নি!'; + + @override + String get noPasskeyFoundDescription => 'এই বৈশিষ্ট্যটি ব্যবহার করতে অনুগ্রহ করে একটি Passkey তৈরি করুন।'; + + @override + String get maybeLater => 'হয়তো পরে'; + + @override + String get effortlessLoginWithPasskeys => 'Passkeys সাথে অনায়াসে লগইন করুন'; + + @override + String get learnMoreAboutPasskeys => 'Passkeys সম্পর্কে আরও জানুন'; + + @override + String get noNeedToRememberPassword => 'পাসওয়ার্ড স্মরণে রাখার দরকার নাই'; + + @override + String get useYourBiometrics => 'বায়োমেট্রিক্স বা স্ক্রিন লক সহ উন্নত সুরক্ষা '; + + @override + String get syncAcrossDevices => 'ডিভাইস জুড়ে সিঙ্ক'; + + @override + String get createPasskey => 'Passkey তৈরি'; + + @override + String get unsupportedPlatform => 'সমর্থিত প্ল্যাটফর্'; + + @override + String get storedOn => 'সংরক্ষণ হয়েছে'; + + @override + String get lastUsed => 'সর্বশেষ ব্যবহৃত'; + + @override + String get rename => 'পুনঃনামকরণ'; + + @override + String get revoke => 'প্রত্যাহার করুন'; + + @override + String get continueTradingButtonText => 'ট্রেডিং চালিয়ে যান'; + + @override + String get addMorePasskeysButtonText => 'আরও Passkeys'; + + @override + String get unableToSetupPasskey => 'Passkey সেটআপ করতে অক্ষম'; + + @override + String get unableToSetupPasskeyDescription => 'আপনার Passkey সেট আপ করার সময় আমরা একটি সমস্যার মুখোমুখি হয়েছি। প্রক্রিয়াটি বাধা দেওয়া হতে পারে, বা সেশনের সময় শেষ হতে পারে। দয়া করে আবার চেষ্টা করুন।'; + + @override + String get passkeysOffErrorTitle => 'Passkeys পরিষেবাটি উপলব্ধ নয়'; + + @override + String get never => 'কখনই'; + + @override + String get unable_to_process_your_request => 'আপনার অনুরোধ প্রক্রিয়া করতে অক্ষম'; + + @override + String get unable_to_process_your_request_description => 'আমরা আপনার অনুরোধ প্রক্রিয়াকরণে একটি অস্থায়ী সমস্যা অনুভব করছি। দয়া করে পরে আবার চেষ্টা করুন।'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_de.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_de.dart new file mode 100644 index 000000000..73931520e --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_de.dart @@ -0,0 +1,173 @@ +import 'deriv_passkeys_localizations.dart'; + +/// The translations for German (`de`). +class DerivPasskeysLocalizationsDe extends DerivPasskeysLocalizations { + DerivPasskeysLocalizationsDe([String locale = 'de']) : super(locale); + + @override + String get passkeyCreatedSuccessTitle => 'Erfolg!'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return 'Ihr Konto ist jetzt mit einem Passkey gesichert. Verwalte deinen Passkey in deinen $platformName -Kontoeinstellungen.'; + } + + @override + String get continueButtonText => 'Weiter'; + + @override + String get unexpectedError => 'Ein unerwarteter Fehler ist aufgetreten!'; + + @override + String get unexpectedErrorDescription => 'Bitte versuchen Sie es später erneut.'; + + @override + String get ok => 'Ok'; + + @override + String get experienceSaferLogins => 'Erleben Sie sicherere Logins'; + + @override + String get enhanceSecurity => 'Verbesserte Sicherheit ist nur einen Fingertipp entfernt.'; + + @override + String get here => 'hier'; + + @override + String get effortlessLogin => 'Müheloses Einloggen mit Passkeys'; + + @override + String get whatArePasskeys => 'Was sind Passkeys? '; + + @override + String get whatArePasskeysDescriptionPoint1 => 'Sichere Alternative zu Passwörtern.'; + + @override + String get whatArePasskeysDescriptionPoint2 => 'Entsperren Sie Ihr Konto wie Ihr Telefon - mit biometrischen Daten, Gesichtsscan oder PIN.'; + + @override + String get whyPasskeys => 'Warum Passkeys?'; + + @override + String get whyPasskeysDescription1 => 'Zusätzliche Sicherheitsebene.'; + + @override + String get whyPasskeysDescription2 => 'Schützt vor unbefugtem Zugriff und Phishing.'; + + @override + String get howToCreatePasskey => 'Wie erstelle ich einen Passkey?'; + + @override + String get howToCreatePasskeyDescription1 => 'Gehen Sie zu \'Kontoeinstellungen\' auf Deriv.'; + + @override + String get howToCreatePasskeyDescription2 => 'Sie können einen Passkey pro Gerät erstellen.'; + + @override + String get p2pHowToCreatePasskey => 'Wie erstelle ich einen Passkey?'; + + @override + String get p2pHowToCreatePasskeyDescription1 => 'Gehen Sie in Ihrer Deriv P2P-App zu „Profil“.'; + + @override + String get p2pHowToCreatePasskeyDescription2 => 'Tippen Sie auf „Passkeys“, um Ihren Passkey zu erstellen.'; + + @override + String get whereArePasskeysSaved => 'Wo werden die Passkeys gespeichert?'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'Android: Google Passwort-Manager.'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS: iCloud-Schlüsselbund.'; + + @override + String get whatHappensIfEmailChanged => 'Was passiert, wenn die E-Mail-Adresse meines Deriv-Kontos geändert wird?'; + + @override + String get whatHappensIfEmailChangedDescription1 => 'Kein Problem! Ihr Passkey funktioniert noch.'; + + @override + String get whatHappensIfEmailChangedDescription2 => 'Melden Sie sich bei Deriv mit Ihrem bestehenden Passkey an.'; + + @override + String get tips => 'Tipps'; + + @override + String get beforeUsingPasskeys => 'Vor der Benutzung von Passkeys'; + + @override + String get enableScreenLock => 'Aktiviere die Bildschirmsperre auf deinem Gerät.'; + + @override + String get signInGoogleOrIcloud => 'Melde dich bei deinem Google- oder iCloud-Konto an.'; + + @override + String get enableBluetooth => 'Aktiviere Bluetooth.'; + + @override + String get noPasskeyFound => 'Kein Passkey gefunden!'; + + @override + String get noPasskeyFoundDescription => 'Bitte erstellen Sie einen Passkey, um diese Funktion zu nutzen.'; + + @override + String get maybeLater => 'Vielleicht später'; + + @override + String get effortlessLoginWithPasskeys => 'Müheloses Einloggen mit Passkeys'; + + @override + String get learnMoreAboutPasskeys => 'Erfahren Sie mehr über Passkeys'; + + @override + String get noNeedToRememberPassword => 'Sie müssen sich kein Passwort merken'; + + @override + String get useYourBiometrics => 'Erhöhte Sicherheit durch Biometrie oder Bildschirmsperre'; + + @override + String get syncAcrossDevices => 'Geräteübergreifend synchronisieren'; + + @override + String get createPasskey => 'Passkeyerstellen'; + + @override + String get unsupportedPlatform => 'Plattform wird nicht unterstützt'; + + @override + String get storedOn => 'Gespeichert am'; + + @override + String get lastUsed => 'Zuletzt verwendet'; + + @override + String get rename => 'Umbenennen'; + + @override + String get revoke => 'Widerrufen'; + + @override + String get continueTradingButtonText => 'Handel fortsetzen'; + + @override + String get addMorePasskeysButtonText => 'Mehr Passkeys'; + + @override + String get unableToSetupPasskey => 'Passkey kann nicht eingerichtet werden'; + + @override + String get unableToSetupPasskeyDescription => 'Beim Einrichten Ihres Passkey ist ein Problem aufgetreten. Der Vorgang wurde möglicherweise unterbrochen oder die Sitzung wurde unterbrochen. Bitte versuchen Sie es erneut.'; + + @override + String get passkeysOffErrorTitle => 'Der Dienst Passkeys ist nicht verfügbar'; + + @override + String get never => 'Niemals'; + + @override + String get unable_to_process_your_request => 'Ihre Anfrage konnte nicht bearbeitet werden'; + + @override + String get unable_to_process_your_request_description => 'Wir haben ein vorübergehendes Problem bei der Bearbeitung Ihrer Anfrage. Bitte versuchen Sie es später erneut.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_en.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_en.dart new file mode 100644 index 000000000..e8cb6b211 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_en.dart @@ -0,0 +1,173 @@ +import 'deriv_passkeys_localizations.dart'; + +/// The translations for English (`en`). +class DerivPasskeysLocalizationsEn extends DerivPasskeysLocalizations { + DerivPasskeysLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get passkeyCreatedSuccessTitle => 'Success!'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return 'Your account is now secured with a passkey. Manage your passkey through your $platformName account settings.'; + } + + @override + String get continueButtonText => 'Continue'; + + @override + String get unexpectedError => 'An unexpected error occurred!'; + + @override + String get unexpectedErrorDescription => 'Please try again later.'; + + @override + String get ok => 'Ok'; + + @override + String get experienceSaferLogins => 'Experience safer logins'; + + @override + String get enhanceSecurity => 'Enhanced security is just a tap away.'; + + @override + String get here => 'here'; + + @override + String get effortlessLogin => 'Effortless login with passkeys'; + + @override + String get whatArePasskeys => 'What are passkeys?'; + + @override + String get whatArePasskeysDescriptionPoint1 => 'Secure alternative to passwords.'; + + @override + String get whatArePasskeysDescriptionPoint2 => 'Unlock your account like your phone - with biometrics, face scan or PIN.'; + + @override + String get whyPasskeys => 'Why passkeys?'; + + @override + String get whyPasskeysDescription1 => 'Extra security layer.'; + + @override + String get whyPasskeysDescription2 => 'Shields against unauthorised access and phishing.'; + + @override + String get howToCreatePasskey => 'How to create a passkey?'; + + @override + String get howToCreatePasskeyDescription1 => 'Go to ‘Account Settings’ on Deriv.'; + + @override + String get howToCreatePasskeyDescription2 => 'You can create one passkey per device.'; + + @override + String get p2pHowToCreatePasskey => 'How to create passkey?'; + + @override + String get p2pHowToCreatePasskeyDescription1 => 'Go to ‘Profile‘ in your Deriv P2P app.'; + + @override + String get p2pHowToCreatePasskeyDescription2 => 'Tap ‘Passkeys‘ to create your passkey.'; + + @override + String get whereArePasskeysSaved => 'Where are passkeys saved?'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'Android: Google password manager.'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS: iCloud keychain.'; + + @override + String get whatHappensIfEmailChanged => 'What happens if my Deriv account email is changed?'; + + @override + String get whatHappensIfEmailChangedDescription1 => 'No problem! Your passkey still works.'; + + @override + String get whatHappensIfEmailChangedDescription2 => 'Sign in to Deriv with your existing passkey.'; + + @override + String get tips => 'Tips'; + + @override + String get beforeUsingPasskeys => 'Before using passkeys'; + + @override + String get enableScreenLock => 'Enable screen lock on your device.'; + + @override + String get signInGoogleOrIcloud => 'Sign in to your Google or iCloud account.'; + + @override + String get enableBluetooth => 'Enable Bluetooth.'; + + @override + String get noPasskeyFound => 'No passkey found!'; + + @override + String get noPasskeyFoundDescription => 'Please create a passkey to use this feature.'; + + @override + String get maybeLater => 'Maybe later'; + + @override + String get effortlessLoginWithPasskeys => 'Effortless login with passkeys'; + + @override + String get learnMoreAboutPasskeys => 'Learn more about passkeys'; + + @override + String get noNeedToRememberPassword => 'No need to remember a password'; + + @override + String get useYourBiometrics => 'Enhanced security with biometrics or screen lock'; + + @override + String get syncAcrossDevices => 'Sync across devices'; + + @override + String get createPasskey => 'Create passkey'; + + @override + String get unsupportedPlatform => 'Unsupported Platform'; + + @override + String get storedOn => 'Stored on'; + + @override + String get lastUsed => 'Last used'; + + @override + String get rename => 'Rename'; + + @override + String get revoke => 'Revoke'; + + @override + String get continueTradingButtonText => 'Continue trading'; + + @override + String get addMorePasskeysButtonText => 'Add more passkeys'; + + @override + String get unableToSetupPasskey => 'Unable to setup passkey'; + + @override + String get unableToSetupPasskeyDescription => 'We encountered an issue while setting up your passkey. The process might have been interrupted, or the session timed out. Please try again.'; + + @override + String get passkeysOffErrorTitle => 'The Passkeys service is unavailable'; + + @override + String get never => 'Never'; + + @override + String get unable_to_process_your_request => 'Unable to process your request'; + + @override + String get unable_to_process_your_request_description => 'We’re experiencing a temporary issue in processing your request. Please try again later.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_es.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_es.dart new file mode 100644 index 000000000..a3a229182 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_es.dart @@ -0,0 +1,173 @@ +import 'deriv_passkeys_localizations.dart'; + +/// The translations for Spanish Castilian (`es`). +class DerivPasskeysLocalizationsEs extends DerivPasskeysLocalizations { + DerivPasskeysLocalizationsEs([String locale = 'es']) : super(locale); + + @override + String get passkeyCreatedSuccessTitle => '¡Exitoso!'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return 'Su cuenta está ahora protegida con una passkey. Gestione su passkey a través de la configuración de su cuenta $platformName.'; + } + + @override + String get continueButtonText => 'Continuar'; + + @override + String get unexpectedError => 'Ha ocurrido un error inesperado!'; + + @override + String get unexpectedErrorDescription => 'Por favor, inténtelo más tarde.'; + + @override + String get ok => 'Ok'; + + @override + String get experienceSaferLogins => 'Experimente inicios de sesión más seguros'; + + @override + String get enhanceSecurity => 'La seguridad mejorada está a sólo un toque de distancia.'; + + @override + String get here => 'aquí'; + + @override + String get effortlessLogin => 'Inicio de sesión sin esfuerzo con passkeys'; + + @override + String get whatArePasskeys => '¿Qué son las passkeys?'; + + @override + String get whatArePasskeysDescriptionPoint1 => 'Alternativa segura a las contraseñas.'; + + @override + String get whatArePasskeysDescriptionPoint2 => 'Desbloquee su cuenta como si fuera su teléfono: con biometría, escáner facial o PIN.'; + + @override + String get whyPasskeys => '¿Por qué passkeys?'; + + @override + String get whyPasskeysDescription1 => 'Capa de seguridad adicional.'; + + @override + String get whyPasskeysDescription2 => 'Protege contra el acceso no autorizado y la suplantación de identidad.'; + + @override + String get howToCreatePasskey => '¿Cómo crear una passkey?'; + + @override + String get howToCreatePasskeyDescription1 => 'Vaya a \"Configuración de la cuenta\" en Deriv.'; + + @override + String get howToCreatePasskeyDescription2 => 'Puede crear una passkey por dispositivo.'; + + @override + String get p2pHowToCreatePasskey => '¿Cómo crear una clave de paso?'; + + @override + String get p2pHowToCreatePasskeyDescription1 => 'Ir a «Perfil» en tu aplicación Deriv P2P.'; + + @override + String get p2pHowToCreatePasskeyDescription2 => 'Pulsa «Claves de paso» para crear tu clave de paso.'; + + @override + String get whereArePasskeysSaved => '¿Dónde se guardan las passkeys?'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'Android: Gestor de contraseñas de Google.'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS: Llavero de iCloud.'; + + @override + String get whatHappensIfEmailChanged => '¿Qué ocurre si se cambia el correo electrónico de mi cuenta de Deriv?'; + + @override + String get whatHappensIfEmailChangedDescription1 => 'No hay problema. Su passkey sigue funcionando.'; + + @override + String get whatHappensIfEmailChangedDescription2 => 'Inicie sesión en Deriv con su passkey actual.'; + + @override + String get tips => 'Consejos'; + + @override + String get beforeUsingPasskeys => 'Antes de usar la passkey:'; + + @override + String get enableScreenLock => 'Habilite el bloqueo de pantalla en su dispositivo.'; + + @override + String get signInGoogleOrIcloud => 'Inicie sesión en su cuenta de Google o iCloud.'; + + @override + String get enableBluetooth => 'Activar Bluetooth.'; + + @override + String get noPasskeyFound => '¡No se encontró ninguna passkey!'; + + @override + String get noPasskeyFoundDescription => 'Cree una passkey para usar esta función.'; + + @override + String get maybeLater => 'Quizás más tarde'; + + @override + String get effortlessLoginWithPasskeys => 'Inicio de sesión sin esfuerzo con passkeys'; + + @override + String get learnMoreAboutPasskeys => 'Obtenga más información sobre las passkeys'; + + @override + String get noNeedToRememberPassword => 'No es necesario recordar una contraseña'; + + @override + String get useYourBiometrics => 'Seguridad mejorada con biometría o bloqueo de pantalla'; + + @override + String get syncAcrossDevices => 'Sincronización entre dispositivos'; + + @override + String get createPasskey => 'Crear passkey'; + + @override + String get unsupportedPlatform => 'Plataforma no compatible'; + + @override + String get storedOn => 'Almacenado en'; + + @override + String get lastUsed => 'Utilizado por última vez'; + + @override + String get rename => 'Renombrar'; + + @override + String get revoke => 'Revocar'; + + @override + String get continueTradingButtonText => 'Continúe operando'; + + @override + String get addMorePasskeysButtonText => 'Añadir más Passkeys'; + + @override + String get unableToSetupPasskey => 'No se puede configurar la passkey'; + + @override + String get unableToSetupPasskeyDescription => 'Hemos encontrado un problema al configurar su passkey. Es posible que el proceso se haya interrumpido o que la sesión haya expirado. Por favor, inténtelo de nuevo.'; + + @override + String get passkeysOffErrorTitle => 'El servicio Passkeys no está disponible'; + + @override + String get never => 'Nunca'; + + @override + String get unable_to_process_your_request => 'No se puede procesar su solicitud'; + + @override + String get unable_to_process_your_request_description => 'Estamos teniendo un problema temporal al procesar tu solicitud. Vuelva a intentarlo más tarde.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_fr.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_fr.dart new file mode 100644 index 000000000..7eca2189e --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_fr.dart @@ -0,0 +1,173 @@ +import 'deriv_passkeys_localizations.dart'; + +/// The translations for French (`fr`). +class DerivPasskeysLocalizationsFr extends DerivPasskeysLocalizations { + DerivPasskeysLocalizationsFr([String locale = 'fr']) : super(locale); + + @override + String get passkeyCreatedSuccessTitle => 'Effectué !'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return 'Votre compte est désormais sécurisé par une passkey. Gérez votre passkey à partir de vos paramètres de compte $platformName.'; + } + + @override + String get continueButtonText => 'Continuer'; + + @override + String get unexpectedError => 'Une erreur inattendue s\'est produite !'; + + @override + String get unexpectedErrorDescription => 'Veuillez réessayer plus tard.'; + + @override + String get ok => 'Ok'; + + @override + String get experienceSaferLogins => 'Bénéficiez de connexions plus sûres'; + + @override + String get enhanceSecurity => 'Une sécurité renforcée est à portée de main.'; + + @override + String get here => 'ici'; + + @override + String get effortlessLogin => 'Connexion facile à l\'aide de passkeys'; + + @override + String get whatArePasskeys => 'Que sont les passkeys ?'; + + @override + String get whatArePasskeysDescriptionPoint1 => 'Alternative sécurisée aux mots de passe.'; + + @override + String get whatArePasskeysDescriptionPoint2 => 'Déverrouillez votre compte comme votre téléphone - par biométrie, scan du visage ou code PIN.'; + + @override + String get whyPasskeys => 'Pourquoi choisir des passkeys ?'; + + @override + String get whyPasskeysDescription1 => 'Couche de sécurité supplémentaire.'; + + @override + String get whyPasskeysDescription2 => 'Protège contre les accès non autorisés et le phishing.'; + + @override + String get howToCreatePasskey => 'Comment créer une passkey ?'; + + @override + String get howToCreatePasskeyDescription1 => 'Allez dans \"Paramètres du compte\" sur Deriv.'; + + @override + String get howToCreatePasskeyDescription2 => 'Vous pouvez créer une Passkey par appareil.'; + + @override + String get p2pHowToCreatePasskey => 'Comment créer une clé d\'accès ?'; + + @override + String get p2pHowToCreatePasskeyDescription1 => 'Accéder à « Profil » dans votre application Deriv P2P.'; + + @override + String get p2pHowToCreatePasskeyDescription2 => 'Appuyez sur « Clés d\'accès » pour créer votre clé d\'accès.'; + + @override + String get whereArePasskeysSaved => 'Où sont enregistrées les passkeys ?'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'Android : Gestionnaire de mots de passe Google.'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS : trousseau iCloud.'; + + @override + String get whatHappensIfEmailChanged => 'Que se passe-t-il si l\'adresse e-mail de mon compte Deriv est modifiée ?'; + + @override + String get whatHappensIfEmailChangedDescription1 => 'Pas de problème ! Votre Passkey fonctionne toujours.'; + + @override + String get whatHappensIfEmailChangedDescription2 => 'Connectez-vous à Deriv avec votre Passkey existant.'; + + @override + String get tips => 'Conseils'; + + @override + String get beforeUsingPasskeys => 'Avant d\'utiliser la passkey :'; + + @override + String get enableScreenLock => 'Activez le verrouillage de l\'écran sur votre appareil.'; + + @override + String get signInGoogleOrIcloud => 'Connectez-vous à votre compte Google ou iCloud.'; + + @override + String get enableBluetooth => 'Activer Bluetooth.'; + + @override + String get noPasskeyFound => 'Aucun passkey n\'a été trouvé !'; + + @override + String get noPasskeyFoundDescription => 'Veuillez créer une passkey pour utiliser cette fonctionnalité.'; + + @override + String get maybeLater => 'Peut-être plus tard'; + + @override + String get effortlessLoginWithPasskeys => 'Connexion facile à l\'aide de passkeys'; + + @override + String get learnMoreAboutPasskeys => 'En savoir plus sur les passkeys'; + + @override + String get noNeedToRememberPassword => 'Pas besoin de mémoriser un mot de passe'; + + @override + String get useYourBiometrics => 'Sécurité renforcée grâce à la biométrie ou au verrouillage de l\'écran'; + + @override + String get syncAcrossDevices => 'Synchronisation sur tous les appareils'; + + @override + String get createPasskey => 'Créer une passkey'; + + @override + String get unsupportedPlatform => 'Plateforme non prise en charge'; + + @override + String get storedOn => 'Stocké sur '; + + @override + String get lastUsed => 'Dernière utilisation'; + + @override + String get rename => 'Renommer'; + + @override + String get revoke => 'Révoquer'; + + @override + String get continueTradingButtonText => 'Poursuivre les opérations de trading'; + + @override + String get addMorePasskeysButtonText => 'Ajouter plus de Passkeys'; + + @override + String get unableToSetupPasskey => 'Impossible de configurer la Passkey'; + + @override + String get unableToSetupPasskeyDescription => 'Nous avons rencontré un problème lors de l\'établissement de votre Passkey. Il se peut que le processus ait été interrompu ou que la session ait expiré. Veuillez réessayer.'; + + @override + String get passkeysOffErrorTitle => 'Le service Passkeys est indisponible'; + + @override + String get never => 'Jamais'; + + @override + String get unable_to_process_your_request => 'Impossible de traiter votre demande'; + + @override + String get unable_to_process_your_request_description => 'Nous rencontrons un problème temporaire lors du traitement de votre demande. Veuillez réessayer ultérieurement.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_it.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_it.dart new file mode 100644 index 000000000..56180bfa2 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_it.dart @@ -0,0 +1,173 @@ +import 'deriv_passkeys_localizations.dart'; + +/// The translations for Italian (`it`). +class DerivPasskeysLocalizationsIt extends DerivPasskeysLocalizations { + DerivPasskeysLocalizationsIt([String locale = 'it']) : super(locale); + + @override + String get passkeyCreatedSuccessTitle => 'Fatto!'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return 'Il tuo account è ora protetto con una passkey. Gestisci la tua passkey tramite le impostazioni del tuo account $platformName.'; + } + + @override + String get continueButtonText => 'Continua'; + + @override + String get unexpectedError => 'Si è verificato un errore imprevisto!'; + + @override + String get unexpectedErrorDescription => 'Riprova più tardi.'; + + @override + String get ok => 'Ok'; + + @override + String get experienceSaferLogins => 'Sperimenta accessi più sicuri'; + + @override + String get enhanceSecurity => 'Per una maggiore sicurezza basta un tocco.'; + + @override + String get here => 'qui'; + + @override + String get effortlessLogin => 'Accesso semplice con passkeys'; + + @override + String get whatArePasskeys => 'Cosa sono le passkey?'; + + @override + String get whatArePasskeysDescriptionPoint1 => 'Un\'alternativa sicura alle password.'; + + @override + String get whatArePasskeysDescriptionPoint2 => 'Sblocca il suo conto come il suo telefono - con la biometria, la scansione del volto o il PIN.'; + + @override + String get whyPasskeys => 'Perché la passkey?'; + + @override + String get whyPasskeysDescription1 => 'Un ulteriore livello di sicurezza.'; + + @override + String get whyPasskeysDescription2 => 'Protegge dagli accessi non autorizzati e dal phishing.'; + + @override + String get howToCreatePasskey => 'Come creare una passkey?'; + + @override + String get howToCreatePasskeyDescription1 => 'Vada a \'Impostazioni del conto\' su Deriv.'; + + @override + String get howToCreatePasskeyDescription2 => 'Può creare una sola Passkey per dispositivo.'; + + @override + String get p2pHowToCreatePasskey => 'Come creare una passkey?'; + + @override + String get p2pHowToCreatePasskeyDescription1 => 'Vai a «Profilo» nella tua app Deriv P2P.'; + + @override + String get p2pHowToCreatePasskeyDescription2 => 'Tocca «Passkey» per creare la tua passkey.'; + + @override + String get whereArePasskeysSaved => 'Dove vengono salvate la passkey?'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'Android: Gestore di password di Google.'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS: Portachiavi iCloud.'; + + @override + String get whatHappensIfEmailChanged => 'Cosa succede se l\'email del mio account Deriv viene modificata?'; + + @override + String get whatHappensIfEmailChangedDescription1 => 'Nessun problema! La sua Passkey funziona ancora.'; + + @override + String get whatHappensIfEmailChangedDescription2 => 'Acceda a Deriv con la sua Passkey esistente.'; + + @override + String get tips => 'Suggerimento'; + + @override + String get beforeUsingPasskeys => 'Prima di utilizzare la passkey'; + + @override + String get enableScreenLock => 'Abilita il blocco dello schermo sul tuo dispositivo.'; + + @override + String get signInGoogleOrIcloud => 'Accedi al tuo account Google o iCloud.'; + + @override + String get enableBluetooth => 'Abilita il Bluetooth.'; + + @override + String get noPasskeyFound => 'Nessuna passkey trovata!'; + + @override + String get noPasskeyFoundDescription => 'Crea una passkey per utilizzare questa funzione.'; + + @override + String get maybeLater => 'Forse più tardi'; + + @override + String get effortlessLoginWithPasskeys => 'Accesso semplice con passkeys'; + + @override + String get learnMoreAboutPasskeys => 'Scopri di più sulle passkey'; + + @override + String get noNeedToRememberPassword => 'Non è necessario ricordare una password'; + + @override + String get useYourBiometrics => 'Sicurezza avanzata con biometria o blocco dello schermo'; + + @override + String get syncAcrossDevices => 'Sincronizzazione su più dispositivi'; + + @override + String get createPasskey => 'Crea Passkey'; + + @override + String get unsupportedPlatform => 'Piattaforma non supportata'; + + @override + String get storedOn => 'Memorizzato su'; + + @override + String get lastUsed => 'Ultima volta'; + + @override + String get rename => 'Rinomina'; + + @override + String get revoke => 'Revoca'; + + @override + String get continueTradingButtonText => 'Continua il trading'; + + @override + String get addMorePasskeysButtonText => 'Più passkeys'; + + @override + String get unableToSetupPasskey => 'Impossibile impostare la Passkey'; + + @override + String get unableToSetupPasskeyDescription => 'Abbiamo riscontrato un problema durante la configurazione della sua Passkey. Il processo potrebbe essere stato interrotto, oppure la sessione è scaduta. Provi di nuovo.'; + + @override + String get passkeysOffErrorTitle => 'Il servizio Passkeys non è disponibile'; + + @override + String get never => 'Mai'; + + @override + String get unable_to_process_your_request => 'Impossibile elaborare la tua richiesta'; + + @override + String get unable_to_process_your_request_description => 'Stiamo riscontrando un problema temporaneo nell\'elaborazione della tua richiesta. Riprova più tardi.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_km.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_km.dart new file mode 100644 index 000000000..e7dfaae89 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_km.dart @@ -0,0 +1,173 @@ +import 'deriv_passkeys_localizations.dart'; + +/// The translations for Khmer Central Khmer (`km`). +class DerivPasskeysLocalizationsKm extends DerivPasskeysLocalizations { + DerivPasskeysLocalizationsKm([String locale = 'km']) : super(locale); + + @override + String get passkeyCreatedSuccessTitle => 'ជោគជ័យ!'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return 'គណនីរបស់អ្នកឥឡូវនេះត្រូវបានការពារដោយសោចូល។ គ្រប់គ្រងសោចូលរបស់អ្នកតាមរយៈការកំណត់គណនី $platformName របស់អ្នក។'; + } + + @override + String get continueButtonText => 'បន្ត'; + + @override + String get unexpectedError => 'មានកំហុសដែលមិនបានរំពឹងទុកបានកើតឡើង!'; + + @override + String get unexpectedErrorDescription => 'សូមព្យាយាមម្តងទៀតនៅពេលក្រោយ។'; + + @override + String get ok => 'យល់ព្រម'; + + @override + String get experienceSaferLogins => 'ជួបប្រទះការចូលដែលមានសុវត្ថិភាពជាងមុន'; + + @override + String get enhanceSecurity => 'សុវត្ថិភាពកាន់តែប្រសើរគឺគ្រាន់តែចុចមួយដងប៉ុណ្ណោះ។'; + + @override + String get here => 'នៅទីនេះ'; + + @override + String get effortlessLogin => 'ចូលបានយ៉ាងងាយស្រួលជាមួយសោចូល'; + + @override + String get whatArePasskeys => 'តើសោចូលជាអ្វី?'; + + @override + String get whatArePasskeysDescriptionPoint1 => 'ជម្រើសសុវត្ថិភាពសម្រាប់ពាក្យសម្ងាត់។'; + + @override + String get whatArePasskeysDescriptionPoint2 => 'ដោះសោគណនីរបស់អ្នកដូចទូរស័ព្ទរបស់អ្នក - ជាមួយជីវមាត្រ ការស្កេនមុខ ឬ PIN។'; + + @override + String get whyPasskeys => 'ហេតុអ្វីបានជាសោចូល?'; + + @override + String get whyPasskeysDescription1 => 'ស្រទាប់សុវត្ថិភាពបន្ថែម។'; + + @override + String get whyPasskeysDescription2 => 'ការពារប្រឆាំងនឹងការចូលប្រើប្រាស់ដោយគ្មានការអនុញ្ញាត និងការបន្លំ។'; + + @override + String get howToCreatePasskey => 'របៀបបង្កើតសោចូល?'; + + @override + String get howToCreatePasskeyDescription1 => 'ចូលទៅកាន់ \'ការកំណត់គណនី\' នៅលើ Deriv។'; + + @override + String get howToCreatePasskeyDescription2 => 'អ្នកអាចបង្កើតសោចូលមួយសម្រាប់ឧបករណ៍នីមួយៗ។'; + + @override + String get p2pHowToCreatePasskey => 'របៀបបង្កើតសោចូល?'; + + @override + String get p2pHowToCreatePasskeyDescription1 => 'ចូលទៅកាន់ \'ប្រវត្តិរូប\' នៅក្នុងកម្មវិធី Deriv P2P របស់អ្នក។'; + + @override + String get p2pHowToCreatePasskeyDescription2 => 'ចុច \'សោចូល\' ដើម្បីបង្កើតសោចូលរបស់អ្នក។'; + + @override + String get whereArePasskeysSaved => 'តើសោចូលត្រូវបានរក្សាទុកនៅឯណា?'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'Android៖ កម្មវិធីគ្រប់គ្រងពាក្យសម្ងាត់ Google។'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS៖ iCloud keychain។'; + + @override + String get whatHappensIfEmailChanged => 'តើមានអ្វីកើតឡើងប្រសិនបើអ៊ីមែលគណនី Deriv របស់ខ្ញុំត្រូវបានផ្លាស់ប្តូរ?'; + + @override + String get whatHappensIfEmailChangedDescription1 => 'គ្មានបញ្ហាទេ! សោចូលរបស់អ្នកនៅតែដំណើរការ។'; + + @override + String get whatHappensIfEmailChangedDescription2 => 'ចូលទៅក្នុង Deriv ជាមួយសោចូលដែលមានស្រាប់របស់អ្នក។'; + + @override + String get tips => 'ការណែនាំ'; + + @override + String get beforeUsingPasskeys => 'មុនពេលប្រើប្រាស់សោចូល'; + + @override + String get enableScreenLock => 'បើកការចាក់សោអេក្រង់នៅលើឧបករណ៍របស់អ្នក។'; + + @override + String get signInGoogleOrIcloud => 'ចូលទៅក្នុងគណនី Google ឬ iCloud របស់អ្នក។'; + + @override + String get enableBluetooth => 'បើក Bluetooth។'; + + @override + String get noPasskeyFound => 'រកមិនឃើញសោចូលទេ!'; + + @override + String get noPasskeyFoundDescription => 'សូមបង្កើតសោចូលដើម្បីប្រើមុខងារនេះ។'; + + @override + String get maybeLater => 'ប្រហែលជាពេលក្រោយ'; + + @override + String get effortlessLoginWithPasskeys => 'ចូលបានយ៉ាងងាយស្រួលជាមួយសោចូល'; + + @override + String get learnMoreAboutPasskeys => 'ស្វែងយល់បន្ថែមអំពីសោចូល'; + + @override + String get noNeedToRememberPassword => 'មិនចាំបាច់ចាំពាក្យសម្ងាត់ទេ'; + + @override + String get useYourBiometrics => 'សុវត្ថិភាពកាន់តែប្រសើរឡើងជាមួយជីវមាត្រ ឬការចាក់សោអេក្រង់'; + + @override + String get syncAcrossDevices => 'ធ្វើសមកាលកម្មនៅលើឧបករណ៍'; + + @override + String get createPasskey => 'បង្កើតសោចូល'; + + @override + String get unsupportedPlatform => 'វេទិកាមិនត្រូវបានគាំទ្រ'; + + @override + String get storedOn => 'រក្សាទុកនៅលើ'; + + @override + String get lastUsed => 'ប្រើចុងក្រោយ'; + + @override + String get rename => 'ប្តូរឈ្មោះ'; + + @override + String get revoke => 'លុបចោល'; + + @override + String get continueTradingButtonText => 'បន្តការជួញដូរ'; + + @override + String get addMorePasskeysButtonText => 'បន្ថែមសោចូលបន្ថែម'; + + @override + String get unableToSetupPasskey => 'មិនអាចដំឡើងសោចូលបានទេ'; + + @override + String get unableToSetupPasskeyDescription => 'យើងបានជួបប្រទះបញ្ហាមួយខណៈពេលកំពុងដំឡើងសោចូលរបស់អ្នក។ ដំណើរការនេះអាចត្រូវបានរំខាន ឬវគ្គបានផុតកំណត់។ សូមព្យាយាមម្តងទៀត។'; + + @override + String get passkeysOffErrorTitle => 'សេវាកម្មសោចូលមិនមាន'; + + @override + String get never => 'មិនដែល'; + + @override + String get unable_to_process_your_request => 'មិនអាចដំណើរការសំណើរបស់អ្នកបានទេ'; + + @override + String get unable_to_process_your_request_description => 'យើងកំពុងជួបប្រទះបញ្ហាបណ្តោះអាសន្នក្នុងការដំណើរការសំណើរបស់អ្នក។ សូមព្យាយាមម្តងទៀតនៅពេលក្រោយ។'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_ko.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_ko.dart new file mode 100644 index 000000000..9634249f2 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_ko.dart @@ -0,0 +1,173 @@ +import 'deriv_passkeys_localizations.dart'; + +/// The translations for Korean (`ko`). +class DerivPasskeysLocalizationsKo extends DerivPasskeysLocalizations { + DerivPasskeysLocalizationsKo([String locale = 'ko']) : super(locale); + + @override + String get passkeyCreatedSuccessTitle => '성공!'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return '이제 여러분의 계정은 passkey로 보호됩니다. $platformName 계정 설정을 통해 passkey를 관리하세요.'; + } + + @override + String get continueButtonText => '계속'; + + @override + String get unexpectedError => '예상하지 못한 오류가 발생했습니다!'; + + @override + String get unexpectedErrorDescription => '나중에 다시 시도해 주시기 바랍니다.'; + + @override + String get ok => '확인'; + + @override + String get experienceSaferLogins => '로그인이 더 안전합니다'; + + @override + String get enhanceSecurity => '탭 한 번으로 보안을 강화할 수 있습니다.'; + + @override + String get here => '여기'; + + @override + String get effortlessLogin => 'Passkeys로 간편한 로그인'; + + @override + String get whatArePasskeys => 'Passkeys란 무엇인가요?'; + + @override + String get whatArePasskeysDescriptionPoint1 => '비밀번호를 대체할 수 있는 안전한 비밀번호.'; + + @override + String get whatArePasskeysDescriptionPoint2 => '생체 인식, 얼굴 스캔 또는 PIN을 사용하여 휴대폰처럼 계정을 잠금해제하세요.'; + + @override + String get whyPasskeys => 'Passkeys를 사용하는 이유는 무엇인가요?'; + + @override + String get whyPasskeysDescription1 => '추가 보안 계층.'; + + @override + String get whyPasskeysDescription2 => '무단 접근 및 피싱으로부터 보호합니다.'; + + @override + String get howToCreatePasskey => 'Passkey는 어떻게 생성할 수 있나요?'; + + @override + String get howToCreatePasskeyDescription1 => 'Deriv의 \'계정 설정\'으로 이동하세요.'; + + @override + String get howToCreatePasskeyDescription2 => '기기당 하나의 Passkey를 만들 수 있습니다.'; + + @override + String get p2pHowToCreatePasskey => '암호 키는 어떻게 만드나요?'; + + @override + String get p2pHowToCreatePasskeyDescription1 => 'Deriv P2P 앱에서 \'프로필\'로 이동합니다.'; + + @override + String get p2pHowToCreatePasskeyDescription2 => '\'암호\'를 눌러 암호를 생성합니다.'; + + @override + String get whereArePasskeysSaved => 'Passkeys는 어디에 저장되나요?'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'Android: Google 비밀번호 관리자.'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS: iCloud 키체인.'; + + @override + String get whatHappensIfEmailChanged => '제 Deriv 계정 이메일이 변경되면 어떻게 되나요?'; + + @override + String get whatHappensIfEmailChangedDescription1 => '괜찮습니다! 귀하의 Passkey를 그대로 사용하실 수 있습니다.'; + + @override + String get whatHappensIfEmailChangedDescription2 => '기존 Passkey를 사용하여 Deriv에 로그인하세요.'; + + @override + String get tips => '팁'; + + @override + String get beforeUsingPasskeys => 'Passkeys를 사용하기 전에'; + + @override + String get enableScreenLock => '기기에서 화면 잠금을 활성화하세요.'; + + @override + String get signInGoogleOrIcloud => 'Google 또는 iCloud 계정으로 로그인하세요.'; + + @override + String get enableBluetooth => '블루투스를 활성화하세요.'; + + @override + String get noPasskeyFound => 'Passkey를 찾을 수 없습니다!'; + + @override + String get noPasskeyFoundDescription => '이 기능을 사용하기 위해서는 passkey를 생성하시기 바랍니다.'; + + @override + String get maybeLater => '다음으로 미루겠습니다'; + + @override + String get effortlessLoginWithPasskeys => 'Passkeys를 통한 간단한 로그인'; + + @override + String get learnMoreAboutPasskeys => 'Passkeys에 대해 더 알아보기'; + + @override + String get noNeedToRememberPassword => '비밀번호를 기억하지 않아도 됩니다'; + + @override + String get useYourBiometrics => '생체인식 또는 화면 잠금을 통한 보안 강화'; + + @override + String get syncAcrossDevices => '기기 간 동기화'; + + @override + String get createPasskey => 'Passkey 생성'; + + @override + String get unsupportedPlatform => '지원되지 않는 플랫폼입니다'; + + @override + String get storedOn => '저장 위치'; + + @override + String get lastUsed => '최근 사용'; + + @override + String get rename => '이름 변경'; + + @override + String get revoke => '철회'; + + @override + String get continueTradingButtonText => '계속 거래하기'; + + @override + String get addMorePasskeysButtonText => '더 많은 passkeys'; + + @override + String get unableToSetupPasskey => 'Passkey를 설정할 수 없습니다'; + + @override + String get unableToSetupPasskeyDescription => 'Passkey를 설정하는 동안 문제가 발생했습니다. 프로세스가 중단되었거나 세션 시간이 초과되었을 수 있습니다. 다시 시도해 주시기 바랍니다.'; + + @override + String get passkeysOffErrorTitle => 'Passkeys 서비스를 이용할 수 없습니다'; + + @override + String get never => '절대 안 돼'; + + @override + String get unable_to_process_your_request => '요청을 처리할 수 없습니다'; + + @override + String get unable_to_process_your_request_description => '요청을 처리하는 동안 일시적인 문제가 발생했습니다.나중에 다시 시도하세요.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_mn.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_mn.dart new file mode 100644 index 000000000..d9e16a5f6 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_mn.dart @@ -0,0 +1,173 @@ +import 'deriv_passkeys_localizations.dart'; + +/// The translations for Mongolian (`mn`). +class DerivPasskeysLocalizationsMn extends DerivPasskeysLocalizations { + DerivPasskeysLocalizationsMn([String locale = 'mn']) : super(locale); + + @override + String get passkeyCreatedSuccessTitle => 'Success!'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return 'Your account is now secured with a passkey. Manage your passkey through your $platformName account settings.'; + } + + @override + String get continueButtonText => 'Continue'; + + @override + String get unexpectedError => 'An unexpected error occurred!'; + + @override + String get unexpectedErrorDescription => 'Please try again later.'; + + @override + String get ok => 'Ok'; + + @override + String get experienceSaferLogins => 'Experience safer logins'; + + @override + String get enhanceSecurity => 'Enhanced security is just a tap away.'; + + @override + String get here => 'here'; + + @override + String get effortlessLogin => 'Effortless login with passkeys'; + + @override + String get whatArePasskeys => 'What are passkeys?'; + + @override + String get whatArePasskeysDescriptionPoint1 => 'Secure alternative to passwords.'; + + @override + String get whatArePasskeysDescriptionPoint2 => 'Unlock your account like your phone - with biometrics, face scan or PIN.'; + + @override + String get whyPasskeys => 'Why passkeys?'; + + @override + String get whyPasskeysDescription1 => 'Extra security layer.'; + + @override + String get whyPasskeysDescription2 => 'Shields against unauthorised access and phishing.'; + + @override + String get howToCreatePasskey => 'How to create a passkey?'; + + @override + String get howToCreatePasskeyDescription1 => 'Go to ‘Account Settings’ on Deriv.'; + + @override + String get howToCreatePasskeyDescription2 => 'You can create one passkey per device.'; + + @override + String get p2pHowToCreatePasskey => 'How to create passkey?'; + + @override + String get p2pHowToCreatePasskeyDescription1 => 'Go to ‘Profile‘ in your Deriv P2P app.'; + + @override + String get p2pHowToCreatePasskeyDescription2 => 'Tap ‘Passkeys‘ to create your passkey.'; + + @override + String get whereArePasskeysSaved => 'Where are passkeys saved?'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'Android: Google password manager.'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS: iCloud keychain.'; + + @override + String get whatHappensIfEmailChanged => 'What happens if my Deriv account email is changed?'; + + @override + String get whatHappensIfEmailChangedDescription1 => 'No problem! Your passkey still works.'; + + @override + String get whatHappensIfEmailChangedDescription2 => 'Sign in to Deriv with your existing passkey.'; + + @override + String get tips => 'Tips'; + + @override + String get beforeUsingPasskeys => 'Before using passkeys'; + + @override + String get enableScreenLock => 'Enable screen lock on your device.'; + + @override + String get signInGoogleOrIcloud => 'Sign in to your Google or iCloud account.'; + + @override + String get enableBluetooth => 'Enable Bluetooth.'; + + @override + String get noPasskeyFound => 'No passkey found!'; + + @override + String get noPasskeyFoundDescription => 'Please create a passkey to use this feature.'; + + @override + String get maybeLater => 'Maybe later'; + + @override + String get effortlessLoginWithPasskeys => 'Effortless login with passkeys'; + + @override + String get learnMoreAboutPasskeys => 'Learn more about passkeys'; + + @override + String get noNeedToRememberPassword => 'No need to remember a password'; + + @override + String get useYourBiometrics => 'Enhanced security with biometrics or screen lock'; + + @override + String get syncAcrossDevices => 'Sync across devices'; + + @override + String get createPasskey => 'Create passkey'; + + @override + String get unsupportedPlatform => 'Unsupported Platform'; + + @override + String get storedOn => 'Stored on'; + + @override + String get lastUsed => 'Last used'; + + @override + String get rename => 'Rename'; + + @override + String get revoke => 'Revoke'; + + @override + String get continueTradingButtonText => 'Continue trading'; + + @override + String get addMorePasskeysButtonText => 'Add more passkeys'; + + @override + String get unableToSetupPasskey => 'Unable to setup passkey'; + + @override + String get unableToSetupPasskeyDescription => 'We encountered an issue while setting up your passkey. The process might have been interrupted, or the session timed out. Please try again.'; + + @override + String get passkeysOffErrorTitle => 'The Passkeys service is unavailable'; + + @override + String get never => 'Never'; + + @override + String get unable_to_process_your_request => 'Unable to process your request'; + + @override + String get unable_to_process_your_request_description => 'We’re experiencing a temporary issue in processing your request. Please try again later.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_pl.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_pl.dart new file mode 100644 index 000000000..6dc9f0359 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_pl.dart @@ -0,0 +1,173 @@ +import 'deriv_passkeys_localizations.dart'; + +/// The translations for Polish (`pl`). +class DerivPasskeysLocalizationsPl extends DerivPasskeysLocalizations { + DerivPasskeysLocalizationsPl([String locale = 'pl']) : super(locale); + + @override + String get passkeyCreatedSuccessTitle => 'Udało się!'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return 'Twoje konto jest teraz zabezpieczone passkey. Zarządzaj swoim passkey za pomocą ustawień konta $platformName.'; + } + + @override + String get continueButtonText => 'Kontynuuj'; + + @override + String get unexpectedError => 'Wystąpił nieoczekiwany błąd!'; + + @override + String get unexpectedErrorDescription => 'Spróbuj później.'; + + @override + String get ok => 'Ok'; + + @override + String get experienceSaferLogins => 'Doświadcz bezpieczniejszych logowań'; + + @override + String get enhanceSecurity => 'Zwiększone bezpieczeństwo jest na wyciągnięcie ręki.'; + + @override + String get here => 'tutaj'; + + @override + String get effortlessLogin => 'Łatwe logowanie za pomocą passkeys'; + + @override + String get whatArePasskeys => 'Czym są passkeys?'; + + @override + String get whatArePasskeysDescriptionPoint1 => 'Bezpieczna alternatywa dla haseł.'; + + @override + String get whatArePasskeysDescriptionPoint2 => 'Odblokuj swoje konto jak telefon - za pomocą danych biometrycznych, skanowania twarzy lub PIN-u.'; + + @override + String get whyPasskeys => 'Dlaczego Passkeys?'; + + @override + String get whyPasskeysDescription1 => 'Dodatkowa warstwa bezpieczeństwa.'; + + @override + String get whyPasskeysDescription2 => 'Chroni przed nieautoryzowanym dostępem i phishingiem.'; + + @override + String get howToCreatePasskey => 'Jak utworzyć passkey?'; + + @override + String get howToCreatePasskeyDescription1 => 'Przejdź do ‘Ustawień konta’ na Deriv.'; + + @override + String get howToCreatePasskeyDescription2 => 'Możesz utworzyć jedno hasło na urządzenie.'; + + @override + String get p2pHowToCreatePasskey => 'Jak utworzyć klucz hasłowy?'; + + @override + String get p2pHowToCreatePasskeyDescription1 => 'Przejdź do „Profil” w aplikacji Deriv P2P.'; + + @override + String get p2pHowToCreatePasskeyDescription2 => 'Stuknij „Kody haseł”, aby utworzyć klucz hasła.'; + + @override + String get whereArePasskeysSaved => 'Gdzie są zapisywane passkeys?'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'Android: menedżer haseł Google.'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS: pęk kluczy iCloud.'; + + @override + String get whatHappensIfEmailChanged => 'Co się stanie, jeśli e-mail mojego konta Deriv zostanie zmieniony?'; + + @override + String get whatHappensIfEmailChangedDescription1 => 'Bez problemu! Twój Passkey nadal działa.'; + + @override + String get whatHappensIfEmailChangedDescription2 => 'Zaloguj się do Deriv przy użyciu istniejącego Passkey.'; + + @override + String get tips => 'Porady'; + + @override + String get beforeUsingPasskeys => 'Przed użyciem passkeys'; + + @override + String get enableScreenLock => 'Włącz blokadę ekranu na swoim urządzeniu.'; + + @override + String get signInGoogleOrIcloud => 'Zaloguj się na swoje konto Google lub iCloud.'; + + @override + String get enableBluetooth => 'Włącz Bluetooth.'; + + @override + String get noPasskeyFound => 'Nie znaleziono passkey!'; + + @override + String get noPasskeyFoundDescription => 'Aby korzystać z tej funkcji, utwórz passkey.'; + + @override + String get maybeLater => 'Może później'; + + @override + String get effortlessLoginWithPasskeys => 'Łatwe logowanie za pomocą passkeys'; + + @override + String get learnMoreAboutPasskeys => 'Dowiedz się więcej o passkeys'; + + @override + String get noNeedToRememberPassword => 'Nie ma potrzeby zapamiętywania hasła'; + + @override + String get useYourBiometrics => 'Zwiększone bezpieczeństwo dzięki biometrii lub blokadzie ekranu'; + + @override + String get syncAcrossDevices => 'Synchronizacja między urządzeniami'; + + @override + String get createPasskey => 'Utwórz passkey'; + + @override + String get unsupportedPlatform => 'Nieobsługiwana platforma'; + + @override + String get storedOn => 'Przechowywane na'; + + @override + String get lastUsed => 'Ostatnio używane'; + + @override + String get rename => 'Zmień nazwę'; + + @override + String get revoke => 'Odwołać'; + + @override + String get continueTradingButtonText => 'Kontynuuj handlowanie'; + + @override + String get addMorePasskeysButtonText => 'Więcej passkeys'; + + @override + String get unableToSetupPasskey => 'Nie można ustawić passkey'; + + @override + String get unableToSetupPasskeyDescription => 'Napotkaliśmy problem podczas konfigurowania Passkey. Proces mógł zostać przerwany lub upłynął limit czasu sesji. Proszę spróbuj ponownie.'; + + @override + String get passkeysOffErrorTitle => 'Usługa Passkeys jest niedostępna'; + + @override + String get never => 'Nigdy'; + + @override + String get unable_to_process_your_request => 'Nie można przetworzyć żądania'; + + @override + String get unable_to_process_your_request_description => 'Mamy tymczasowy problem z przetwarzaniem Twojej prośby. Spróbuj ponownie później.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_pt.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_pt.dart new file mode 100644 index 000000000..e8d959a8c --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_pt.dart @@ -0,0 +1,173 @@ +import 'deriv_passkeys_localizations.dart'; + +/// The translations for Portuguese (`pt`). +class DerivPasskeysLocalizationsPt extends DerivPasskeysLocalizations { + DerivPasskeysLocalizationsPt([String locale = 'pt']) : super(locale); + + @override + String get passkeyCreatedSuccessTitle => 'Concluído com sucesso!'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return 'A sua conta está agora protegida com uma chave de acesso. Pode fazer a gestão da sua chave de acesso através das definições da sua conta $platformName.'; + } + + @override + String get continueButtonText => 'Continuar'; + + @override + String get unexpectedError => 'Ocorreu um erro inesperado!'; + + @override + String get unexpectedErrorDescription => 'Por favor, tente novamente mais tarde.'; + + @override + String get ok => 'Ok'; + + @override + String get experienceSaferLogins => 'Experimente inícios de sessão mais seguros'; + + @override + String get enhanceSecurity => 'Segurança reforçada à distância de um clique.'; + + @override + String get here => 'aqui'; + + @override + String get effortlessLogin => 'Início de sessão simplificado com as chaves de acesso'; + + @override + String get whatArePasskeys => 'O que são chaves de acesso?'; + + @override + String get whatArePasskeysDescriptionPoint1 => 'Alternativa segura às palavras-passe.'; + + @override + String get whatArePasskeysDescriptionPoint2 => 'Desbloqueie a sua conta com a mesma facilidade que desbloqueia o seu telemóvel - seja através de biometria, reconhecimento facial ou introdução de PIN.'; + + @override + String get whyPasskeys => 'Porque é que se utilizam chaves de acesso?'; + + @override + String get whyPasskeysDescription1 => 'Camada de segurança adicional.'; + + @override + String get whyPasskeysDescription2 => 'Protege contra o acesso não autorizado e phishing.'; + + @override + String get howToCreatePasskey => 'Como criar uma chave de acesso?'; + + @override + String get howToCreatePasskeyDescription1 => 'Aceda às \"Definições de conta\" na Deriv.'; + + @override + String get howToCreatePasskeyDescription2 => 'Pode criar uma passkey por dispositivo.'; + + @override + String get p2pHowToCreatePasskey => 'Como pode criar uma passkey?'; + + @override + String get p2pHowToCreatePasskeyDescription1 => 'Aceda à secção \"Perfil\" na sua aplicação Deriv P2P.'; + + @override + String get p2pHowToCreatePasskeyDescription2 => 'Selecione \"Passkeys\" para criar a sua chave de acesso.'; + + @override + String get whereArePasskeysSaved => 'Onde são guardadas as chaves de acesso?'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'Android: Gestor de palavras-passe do Google.'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS: iCloud keychain.'; + + @override + String get whatHappensIfEmailChanged => 'O que acontece se o e-mail da minha conta Deriv for alterado?'; + + @override + String get whatHappensIfEmailChangedDescription1 => 'Não tem problema! A sua passkey ainda funciona.'; + + @override + String get whatHappensIfEmailChangedDescription2 => 'Inicie sessão na Deriv com a sua passkey.'; + + @override + String get tips => 'Dicas'; + + @override + String get beforeUsingPasskeys => 'Antes de utilizar as chaves de acesso'; + + @override + String get enableScreenLock => 'Ative o bloqueio de ecrã no seu dispositivo.'; + + @override + String get signInGoogleOrIcloud => 'Inicie sessão na sua conta Google ou iCloud.'; + + @override + String get enableBluetooth => 'Ativar Bluetooth.'; + + @override + String get noPasskeyFound => 'Não foi encontrada nenhuma chave de acesso!'; + + @override + String get noPasskeyFoundDescription => 'Por favor, crie uma chave de acesso para utilizar esta funcionalidade.'; + + @override + String get maybeLater => 'Talvez mais tarde'; + + @override + String get effortlessLoginWithPasskeys => 'Início de sessão simplificado com as chaves de acesso'; + + @override + String get learnMoreAboutPasskeys => 'Saiba mais sobre as chaves de acesso'; + + @override + String get noNeedToRememberPassword => 'Não memorizar a palavra-passe'; + + @override + String get useYourBiometrics => 'Segurança reforçada com dados biométricos ou bloqueio do ecrã'; + + @override + String get syncAcrossDevices => 'Sincronizar entre dispositivos'; + + @override + String get createPasskey => 'Criar passkey'; + + @override + String get unsupportedPlatform => 'Plataforma não suportada'; + + @override + String get storedOn => 'Armazenado em'; + + @override + String get lastUsed => 'Última utilização'; + + @override + String get rename => 'Renomear'; + + @override + String get revoke => 'Revogar'; + + @override + String get continueTradingButtonText => 'Continuar a negociar'; + + @override + String get addMorePasskeysButtonText => 'Adicionar mais passkeys'; + + @override + String get unableToSetupPasskey => 'Não é possível configurar a passkey'; + + @override + String get unableToSetupPasskeyDescription => 'Houve um problema ao configurar a sua passkey. O processo pode ter sido interrompido ou a sessão expirou. Por favor, tente novamente.'; + + @override + String get passkeysOffErrorTitle => 'O serviço de Passkeys não está disponível'; + + @override + String get never => 'Nunca'; + + @override + String get unable_to_process_your_request => 'Não foi possível completar o processamento do seu pedido'; + + @override + String get unable_to_process_your_request_description => 'Surgiu um problema temporário no processamento do seu pedido. Por favor, tente novamente mais tarde.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_ru.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_ru.dart new file mode 100644 index 000000000..00bf11e67 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_ru.dart @@ -0,0 +1,173 @@ +import 'deriv_passkeys_localizations.dart'; + +/// The translations for Russian (`ru`). +class DerivPasskeysLocalizationsRu extends DerivPasskeysLocalizations { + DerivPasskeysLocalizationsRu([String locale = 'ru']) : super(locale); + + @override + String get passkeyCreatedSuccessTitle => 'Готово!'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return 'Теперь ваша учетная запись защищена passkey. Управляйте своим passkey через настройки учетной записи $platformName.'; + } + + @override + String get continueButtonText => 'Продолжить'; + + @override + String get unexpectedError => 'Произошла неожиданная ошибка!'; + + @override + String get unexpectedErrorDescription => 'Попробуйте позже.'; + + @override + String get ok => 'Ok'; + + @override + String get experienceSaferLogins => 'Испытайте более безопасный вход в систему'; + + @override + String get enhanceSecurity => 'Повышенная безопасность — всего лишь одно касание.'; + + @override + String get here => 'здесь'; + + @override + String get effortlessLogin => 'Легкий вход в систему с помощью passkeys'; + + @override + String get whatArePasskeys => 'Что такое passkeys?'; + + @override + String get whatArePasskeysDescriptionPoint1 => 'Безопасная альтернатива паролям.'; + + @override + String get whatArePasskeysDescriptionPoint2 => 'Разблокируйте свою учетную запись, как телефон — с помощью биометрии, сканирования лица или PIN-кода.'; + + @override + String get whyPasskeys => 'Зачем нужны passkeys?'; + + @override + String get whyPasskeysDescription1 => 'Дополнительный уровень безопасности.'; + + @override + String get whyPasskeysDescription2 => 'Защищает от несанкционированного доступа и фишинга.'; + + @override + String get howToCreatePasskey => 'Как создать passkey?'; + + @override + String get howToCreatePasskeyDescription1 => 'Перейдите в ‘Настройки учетной записи’ на Deriv.'; + + @override + String get howToCreatePasskeyDescription2 => 'Вы можете создать один passkey для каждого устройства.'; + + @override + String get p2pHowToCreatePasskey => 'Как создать пароль?'; + + @override + String get p2pHowToCreatePasskeyDescription1 => 'Перейдите на «Профиль» в приложении Deriv P2P.'; + + @override + String get p2pHowToCreatePasskeyDescription2 => 'Нажмите «Пароли», чтобы создать свой пароль.'; + + @override + String get whereArePasskeysSaved => 'Где хранятся passkeys?'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'Android: Менеджер паролей Google.'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS: связка ключей iCloud.'; + + @override + String get whatHappensIfEmailChanged => 'Что произойдет, если адрес электронной почты моей учетной записи Deriv изменится?'; + + @override + String get whatHappensIfEmailChangedDescription1 => 'Нет проблем! Ваш passkey по-прежнему работает.'; + + @override + String get whatHappensIfEmailChangedDescription2 => 'Войдите в Deriv, используя существующий passkey.'; + + @override + String get tips => 'Советы'; + + @override + String get beforeUsingPasskeys => 'Перед использованием passkey'; + + @override + String get enableScreenLock => 'Включите блокировку экрана на своем устройстве.'; + + @override + String get signInGoogleOrIcloud => 'Войдите в свою учетную запись Google или iCloud.'; + + @override + String get enableBluetooth => 'Включите Bluetooth.'; + + @override + String get noPasskeyFound => 'Passkey не найден!'; + + @override + String get noPasskeyFoundDescription => 'Чтобы использовать эту функцию, создайте Passkey.'; + + @override + String get maybeLater => 'Может быть позже'; + + @override + String get effortlessLoginWithPasskeys => 'Легкий вход в систему с помощью passkeys'; + + @override + String get learnMoreAboutPasskeys => 'Узнайте больше о passkeys'; + + @override + String get noNeedToRememberPassword => 'Нет необходимости запоминать пароль'; + + @override + String get useYourBiometrics => 'Повышенная безопасность с помощью биометрии или блокировки экрана'; + + @override + String get syncAcrossDevices => 'Синхронизация между устройствами'; + + @override + String get createPasskey => 'Создать passkey'; + + @override + String get unsupportedPlatform => 'Неподдерживаемая платформа'; + + @override + String get storedOn => 'Хранится в'; + + @override + String get lastUsed => 'Недавние'; + + @override + String get rename => 'Переименовать'; + + @override + String get revoke => 'Отменить'; + + @override + String get continueTradingButtonText => 'Продолжить торговлю'; + + @override + String get addMorePasskeysButtonText => 'Больше passkeys'; + + @override + String get unableToSetupPasskey => 'Невозможно настроить passkey'; + + @override + String get unableToSetupPasskeyDescription => 'Мы столкнулись с проблемой при настройке вашего passkey. Возможно, процесс был прерван, или сессия завершилась по таймеру. Пожалуйста, попробуйте еще раз.'; + + @override + String get passkeysOffErrorTitle => 'Служба Passkeys недоступна'; + + @override + String get never => 'Никогда'; + + @override + String get unable_to_process_your_request => 'Не удалось обработать ваш запрос'; + + @override + String get unable_to_process_your_request_description => 'При обработке вашего запроса возникла временная проблема. Пожалуйста, повторите попытку позже.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_si.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_si.dart new file mode 100644 index 000000000..fd38c8398 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_si.dart @@ -0,0 +1,173 @@ +import 'deriv_passkeys_localizations.dart'; + +/// The translations for Sinhala Sinhalese (`si`). +class DerivPasskeysLocalizationsSi extends DerivPasskeysLocalizations { + DerivPasskeysLocalizationsSi([String locale = 'si']) : super(locale); + + @override + String get passkeyCreatedSuccessTitle => 'සාර්ථකයි!'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return 'ඔබගේ ගිණුම දැන් Passkey සුරක්ෂිත කර ඇත. ඔබේ $platformName ගිණු ම් සැකසුම් හරහා ඔබගේ passkey කළමනාකරණය කරන්න.'; + } + + @override + String get continueButtonText => 'ඉදිරියට යන්න'; + + @override + String get unexpectedError => 'අනපේක්ෂිත දෝෂයක් ඇති විය!'; + + @override + String get unexpectedErrorDescription => 'කරුණාකර පසුව උත්සාහ කරන්න.'; + + @override + String get ok => 'Ok'; + + @override + String get experienceSaferLogins => 'ආරක්ෂිත පිවිසුම් අත්විඳින්න'; + + @override + String get enhanceSecurity => 'වැඩි දියුණු කළ ආරක්ෂාව එක ටැප් එකක් දුරින්.'; + + @override + String get here => 'මෙන්න'; + + @override + String get effortlessLogin => 'Passkeys සමඟ වෙහෙස නොබලා පිවිසීම'; + + @override + String get whatArePasskeys => 'Passkeys යනු කුමක්ද?'; + + @override + String get whatArePasskeysDescriptionPoint1 => 'මුරපද සඳහා ආරක්ෂිත විකල්පයක්.'; + + @override + String get whatArePasskeysDescriptionPoint2 => 'ජෛවමිතික, මුහුණු ස්කෑන් කිරීම හෝ PIN සමඟ ඔබේ දුරකථනයයේ අකාරයටම ඔබේ ගිණුම අගුළු හරින්න.'; + + @override + String get whyPasskeys => 'Passkeys අවශ්‍ය වන්නේ ඇයි?'; + + @override + String get whyPasskeysDescription1 => 'අමතර ආරක්ෂක ස්ථරය.'; + + @override + String get whyPasskeysDescription2 => 'අනවසර ප්‍රවේශයෙන් සහ තතුබෑම්වලින් ආරක්ෂා කරයි.'; + + @override + String get howToCreatePasskey => 'නිර්මාණය Passkey කෙසේද?'; + + @override + String get howToCreatePasskeyDescription1 => 'Deriv හි \'ගිණුම් සැකසීම්\' වෙත යන්න.'; + + @override + String get howToCreatePasskeyDescription2 => 'ඔබට එක් උපාංගයකට එක් passkey එකක් සෑදිය හැක.'; + + @override + String get p2pHowToCreatePasskey => 'මුරපද නිර්මාණය කරන්නේ කෙසේද?'; + + @override + String get p2pHowToCreatePasskeyDescription1 => 'ඔබේ ඩෙරිව් පී 2 පී යෙදුමේ \'පැතිකඩ\' වෙත යන්න.'; + + @override + String get p2pHowToCreatePasskeyDescription2 => 'ඔබගේ මුරපද නිර්මාණය කිරීම සඳහා \'මුරපද\' තට්ටු කරන්න.'; + + @override + String get whereArePasskeysSaved => 'Passkey සුරකින්නේ කොහේද?'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'Android: Google මුරපද කළමනාකරු.'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS: iCloud යතුරු දාමය.'; + + @override + String get whatHappensIfEmailChanged => 'මගේ Deriv ගිණුමේ විද්යුත් තැපෑල වෙනස් කළහොත් කුමක් සිදුවේද?'; + + @override + String get whatHappensIfEmailChangedDescription1 => 'ප්‍රශ්නයක් නැත! ඔබේ passkey එක තවමත් ක්‍රියා කරයි.'; + + @override + String get whatHappensIfEmailChangedDescription2 => 'ඔබේ පවතින passkey සමඟින් Deriv වෙත පුරන්න.'; + + @override + String get tips => 'ඉඟි'; + + @override + String get beforeUsingPasskeys => 'Passkey භාවිතා කිරීමට පෙර'; + + @override + String get enableScreenLock => 'ඔබේ උපාංගයේ තිර අගුල සක්රීය කරන්න.'; + + @override + String get signInGoogleOrIcloud => 'ඔබේ Google හෝ iCloud ගිණුමට පිවිසෙන්න.'; + + @override + String get enableBluetooth => 'බ්ලූටූත් සක්රීය කරන්න.'; + + @override + String get noPasskeyFound => 'Passkey සොයාගත නොමැත!'; + + @override + String get noPasskeyFoundDescription => 'කරුණාකර මෙම විශේෂාංගය භාවිතා කිරීම සඳහා passkey යන්නක් සාදන්න.'; + + @override + String get maybeLater => 'සමහර විට පසුව'; + + @override + String get effortlessLoginWithPasskeys => 'Passkeys සමඟ වෙහෙස නොබලා පිවිසීම'; + + @override + String get learnMoreAboutPasskeys => 'Passkeys ගැන වැඩි විස්තර දැනගන්න'; + + @override + String get noNeedToRememberPassword => 'මුරපදයක් මතක තබා ගැනීමට අවශ්ය නැත'; + + @override + String get useYourBiometrics => 'ජෛවමිතික හෝ තිර අගුල සමඟ වැඩි දියුණු කළ ආරක්ෂාව'; + + @override + String get syncAcrossDevices => 'උපාංග හරහා සමමුහූර්ත කරන්න'; + + @override + String get createPasskey => 'Passkey සාදන්න'; + + @override + String get unsupportedPlatform => 'සහාය නොදක්වන වේදිකාව'; + + @override + String get storedOn => 'ගබඩා කර ඇත'; + + @override + String get lastUsed => 'අවසන් වරට භාවිත කළේ'; + + @override + String get rename => 'නැවත නම් කරන්න'; + + @override + String get revoke => 'අවලංගු කරන්න'; + + @override + String get continueTradingButtonText => 'දිගටම ගනුදෙනු කරන්න'; + + @override + String get addMorePasskeysButtonText => 'තවත් passkeys එක් කරන්න'; + + @override + String get unableToSetupPasskey => 'Passkey සැකසීමට නොහැකි විය'; + + @override + String get unableToSetupPasskeyDescription => 'ඔබගේ passkey සැකසීමේදී අපට ගැටලුවක් ඇති විය. ක්රියාවලියට බාධා ඇති විය හැකිය, නැතහොත් සැසිය කාලය අවසන් විය හැකිය. කරුණාකර නැවත උත්සාහ කරන්න.'; + + @override + String get passkeysOffErrorTitle => 'Passkeys සේවාව ලබා ගත නොහැක'; + + @override + String get never => 'කවදාවත්'; + + @override + String get unable_to_process_your_request => 'ඔබගේ ඉල්ලීම සැකසීමට නොහැකි විය'; + + @override + String get unable_to_process_your_request_description => 'ඔබගේ ඉල්ලීම සැකසීමේදී අපට තාවකාලික ගැටළුවක් අත්විඳිනවා. කරුණාකර පසුව නැවත උත්සාහ කරන්න.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_sw.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_sw.dart new file mode 100644 index 000000000..2c04831cc --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_sw.dart @@ -0,0 +1,173 @@ +import 'deriv_passkeys_localizations.dart'; + +/// The translations for Swahili (`sw`). +class DerivPasskeysLocalizationsSw extends DerivPasskeysLocalizations { + DerivPasskeysLocalizationsSw([String locale = 'sw']) : super(locale); + + @override + String get passkeyCreatedSuccessTitle => 'Mafanikio!'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return 'Akaunti yako sasa imehifadhiwa na passkey. Dhibiti passkey yako kupi tia mipangilio yako ya akaunti ya $platformName.'; + } + + @override + String get continueButtonText => 'Endelea'; + + @override + String get unexpectedError => 'Hitilafu yasiyotarajiwa!'; + + @override + String get unexpectedErrorDescription => 'Tafadhali jaribu tena.'; + + @override + String get ok => 'Ok'; + + @override + String get experienceSaferLogins => 'Pata kuingia salama'; + + @override + String get enhanceSecurity => 'Usalama ulioimarishwa uko karibu sana, unahitaji kubofya tu.'; + + @override + String get here => 'hapa'; + + @override + String get effortlessLogin => 'Kuingia bila juhudi kwa passkeys'; + + @override + String get whatArePasskeys => 'Nini passkeys?'; + + @override + String get whatArePasskeysDescriptionPoint1 => 'Njia mbadala ya kufanya nenosiri kuwa salama.'; + + @override + String get whatArePasskeysDescriptionPoint2 => 'Fungua akaunti yako kama vile simu yako - kwa biyometriki, kuskani uso au PIN.'; + + @override + String get whyPasskeys => 'Kwa nini passkeys?'; + + @override + String get whyPasskeysDescription1 => 'Safu ya ziada ya usalama.'; + + @override + String get whyPasskeysDescription2 => 'Kinga dhidi ya ufikiaji ambao haujaidhinishwa na hadaa.'; + + @override + String get howToCreatePasskey => 'Jinsi ya kuunda passkey?'; + + @override + String get howToCreatePasskeyDescription1 => 'Nenda katika \'Mipangilio ya Akaunti\' kwenye Deriv.'; + + @override + String get howToCreatePasskeyDescription2 => 'Unaweza kuunda passkey moja kwa kila kifaa.'; + + @override + String get p2pHowToCreatePasskey => 'Jinsi ya kuunda passkey?'; + + @override + String get p2pHowToCreatePasskeyDescription1 => 'Nenda kwenye \'Profaili\' katika programu yako ya Deriv P2P.'; + + @override + String get p2pHowToCreatePasskeyDescription2 => 'Gonga \'Passkeys\' ili kuunda nenosiri lako.'; + + @override + String get whereArePasskeysSaved => 'Passkeys zimehifadhiwa wapi?'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'Android: Google password manager.'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS: iCloud keychain.'; + + @override + String get whatHappensIfEmailChanged => 'Nini kinatokea ikiwa barua pepe yangu ya akaunti ya Deriv imebadil?'; + + @override + String get whatHappensIfEmailChangedDescription1 => 'Hakuna tatizo! Passkey yako bado inafanya kazi.'; + + @override + String get whatHappensIfEmailChangedDescription2 => 'Ingia kwenye Deriv na passkey yako iliyopo.'; + + @override + String get tips => 'Vidokezo'; + + @override + String get beforeUsingPasskeys => 'Kabla ya kutumia passkeys'; + + @override + String get enableScreenLock => 'Wezesha kufunga skrini kwenye kifaa chako.'; + + @override + String get signInGoogleOrIcloud => 'Ingia kwenye akaunti yako ya Google au iCloud.'; + + @override + String get enableBluetooth => 'Wezesha Bluetooth.'; + + @override + String get noPasskeyFound => 'Hakuna passkey iliyopatikana!'; + + @override + String get noPasskeyFoundDescription => 'Tafadhali unda passkey ili kutumia kipengele hiki.'; + + @override + String get maybeLater => 'Labda baadaye'; + + @override + String get effortlessLoginWithPasskeys => 'Kuingia bila juhudi kwa passkeys'; + + @override + String get learnMoreAboutPasskeys => 'Jifunze zaidi kuhusu passkeys'; + + @override + String get noNeedToRememberPassword => 'Hakuna haja ya kukumbuka nenosiri'; + + @override + String get useYourBiometrics => 'Usalama ulioboreshwa na biometriki au kufunga skrini'; + + @override + String get syncAcrossDevices => 'Sawazisha katika vifaa'; + + @override + String get createPasskey => 'Unda passkey'; + + @override + String get unsupportedPlatform => 'Jukwaa isiyosaidiwa'; + + @override + String get storedOn => 'Imehifadhiwa kwenye'; + + @override + String get lastUsed => 'Imetumika mwisho'; + + @override + String get rename => 'Badilisha jina'; + + @override + String get revoke => 'Kufuta'; + + @override + String get continueTradingButtonText => 'Endelea kufanya biashara'; + + @override + String get addMorePasskeysButtonText => 'Ongeza passkeys nyingine'; + + @override + String get unableToSetupPasskey => 'Haiwezi kuanzisha kifungu cha passkey'; + + @override + String get unableToSetupPasskeyDescription => 'Tulikutana na shida wakati wa kuanzisha passkey lako. Utaratibu huo unaweza kuingiliwa, au kikao kimekamilika. Tafadhali jaribu tena.'; + + @override + String get passkeysOffErrorTitle => 'Huduma ya Passkeys haipatikani'; + + @override + String get never => 'Kamwe'; + + @override + String get unable_to_process_your_request => 'Haiwezi kushughulikia ombi lako'; + + @override + String get unable_to_process_your_request_description => 'Tunapata shida ya muda katika kusindika ombi lako. Tafadhali jaribu tena baadaye.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_th.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_th.dart new file mode 100644 index 000000000..b48f49d11 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_th.dart @@ -0,0 +1,173 @@ +import 'deriv_passkeys_localizations.dart'; + +/// The translations for Thai (`th`). +class DerivPasskeysLocalizationsTh extends DerivPasskeysLocalizations { + DerivPasskeysLocalizationsTh([String locale = 'th']) : super(locale); + + @override + String get passkeyCreatedSuccessTitle => 'สำเร็จแล้ว!'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return 'ตอนนี้บัญชีของคุณได้รับการรักษาความปลอดภัยด้วย Passkey โดยสามารถจัดการ Passkey นี้ได้ผ่านการตั้งค่าบัญชี $platformName ของคุณ'; + } + + @override + String get continueButtonText => 'ดำเนินการต่อ'; + + @override + String get unexpectedError => 'มีข้อผิดพลาดเกิดขึ้น!'; + + @override + String get unexpectedErrorDescription => 'โปรดลองอีกครั้งในภายหลัง'; + + @override + String get ok => 'Ok'; + + @override + String get experienceSaferLogins => 'สัมผัสประสบการณ์การเข้าสู่ระบบที่ปลอดภัยขึ้น'; + + @override + String get enhanceSecurity => 'การเสริมความปลอดภัยที่อยู่แค่เพียงคุณแตะปุ่มเดียว'; + + @override + String get here => 'ที่นี่'; + + @override + String get effortlessLogin => 'เข้าสู่ระบบได้อย่างง่ายดายด้วย Passkey'; + + @override + String get whatArePasskeys => 'Passkey คืออะไร?'; + + @override + String get whatArePasskeysDescriptionPoint1 => 'ทางเลือกที่ปลอดภัยนอกเหนือไปจากรหัสผ่าน'; + + @override + String get whatArePasskeysDescriptionPoint2 => 'ปลดล็อคบัญชีคุณได้เหมือนที่ทำในโทรศัพท์ - ผ่านไบโอเมตริกซ์ การสแกนใบหน้า หรือหมายเลข PIN'; + + @override + String get whyPasskeys => 'ทำไมต้องใช้ Passkey?'; + + @override + String get whyPasskeysDescription1 => 'เพิ่มอีกชั้นความปลอดภัยพิเศษ'; + + @override + String get whyPasskeysDescription2 => 'กันการเข้าถึงที่ไม่ได้อนุญาตและการโจมตีแบบฟิชชิ่ง'; + + @override + String get howToCreatePasskey => 'จะสร้าง Passkey ได้อย่างไร?'; + + @override + String get howToCreatePasskeyDescription1 => 'ไปที่ \'การตั้งค่าบัญชี\' บน Deriv'; + + @override + String get howToCreatePasskeyDescription2 => 'คุณสามารถสร้างหนึ่ง Passkey ต่ออุปกรณ์'; + + @override + String get p2pHowToCreatePasskey => 'จะสร้าง Passkey ได้อย่างไร?'; + + @override + String get p2pHowToCreatePasskeyDescription1 => 'ไปที่ \'โปรไฟล์\' ในแอป Deriv P2P ของคุณ'; + + @override + String get p2pHowToCreatePasskeyDescription2 => 'แตะ \'Passkeys\' เพื่อสร้าง Passkey ของคุณ'; + + @override + String get whereArePasskeysSaved => 'Passkey จะถูกบันทึกไว้ที่ไหน?'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'Android: ตัวจัดการรหัสผ่าน Google'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS: พวงกุญแจ iCloud'; + + @override + String get whatHappensIfEmailChanged => 'จะเกิดอะไรขึ้นหากอีเมล์บัญชี Deriv ของฉันเปลี่ยนไป?'; + + @override + String get whatHappensIfEmailChangedDescription1 => 'ไม่มีปัญหา! Passkey ของคุณยังใช้งานได้อยู่'; + + @override + String get whatHappensIfEmailChangedDescription2 => 'ลงชื่อเข้าใช้ Deriv ด้วย Passkey ที่มีอยู่ของคุณ'; + + @override + String get tips => 'เคล็ดลับ'; + + @override + String get beforeUsingPasskeys => 'ก่อนที่จะใช้ Passkey'; + + @override + String get enableScreenLock => 'เปิดใช้งานการล็อคหน้าจอบนอุปกรณ์ของคุณ'; + + @override + String get signInGoogleOrIcloud => 'ลงชื่อเข้าใช้บัญชี Google หรือ iCloud ของคุณ'; + + @override + String get enableBluetooth => 'เปิดใช้งานบลูทูธ'; + + @override + String get noPasskeyFound => 'ไม่พบ Passkey!'; + + @override + String get noPasskeyFoundDescription => 'กรุณาสร้าง Passkey เพื่อใช้ฟีเจอร์นี้'; + + @override + String get maybeLater => 'ไว้ทีหลัง'; + + @override + String get effortlessLoginWithPasskeys => 'เข้าสู่ระบบได้อย่างง่ายดายด้วย Passkey'; + + @override + String get learnMoreAboutPasskeys => 'เรียนรู้เพิ่มเติมเกี่ยวกับ Passkey'; + + @override + String get noNeedToRememberPassword => 'ไม่จำเป็นต้องจดจำรหัสผ่าน'; + + @override + String get useYourBiometrics => 'เพิ่มความปลอดภัยด้วยไบโอเมตริกซ์หรือการล็อคหน้าจอ'; + + @override + String get syncAcrossDevices => 'ซิงค์ได้ระหว่างอุปกรณ์ต่างๆ'; + + @override + String get createPasskey => 'สร้าง Passkey'; + + @override + String get unsupportedPlatform => 'แพลตฟอร์มที่ไม่รองรับ'; + + @override + String get storedOn => 'เก็บไว้ใน'; + + @override + String get lastUsed => 'ใช้ครั้งล่าสุด'; + + @override + String get rename => 'เปลี่ยนชื่อ'; + + @override + String get revoke => 'เพิกถอน'; + + @override + String get continueTradingButtonText => 'ดำเนินการเทรดต่อ'; + + @override + String get addMorePasskeysButtonText => 'เพิ่มจำนวน Passkey'; + + @override + String get unableToSetupPasskey => 'ไม่สามารถตั้งค่า Passkey ได้'; + + @override + String get unableToSetupPasskeyDescription => 'เราพบปัญหาขณะตั้งค่า Passkey ของคุณ กระบวนการอาจถูกขัดจังหวะหรือเซสชั่นหมดเวลา โปรดลองอีกครั้ง'; + + @override + String get passkeysOffErrorTitle => 'บริการ Passkey ไม่พร้อมใช้งาน'; + + @override + String get never => 'ไม่เคย'; + + @override + String get unable_to_process_your_request => 'ไม่สามารถประมวลผลคำขอของคุณได้'; + + @override + String get unable_to_process_your_request_description => 'เรากำลังประสบปัญหาชั่วคราวในการประมวลผลคำขอของคุณ โปรดลองอีกครั้งในภายหลัง'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_tr.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_tr.dart new file mode 100644 index 000000000..ee9c2d250 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_tr.dart @@ -0,0 +1,173 @@ +import 'deriv_passkeys_localizations.dart'; + +/// The translations for Turkish (`tr`). +class DerivPasskeysLocalizationsTr extends DerivPasskeysLocalizations { + DerivPasskeysLocalizationsTr([String locale = 'tr']) : super(locale); + + @override + String get passkeyCreatedSuccessTitle => 'Başarı!'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return 'Hesabınız artık bir şifre ile güvence altına alınmıştır. $platformName hesap ayarlarınızdan passkey\'inizi yönetin.'; + } + + @override + String get continueButtonText => 'Devam et'; + + @override + String get unexpectedError => 'Beklenmedik bir hata oluştu!'; + + @override + String get unexpectedErrorDescription => 'Lütfen daha sonra deneyin.'; + + @override + String get ok => 'Tamam'; + + @override + String get experienceSaferLogins => 'Daha güvenli oturum açma deneyimini yaşayın'; + + @override + String get enhanceSecurity => 'Gelişmiş güvenlik sadece bir dokunuş uzağınızda.'; + + @override + String get here => 'burada'; + + @override + String get effortlessLogin => 'Passkey tuşları ile zahmetsiz giriş'; + + @override + String get whatArePasskeys => 'Passkeys nedir?'; + + @override + String get whatArePasskeysDescriptionPoint1 => 'Şifrelere güvenli alternatif.'; + + @override + String get whatArePasskeysDescriptionPoint2 => 'Hesabınızın kilidini telefonunuz gibi açın - biyometri, yüz tarama veya PIN ile.'; + + @override + String get whyPasskeys => 'Neden Passkey?'; + + @override + String get whyPasskeysDescription1 => 'Ekstra güvenlik katmanı.'; + + @override + String get whyPasskeysDescription2 => 'Yetkisiz erişime ve kimlik avına karşı kalkanlar.'; + + @override + String get howToCreatePasskey => 'Passkey nasıl oluşturulur?'; + + @override + String get howToCreatePasskeyDescription1 => 'Deriv\'de \'Hesap Ayarları\'na gidin.'; + + @override + String get howToCreatePasskeyDescription2 => 'Cihaz başına bir passkey oluşturabilirsiniz.'; + + @override + String get p2pHowToCreatePasskey => 'Passkey nasıl oluşturulur?'; + + @override + String get p2pHowToCreatePasskeyDescription1 => 'Deriv P2P uygulamanızdaki \'Profil\' bölümüne gidin.'; + + @override + String get p2pHowToCreatePasskeyDescription2 => 'Passkey\'inizi oluşturmak için \"Passkeys\" ögesine dokunun.'; + + @override + String get whereArePasskeysSaved => 'Passkeys nereye kaydedilir?'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'Android: Google şifre yöneticisi.'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS: iCloud anahtar zinciri.'; + + @override + String get whatHappensIfEmailChanged => 'Deriv hesabım e-posta adresim değiştirilirse ne olur?'; + + @override + String get whatHappensIfEmailChangedDescription1 => 'Sorun yok! Passkey hâlâ çalışıyor.'; + + @override + String get whatHappensIfEmailChangedDescription2 => 'Mevcut passkey ile Deriv\'de oturum açın.'; + + @override + String get tips => 'İpuçları'; + + @override + String get beforeUsingPasskeys => 'Passkey kullanmadan önce'; + + @override + String get enableScreenLock => 'Cihazınızda ekran kilidini etkinleştirin.'; + + @override + String get signInGoogleOrIcloud => 'Google veya iCloud hesabınızda oturum açın.'; + + @override + String get enableBluetooth => 'Bluetooth\'u etkinleştir.'; + + @override + String get noPasskeyFound => 'Passkey bulunamadı!'; + + @override + String get noPasskeyFoundDescription => 'Bu özelliği kullanmak için lütfen bir passkey oluşturun.'; + + @override + String get maybeLater => 'Belki daha sonra'; + + @override + String get effortlessLoginWithPasskeys => 'Passkey tuşları ile zahmetsiz giriş'; + + @override + String get learnMoreAboutPasskeys => 'Passkeys hakkında daha fazla bilgi edinin'; + + @override + String get noNeedToRememberPassword => 'Şifreyi hatırlamanıza gerek yok'; + + @override + String get useYourBiometrics => 'Biyometri veya ekran kilidi ile gelişmiş güvenlik'; + + @override + String get syncAcrossDevices => 'Cihazlar arasında senkronizasyon'; + + @override + String get createPasskey => 'Passkey oluştur'; + + @override + String get unsupportedPlatform => 'Desteklenmeyen Platform'; + + @override + String get storedOn => 'Şurada saklanır'; + + @override + String get lastUsed => 'Son kullanılan'; + + @override + String get rename => 'Yeniden Adlandır'; + + @override + String get revoke => 'İptal'; + + @override + String get continueTradingButtonText => 'Alım satıma devam'; + + @override + String get addMorePasskeysButtonText => 'Daha fazla passkeys ekle'; + + @override + String get unableToSetupPasskey => 'Passkey ayarlanamıyor'; + + @override + String get unableToSetupPasskeyDescription => 'Passkey ayarlarken bir sorunla karşılaştık. İşlem yarıda kesilmiş veya oturum zaman aşımına uğramış olabilir. Lütfen tekrar deneyin.'; + + @override + String get passkeysOffErrorTitle => 'Passkeys hizmeti kullanılamıyor'; + + @override + String get never => 'Asla'; + + @override + String get unable_to_process_your_request => 'Talebiniz işleme alınamıyor'; + + @override + String get unable_to_process_your_request_description => 'Talebinizin işlenmesinde geçici bir sorun yaşıyoruz. Lütfen daha sonra tekrar deneyin.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_uz.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_uz.dart new file mode 100644 index 000000000..9c232b4da --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_uz.dart @@ -0,0 +1,173 @@ +import 'deriv_passkeys_localizations.dart'; + +/// The translations for Uzbek (`uz`). +class DerivPasskeysLocalizationsUz extends DerivPasskeysLocalizations { + DerivPasskeysLocalizationsUz([String locale = 'uz']) : super(locale); + + @override + String get passkeyCreatedSuccessTitle => 'Success!'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return 'Your account is now secured with a passkey. Manage your passkey through your $platformName account settings.'; + } + + @override + String get continueButtonText => 'Continue'; + + @override + String get unexpectedError => 'An unexpected error occurred!'; + + @override + String get unexpectedErrorDescription => 'Please try again later.'; + + @override + String get ok => 'Ok'; + + @override + String get experienceSaferLogins => 'Experience safer logins'; + + @override + String get enhanceSecurity => 'Enhanced security is just a tap away.'; + + @override + String get here => 'here'; + + @override + String get effortlessLogin => 'Effortless login with passkeys'; + + @override + String get whatArePasskeys => 'What are passkeys?'; + + @override + String get whatArePasskeysDescriptionPoint1 => 'Secure alternative to passwords.'; + + @override + String get whatArePasskeysDescriptionPoint2 => 'Unlock your account like your phone - with biometrics, face scan or PIN.'; + + @override + String get whyPasskeys => 'Why passkeys?'; + + @override + String get whyPasskeysDescription1 => 'Extra security layer.'; + + @override + String get whyPasskeysDescription2 => 'Shields against unauthorised access and phishing.'; + + @override + String get howToCreatePasskey => 'How to create a passkey?'; + + @override + String get howToCreatePasskeyDescription1 => 'Go to ‘Account Settings’ on Deriv.'; + + @override + String get howToCreatePasskeyDescription2 => 'You can create one passkey per device.'; + + @override + String get p2pHowToCreatePasskey => 'How to create passkey?'; + + @override + String get p2pHowToCreatePasskeyDescription1 => 'Go to ‘Profile‘ in your Deriv P2P app.'; + + @override + String get p2pHowToCreatePasskeyDescription2 => 'Tap ‘Passkeys‘ to create your passkey.'; + + @override + String get whereArePasskeysSaved => 'Where are passkeys saved?'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'Android: Google password manager.'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS: iCloud keychain.'; + + @override + String get whatHappensIfEmailChanged => 'What happens if my Deriv account email is changed?'; + + @override + String get whatHappensIfEmailChangedDescription1 => 'No problem! Your passkey still works.'; + + @override + String get whatHappensIfEmailChangedDescription2 => 'Sign in to Deriv with your existing passkey.'; + + @override + String get tips => 'Tips'; + + @override + String get beforeUsingPasskeys => 'Before using passkeys'; + + @override + String get enableScreenLock => 'Enable screen lock on your device.'; + + @override + String get signInGoogleOrIcloud => 'Sign in to your Google or iCloud account.'; + + @override + String get enableBluetooth => 'Enable Bluetooth.'; + + @override + String get noPasskeyFound => 'No passkey found!'; + + @override + String get noPasskeyFoundDescription => 'Please create a passkey to use this feature.'; + + @override + String get maybeLater => 'Maybe later'; + + @override + String get effortlessLoginWithPasskeys => 'Effortless login with passkeys'; + + @override + String get learnMoreAboutPasskeys => 'Learn more about passkeys'; + + @override + String get noNeedToRememberPassword => 'No need to remember a password'; + + @override + String get useYourBiometrics => 'Enhanced security with biometrics or screen lock'; + + @override + String get syncAcrossDevices => 'Sync across devices'; + + @override + String get createPasskey => 'Create passkey'; + + @override + String get unsupportedPlatform => 'Unsupported Platform'; + + @override + String get storedOn => 'Stored on'; + + @override + String get lastUsed => 'Last used'; + + @override + String get rename => 'Rename'; + + @override + String get revoke => 'Revoke'; + + @override + String get continueTradingButtonText => 'Continue trading'; + + @override + String get addMorePasskeysButtonText => 'Add more passkeys'; + + @override + String get unableToSetupPasskey => 'Unable to setup passkey'; + + @override + String get unableToSetupPasskeyDescription => 'We encountered an issue while setting up your passkey. The process might have been interrupted, or the session timed out. Please try again.'; + + @override + String get passkeysOffErrorTitle => 'The Passkeys service is unavailable'; + + @override + String get never => 'Never'; + + @override + String get unable_to_process_your_request => 'Unable to process your request'; + + @override + String get unable_to_process_your_request_description => 'We’re experiencing a temporary issue in processing your request. Please try again later.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_vi.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_vi.dart new file mode 100644 index 000000000..91b548515 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_vi.dart @@ -0,0 +1,173 @@ +import 'deriv_passkeys_localizations.dart'; + +/// The translations for Vietnamese (`vi`). +class DerivPasskeysLocalizationsVi extends DerivPasskeysLocalizations { + DerivPasskeysLocalizationsVi([String locale = 'vi']) : super(locale); + + @override + String get passkeyCreatedSuccessTitle => 'Thành công!'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return 'Tài khoản của bạn hiện được bảo mật bằng passkey. Quản lý passkey của bạn thông qua cài đặt tài khoản $platformName của bạn.'; + } + + @override + String get continueButtonText => 'Tiếp tục'; + + @override + String get unexpectedError => 'Đã có lỗi bất ngờ xảy ra!'; + + @override + String get unexpectedErrorDescription => 'Vui lòng thử lại sau.'; + + @override + String get ok => 'Ok'; + + @override + String get experienceSaferLogins => 'Trải nghiệm đăng nhập an toàn hơn'; + + @override + String get enhanceSecurity => 'Bảo mật nâng cao chỉ cách một cú chạm.'; + + @override + String get here => 'ở đây'; + + @override + String get effortlessLogin => 'Đăng nhập dễ dàng với các passkeys'; + + @override + String get whatArePasskeys => 'Passkeys là gì?'; + + @override + String get whatArePasskeysDescriptionPoint1 => 'Thay thế an toàn cho mật khẩu.'; + + @override + String get whatArePasskeysDescriptionPoint2 => 'Mở khóa tài khoản của bạn như điện thoại của bạn - với sinh trắc học, quét khuôn mặt hoặc mã PIN.'; + + @override + String get whyPasskeys => 'Tại sao passkeys?'; + + @override + String get whyPasskeysDescription1 => 'Lớp bảo mật bổ sung.'; + + @override + String get whyPasskeysDescription2 => 'Bảo vệ chống truy cập trái phép và lừa đảo.'; + + @override + String get howToCreatePasskey => 'Làm thế nào để tạo passkey?'; + + @override + String get howToCreatePasskeyDescription1 => 'Chuyển đến \'Cài đặt tài khoản\' trên Deriv.'; + + @override + String get howToCreatePasskeyDescription2 => 'Bạn có thể tạo một passkey cho mỗi thiết bị.'; + + @override + String get p2pHowToCreatePasskey => 'Làm thế nào để tạo mật khẩu?'; + + @override + String get p2pHowToCreatePasskeyDescription1 => 'Chuyển đến \'Hồ sơ\' trong ứng dụng Deriv P2P của bạn.'; + + @override + String get p2pHowToCreatePasskeyDescription2 => 'Nhấn vào \'Phím mật khẩu\' để tạo mật khẩu của bạn.'; + + @override + String get whereArePasskeysSaved => 'Passkeys được lưu ở đâu?'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'Android: Trình quản lý mật khẩu Google.'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS: Móc khóa iCloud.'; + + @override + String get whatHappensIfEmailChanged => 'Điều gì xảy ra nếu email tài khoản Deriv của tôi bị thay đổi?'; + + @override + String get whatHappensIfEmailChangedDescription1 => 'Không có vấn đề gì! Khóa passkey của bạn vẫn hoạt động.'; + + @override + String get whatHappensIfEmailChangedDescription2 => 'Đăng nhập vào Deriv bằng passkey hiện có của bạn.'; + + @override + String get tips => 'Mẹo'; + + @override + String get beforeUsingPasskeys => 'Trước khi sử dụng passkeys'; + + @override + String get enableScreenLock => 'Bật khóa màn hình trên thiết bị của bạn.'; + + @override + String get signInGoogleOrIcloud => 'Đăng nhập vào tài khoản Google hoặc iCloud của bạn.'; + + @override + String get enableBluetooth => 'Bật Bluetooth.'; + + @override + String get noPasskeyFound => 'Không tìm thấy passkey!'; + + @override + String get noPasskeyFoundDescription => 'Vui lòng tạo passkey để sử dụng tính năng này.'; + + @override + String get maybeLater => 'Để sau'; + + @override + String get effortlessLoginWithPasskeys => 'Đăng nhập dễ dàng với các passkeys'; + + @override + String get learnMoreAboutPasskeys => 'Tìm hiểu thêm về passkeys'; + + @override + String get noNeedToRememberPassword => 'Không cần nhớ mật khẩu'; + + @override + String get useYourBiometrics => 'Tăng cường bảo mật với sinh trắc học hoặc khóa màn hình'; + + @override + String get syncAcrossDevices => 'Đồng bộ hóa trên các thiết bị'; + + @override + String get createPasskey => 'Tạo passkey'; + + @override + String get unsupportedPlatform => 'Nền tảng không được hỗ trợ'; + + @override + String get storedOn => 'Được lưu trữ trên'; + + @override + String get lastUsed => 'Lần sử dụng cuối'; + + @override + String get rename => 'Đổi tên'; + + @override + String get revoke => 'Thu hồi'; + + @override + String get continueTradingButtonText => 'Tiếp tục giao dịch'; + + @override + String get addMorePasskeysButtonText => 'Thêm passkeys'; + + @override + String get unableToSetupPasskey => 'Không thể thiết lập passkey'; + + @override + String get unableToSetupPasskeyDescription => 'Chúng tôi gặp sự cố khi thiết lập passkey của bạn. Quá trình có thể đã bị gián đoạn hoặc phiên đã hết thời gian. Vui lòng thử lại.'; + + @override + String get passkeysOffErrorTitle => 'Dịch vụ Passkeys không khả dụng'; + + @override + String get never => 'Không bao giờ'; + + @override + String get unable_to_process_your_request => 'Không thể xử lý yêu cầu của bạn'; + + @override + String get unable_to_process_your_request_description => 'Chúng tôi đang gặp sự cố tạm thời trong quá trình xử lý yêu cầu của bạn. Vui lòng thử lại sau.'; +} diff --git a/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_zh.dart b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_zh.dart new file mode 100644 index 000000000..bc205ddf0 --- /dev/null +++ b/packages/deriv_localizations/lib/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_zh.dart @@ -0,0 +1,517 @@ +import 'deriv_passkeys_localizations.dart'; + +/// The translations for Chinese (`zh`). +class DerivPasskeysLocalizationsZh extends DerivPasskeysLocalizations { + DerivPasskeysLocalizationsZh([String locale = 'zh']) : super(locale); + + @override + String get passkeyCreatedSuccessTitle => '成功!'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return '帳戶現在已使用 passkey 保護。請透過 <0/>$platformName 帳戶設定<0/>管理 passkey 。'; + } + + @override + String get continueButtonText => '繼續'; + + @override + String get unexpectedError => '發生不可預測錯誤!'; + + @override + String get unexpectedErrorDescription => '請稍後重試。'; + + @override + String get ok => '確定'; + + @override + String get experienceSaferLogins => '體驗更安全的登入'; + + @override + String get enhanceSecurity => '只需輕按一下即可增強安全性。'; + + @override + String get here => '這裡'; + + @override + String get effortlessLogin => '使用 passkey 輕鬆登入'; + + @override + String get whatArePasskeys => '什麼是 passkey ?'; + + @override + String get whatArePasskeysDescriptionPoint1 => '密碼之外的安全替代品。'; + + @override + String get whatArePasskeysDescriptionPoint2 => '使用生物特徵、臉部掃描或 PIN 像手機一樣解鎖帳戶。'; + + @override + String get whyPasskeys => '為什麼要使用 passkey ?'; + + @override + String get whyPasskeysDescription1 => '額外的安全層。'; + + @override + String get whyPasskeysDescription2 => '防止未經授權的存取和網絡釣魚。'; + + @override + String get howToCreatePasskey => '如何建立 passkey ?'; + + @override + String get howToCreatePasskeyDescription1 => '轉到 Deriv 的‘帳戶設定‘。'; + + @override + String get howToCreatePasskeyDescription2 => '可以為每個裝置建立一個 passkey 。'; + + @override + String get p2pHowToCreatePasskey => '如何建立 passkey ?'; + + @override + String get p2pHowToCreatePasskeyDescription1 => '在 Deriv P2P 應用程式中轉到‘個人資料‘。'; + + @override + String get p2pHowToCreatePasskeyDescription2 => '點選‘密鑰‘以建立密鑰。'; + + @override + String get whereArePasskeysSaved => ' passkey 儲存在哪裡?'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'Android: Google 密碼管理器。'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS:iCloud 鑰匙圈。'; + + @override + String get whatHappensIfEmailChanged => '如果 Deriv 帳戶電子郵件更改,會怎麼樣?'; + + @override + String get whatHappensIfEmailChangedDescription1 => '沒問題! passkey 仍然有效。'; + + @override + String get whatHappensIfEmailChangedDescription2 => '使用現有的 passkey 登入 Deriv。'; + + @override + String get tips => '提示'; + + @override + String get beforeUsingPasskeys => '使用 passkey 之前'; + + @override + String get enableScreenLock => '啟用裝置螢幕鎖定。'; + + @override + String get signInGoogleOrIcloud => '登入 Google 或 iCloud 帳戶。'; + + @override + String get enableBluetooth => '啟用藍牙。'; + + @override + String get noPasskeyFound => '找不到 passkey !'; + + @override + String get noPasskeyFoundDescription => '請建立 passkey 以使用此功能。'; + + @override + String get maybeLater => '以後再說'; + + @override + String get effortlessLoginWithPasskeys => '使用 passkey 輕鬆登入'; + + @override + String get learnMoreAboutPasskeys => '進一步了解 passkey '; + + @override + String get noNeedToRememberPassword => '無需記住密碼'; + + @override + String get useYourBiometrics => '通過生物特徵或螢幕鎖定增強安全性'; + + @override + String get syncAcrossDevices => '跨裝置同步'; + + @override + String get createPasskey => '建立 passkey '; + + @override + String get unsupportedPlatform => '不支援的平台'; + + @override + String get storedOn => '儲存在'; + + @override + String get lastUsed => '上一次使用'; + + @override + String get rename => '重命名'; + + @override + String get revoke => '撤銷'; + + @override + String get continueTradingButtonText => '繼續交易'; + + @override + String get addMorePasskeysButtonText => '新增更多 passkey '; + + @override + String get unableToSetupPasskey => '無法設定 passkey '; + + @override + String get unableToSetupPasskeyDescription => '設定 passkey 時遇到問題。程序可能已中斷,或工作階段逾時。請再試一次。'; + + @override + String get passkeysOffErrorTitle => ' passkey 服務無法使用'; + + @override + String get never => '從未'; + + @override + String get unable_to_process_your_request => '無法處理請求'; + + @override + String get unable_to_process_your_request_description => '處理請求時遇到臨時問題。請稍後再試。'; +} + +/// The translations for Chinese, as used in China (`zh_CN`). +class DerivPasskeysLocalizationsZhCn extends DerivPasskeysLocalizationsZh { + DerivPasskeysLocalizationsZhCn(): super('zh_CN'); + + @override + String get passkeyCreatedSuccessTitle => '成功!'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return '账户现已使用 passkey 保护。通过<0/> $platformName 账户设置<0/>管理 passkey 。'; + } + + @override + String get continueButtonText => '继续'; + + @override + String get unexpectedError => '发生不可预测的错误!'; + + @override + String get unexpectedErrorDescription => '请稍后重试。'; + + @override + String get ok => '确定'; + + @override + String get experienceSaferLogins => '体验更安全的登录'; + + @override + String get enhanceSecurity => '只需轻点一下即可增强安全性。'; + + @override + String get here => '这里'; + + @override + String get effortlessLogin => '使用 passkey 轻松登录'; + + @override + String get whatArePasskeys => '什么是 passkey ?'; + + @override + String get whatArePasskeysDescriptionPoint1 => '密码的安全替代方案。'; + + @override + String get whatArePasskeysDescriptionPoint2 => '使用生物识别、面部扫描或 PIN 码像解锁手机一样解锁账户-。'; + + @override + String get whyPasskeys => '为什么使用 passkey ?'; + + @override + String get whyPasskeysDescription1 => '额外的安全层。'; + + @override + String get whyPasskeysDescription2 => '防范未经授权的访问和网络钓鱼。'; + + @override + String get howToCreatePasskey => '如何创建 passkey ?'; + + @override + String get howToCreatePasskeyDescription1 => '转到 Deriv 的‘账户设置‘。'; + + @override + String get howToCreatePasskeyDescription2 => '每个设备只能创建一个 passkey 。'; + + @override + String get p2pHowToCreatePasskey => '如何创建 passkey ?'; + + @override + String get p2pHowToCreatePasskeyDescription1 => '转到 Deriv P2P 应用程序中的 ‘个人资料‘。'; + + @override + String get p2pHowToCreatePasskeyDescription2 => '点击 ‘ passkey ‘ 以创建 passkey 。'; + + @override + String get whereArePasskeysSaved => ' passkey 保存在哪里?'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'Android: Google 密码管理器。'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS:iCloud 钥匙串。'; + + @override + String get whatHappensIfEmailChanged => '如果 Deriv 账户电子邮件地址更改会怎样?'; + + @override + String get whatHappensIfEmailChangedDescription1 => '没问题! passkey 仍然有效。'; + + @override + String get whatHappensIfEmailChangedDescription2 => '使用现有 passkey 登录 Deriv。'; + + @override + String get tips => '提示'; + + @override + String get beforeUsingPasskeys => '使用 passkey 之前'; + + @override + String get enableScreenLock => '在设备启用屏幕锁定。'; + + @override + String get signInGoogleOrIcloud => '登录到 Google 或 iCloud 账户。'; + + @override + String get enableBluetooth => '启用蓝牙。'; + + @override + String get noPasskeyFound => '找不到 passkey !'; + + @override + String get noPasskeyFoundDescription => '请创建 passkey 以使用此功能。'; + + @override + String get maybeLater => '以后再说'; + + @override + String get effortlessLoginWithPasskeys => '使用 passkey 轻松登录'; + + @override + String get learnMoreAboutPasskeys => '了解有关 passkey 的更多信息'; + + @override + String get noNeedToRememberPassword => '无需记住密码'; + + @override + String get useYourBiometrics => '通过生物识别或屏幕锁增强安全性'; + + @override + String get syncAcrossDevices => '跨设备同步'; + + @override + String get createPasskey => '创建 passkey '; + + @override + String get unsupportedPlatform => '不支持的平台'; + + @override + String get storedOn => '存储在'; + + @override + String get lastUsed => '上一次使用'; + + @override + String get rename => '重命名'; + + @override + String get revoke => '撤销'; + + @override + String get continueTradingButtonText => '继续交易'; + + @override + String get addMorePasskeysButtonText => '添加更多 passkey '; + + @override + String get unableToSetupPasskey => '无法设置 passkey '; + + @override + String get unableToSetupPasskeyDescription => '设置 passkey 时遇到了问题。该过程可能已中断,或者会话超时。请再试一次。'; + + @override + String get passkeysOffErrorTitle => ' passkey 服务不可用'; + + @override + String get never => '从未'; + + @override + String get unable_to_process_your_request => '无法处理请求'; + + @override + String get unable_to_process_your_request_description => '处理请求时遇到了临时问题。请稍后再试。'; +} + +/// The translations for Chinese, as used in Taiwan (`zh_TW`). +class DerivPasskeysLocalizationsZhTw extends DerivPasskeysLocalizationsZh { + DerivPasskeysLocalizationsZhTw(): super('zh_TW'); + + @override + String get passkeyCreatedSuccessTitle => '成功!'; + + @override + String passkeyCreatedSuccessMessage(String platformName) { + return '帳戶現在已使用 passkey 保護。請透過 <0/>$platformName 帳戶設定<0/>管理 passkey 。'; + } + + @override + String get continueButtonText => '繼續'; + + @override + String get unexpectedError => '發生不可預測錯誤!'; + + @override + String get unexpectedErrorDescription => '請稍後重試。'; + + @override + String get ok => '確定'; + + @override + String get experienceSaferLogins => '體驗更安全的登入'; + + @override + String get enhanceSecurity => '只需輕按一下即可增強安全性。'; + + @override + String get here => '這裡'; + + @override + String get effortlessLogin => '使用 passkey 輕鬆登入'; + + @override + String get whatArePasskeys => '什麼是 passkey ?'; + + @override + String get whatArePasskeysDescriptionPoint1 => '密碼之外的安全替代品。'; + + @override + String get whatArePasskeysDescriptionPoint2 => '使用生物特徵、臉部掃描或 PIN 像手機一樣解鎖帳戶。'; + + @override + String get whyPasskeys => '為什麼要使用 passkey ?'; + + @override + String get whyPasskeysDescription1 => '額外的安全層。'; + + @override + String get whyPasskeysDescription2 => '防止未經授權的存取和網絡釣魚。'; + + @override + String get howToCreatePasskey => '如何建立 passkey ?'; + + @override + String get howToCreatePasskeyDescription1 => '轉到 Deriv 的‘帳戶設定‘。'; + + @override + String get howToCreatePasskeyDescription2 => '可以為每個裝置建立一個 passkey 。'; + + @override + String get p2pHowToCreatePasskey => '如何建立 passkey ?'; + + @override + String get p2pHowToCreatePasskeyDescription1 => '在 Deriv P2P 應用程式中轉到‘個人資料‘。'; + + @override + String get p2pHowToCreatePasskeyDescription2 => '點選‘密鑰‘以建立密鑰。'; + + @override + String get whereArePasskeysSaved => ' passkey 儲存在哪裡?'; + + @override + String get whereArePasskeysSavedDescriptionAndroid => 'Android: Google 密碼管理器。'; + + @override + String get whereArePasskeysSavedDescriptionIOS => 'iOS:iCloud 鑰匙圈。'; + + @override + String get whatHappensIfEmailChanged => '如果 Deriv 帳戶電子郵件更改,會怎麼樣?'; + + @override + String get whatHappensIfEmailChangedDescription1 => '沒問題! passkey 仍然有效。'; + + @override + String get whatHappensIfEmailChangedDescription2 => '使用現有的 passkey 登入 Deriv。'; + + @override + String get tips => '提示'; + + @override + String get beforeUsingPasskeys => '使用 passkey 之前'; + + @override + String get enableScreenLock => '啟用裝置螢幕鎖定。'; + + @override + String get signInGoogleOrIcloud => '登入 Google 或 iCloud 帳戶。'; + + @override + String get enableBluetooth => '啟用藍牙。'; + + @override + String get noPasskeyFound => '找不到 passkey !'; + + @override + String get noPasskeyFoundDescription => '請建立 passkey 以使用此功能。'; + + @override + String get maybeLater => '以後再說'; + + @override + String get effortlessLoginWithPasskeys => '使用 passkey 輕鬆登入'; + + @override + String get learnMoreAboutPasskeys => '進一步了解 passkey '; + + @override + String get noNeedToRememberPassword => '無需記住密碼'; + + @override + String get useYourBiometrics => '通過生物特徵或螢幕鎖定增強安全性'; + + @override + String get syncAcrossDevices => '跨裝置同步'; + + @override + String get createPasskey => '建立 passkey '; + + @override + String get unsupportedPlatform => '不支援的平台'; + + @override + String get storedOn => '儲存在'; + + @override + String get lastUsed => '上一次使用'; + + @override + String get rename => '重命名'; + + @override + String get revoke => '撤銷'; + + @override + String get continueTradingButtonText => '繼續交易'; + + @override + String get addMorePasskeysButtonText => '新增更多 passkey '; + + @override + String get unableToSetupPasskey => '無法設定 passkey '; + + @override + String get unableToSetupPasskeyDescription => '設定 passkey 時遇到問題。程序可能已中斷,或工作階段逾時。請再試一次。'; + + @override + String get passkeysOffErrorTitle => ' passkey 服務無法使用'; + + @override + String get never => '從未'; + + @override + String get unable_to_process_your_request => '無法處理請求'; + + @override + String get unable_to_process_your_request_description => '處理請求時遇到臨時問題。請稍後再試。'; +} diff --git a/packages/deriv_localizations/pubspec.yaml b/packages/deriv_localizations/pubspec.yaml new file mode 100644 index 000000000..e01e2ab46 --- /dev/null +++ b/packages/deriv_localizations/pubspec.yaml @@ -0,0 +1,26 @@ +name: deriv_localizations +description: This packages contains all localizations for the deriv packages + +publish_to: 'none' + +version: 1.7.2 + +environment: + sdk: '>=3.0.2 <4.0.0' + +dependencies: + flutter: + sdk: flutter + + flutter_localizations: + sdk: flutter + intl: ^0.19.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +flutter: + uses-material-design: true + generate: true diff --git a/packages/deriv_logger/.gitignore b/packages/deriv_logger/.gitignore new file mode 100644 index 000000000..96486fd93 --- /dev/null +++ b/packages/deriv_logger/.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/packages/deriv_logger/.metadata b/packages/deriv_logger/.metadata new file mode 100644 index 000000000..10542d27f --- /dev/null +++ b/packages/deriv_logger/.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: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + channel: stable + +project_type: package diff --git a/packages/deriv_logger/CHANGELOG.md b/packages/deriv_logger/CHANGELOG.md new file mode 100644 index 000000000..ddd57d786 --- /dev/null +++ b/packages/deriv_logger/CHANGELOG.md @@ -0,0 +1,15 @@ +## 0.0.3+1 + + - **FIX**: Updating Flutter Deriv API version to latest ([#843](https://github.com/regentmarkets/flutter-deriv-packages/issues/843)). ([db461495](https://github.com/regentmarkets/flutter-deriv-packages/commit/db46149557a9a93eb86a6691a8c7831bf709e8f1)) + +## 0.0.3 + + - **FEAT**(deriv_logger): [DERG-2590] Add ability to inspect incoming and outgoing network payloads in UI ([#685](https://github.com/regentmarkets/flutter-deriv-packages/issues/685)). ([4cd38b2a](https://github.com/regentmarkets/flutter-deriv-packages/commit/4cd38b2a34288d049333c7cf0cbeb41b671ebc7c)) + +## 0.0.2 + + - **FEAT**(deriv_logger): add ability to print prettified logs in console and UI ([#608](https://github.com/regentmarkets/flutter-deriv-packages/issues/608)). ([5a91c24b](https://github.com/regentmarkets/flutter-deriv-packages/commit/5a91c24bde607ff37940edf18f8dfac67d3fc4fa)) + +## 0.0.1 + +* TODO: Describe initial release. diff --git a/packages/deriv_logger/LICENSE b/packages/deriv_logger/LICENSE new file mode 100644 index 000000000..ba75c69f7 --- /dev/null +++ b/packages/deriv_logger/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/packages/deriv_logger/README.md b/packages/deriv_logger/README.md new file mode 100644 index 000000000..495f3c9df --- /dev/null +++ b/packages/deriv_logger/README.md @@ -0,0 +1,68 @@ + + +A debugging package that prints console and network logs in the UI. + +## Features + +- preetify terminal logs +- prints terminal logs in UI + +## Getting started + +Add the package to your pubspec.yaml file + +```yaml +deriv_logger: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_logger +``` + +## Usage + +#### Initialize the App Logger + +```dart +AppLogger.initialize(); +``` + +#### View console logs in UI (Optional) + +In order to enable this, Root widget under `MaterialApp` should be wrapped with DebugOverlay widget. + +```dart +DebugOverlay( + builder: (_) => SplashPage(), + enabled: true, + ), +``` + +#### Use its methods + +```dart +/// Log for information +AppLogger.i('Log message', title: 'Title of logs'); + +/// Log for error +AppLogger.e('Log message', title: 'Title of logs'); + +/// Log for fatal +AppLogger.f('Log message', title: 'Title of logs'); + +/// Log for success +AppLogger.s('Log message', title: 'Title of logs'); + +/// Log for warning +AppLogger.w('Log message', title: 'Title of logs'); +``` diff --git a/packages/deriv_logger/analysis_options.yaml b/packages/deriv_logger/analysis_options.yaml new file mode 100644 index 000000000..a5744c1cf --- /dev/null +++ b/packages/deriv_logger/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/deriv_logger/example/.gitignore b/packages/deriv_logger/example/.gitignore new file mode 100644 index 000000000..24476c5d1 --- /dev/null +++ b/packages/deriv_logger/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/packages/deriv_logger/example/.metadata b/packages/deriv_logger/example/.metadata new file mode 100644 index 000000000..0cffca3b7 --- /dev/null +++ b/packages/deriv_logger/example/.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: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + channel: stable + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + - platform: android + create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + - platform: ios + create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + - platform: linux + create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + - platform: macos + create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + - platform: web + create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + - platform: windows + create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + + # 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/packages/deriv_logger/example/README.md b/packages/deriv_logger/example/README.md new file mode 100644 index 000000000..2b3fce4c8 --- /dev/null +++ b/packages/deriv_logger/example/README.md @@ -0,0 +1,16 @@ +# example + +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/packages/deriv_logger/example/analysis_options.yaml b/packages/deriv_logger/example/analysis_options.yaml new file mode 100644 index 000000000..61b6c4de1 --- /dev/null +++ b/packages/deriv_logger/example/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/deriv_logger/example/android/.gitignore b/packages/deriv_logger/example/android/.gitignore new file mode 100644 index 000000000..6f568019d --- /dev/null +++ b/packages/deriv_logger/example/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/packages/deriv_auth_ui/example/android/app/build.gradle b/packages/deriv_logger/example/android/app/build.gradle similarity index 100% rename from packages/deriv_auth_ui/example/android/app/build.gradle rename to packages/deriv_logger/example/android/app/build.gradle diff --git a/packages/deriv_logger/example/android/app/src/debug/AndroidManifest.xml b/packages/deriv_logger/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..399f6981d --- /dev/null +++ b/packages/deriv_logger/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/deriv_logger/example/android/app/src/main/AndroidManifest.xml b/packages/deriv_logger/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..19b862ec8 --- /dev/null +++ b/packages/deriv_logger/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/packages/deriv_logger/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/deriv_logger/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 000000000..e793a000d --- /dev/null +++ b/packages/deriv_logger/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/packages/deriv_logger/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/deriv_logger/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/packages/deriv_logger/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/deriv_logger/example/android/app/src/main/res/drawable/launch_background.xml b/packages/deriv_logger/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/packages/deriv_logger/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/deriv_logger/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/deriv_logger/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..db77bb4b7 Binary files /dev/null and b/packages/deriv_logger/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/deriv_logger/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/deriv_logger/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..17987b79b Binary files /dev/null and b/packages/deriv_logger/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/deriv_logger/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/deriv_logger/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..09d439148 Binary files /dev/null and b/packages/deriv_logger/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/deriv_logger/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/deriv_logger/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..d5f1c8d34 Binary files /dev/null and b/packages/deriv_logger/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/deriv_logger/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/deriv_logger/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..4d6372eeb Binary files /dev/null and b/packages/deriv_logger/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/deriv_logger/example/android/app/src/main/res/values-night/styles.xml b/packages/deriv_logger/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/packages/deriv_logger/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/deriv_logger/example/android/app/src/main/res/values/styles.xml b/packages/deriv_logger/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..cb1ef8805 --- /dev/null +++ b/packages/deriv_logger/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/deriv_logger/example/android/app/src/profile/AndroidManifest.xml b/packages/deriv_logger/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..399f6981d --- /dev/null +++ b/packages/deriv_logger/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/deriv_logger/example/android/build.gradle b/packages/deriv_logger/example/android/build.gradle new file mode 100644 index 000000000..f7eb7f63c --- /dev/null +++ b/packages/deriv_logger/example/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/packages/deriv_logger/example/android/gradle.properties b/packages/deriv_logger/example/android/gradle.properties new file mode 100644 index 000000000..94adc3a3f --- /dev/null +++ b/packages/deriv_logger/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/deriv_logger/example/android/settings.gradle b/packages/deriv_logger/example/android/settings.gradle new file mode 100644 index 000000000..44e62bcf0 --- /dev/null +++ b/packages/deriv_logger/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/packages/deriv_logger/example/ios/.gitignore b/packages/deriv_logger/example/ios/.gitignore new file mode 100644 index 000000000..7a7f9873a --- /dev/null +++ b/packages/deriv_logger/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/packages/deriv_logger/example/ios/Flutter/AppFrameworkInfo.plist b/packages/deriv_logger/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..9625e105d --- /dev/null +++ b/packages/deriv_logger/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/packages/deriv_logger/example/ios/Flutter/Debug.xcconfig b/packages/deriv_logger/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..ec97fc6f3 --- /dev/null +++ b/packages/deriv_logger/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/packages/deriv_logger/example/ios/Flutter/Release.xcconfig b/packages/deriv_logger/example/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..c4855bfe2 --- /dev/null +++ b/packages/deriv_logger/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/packages/deriv_auth_ui/example/ios/Podfile b/packages/deriv_logger/example/ios/Podfile similarity index 100% rename from packages/deriv_auth_ui/example/ios/Podfile rename to packages/deriv_logger/example/ios/Podfile diff --git a/packages/deriv_logger/example/ios/Runner.xcodeproj/project.pbxproj b/packages/deriv_logger/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..529e6fec8 --- /dev/null +++ b/packages/deriv_logger/example/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 = ZD5K9ZP6F4; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + 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.example.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.example.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.example.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 = ZD5K9ZP6F4; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + 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 = ZD5K9ZP6F4; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + 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/packages/deriv_logger/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/deriv_logger/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/packages/deriv_logger/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/deriv_logger/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/deriv_logger/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/deriv_logger/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/deriv_logger/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/deriv_logger/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/deriv_logger/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/deriv_auth_ui/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/deriv_logger/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from packages/deriv_auth_ui/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/deriv_logger/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/packages/deriv_logger/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/deriv_logger/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..1d526a16e --- /dev/null +++ b/packages/deriv_logger/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/deriv_logger/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/deriv_logger/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/deriv_logger/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/deriv_logger/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/deriv_logger/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/deriv_logger/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/deriv_logger/example/ios/Runner/AppDelegate.swift b/packages/deriv_logger/example/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..70693e4a8 --- /dev/null +++ b/packages/deriv_logger/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/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/packages/deriv_logger/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/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 000000000..dc9ada472 Binary files /dev/null and b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000..7353c41ec Binary files /dev/null and b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000..797d452e4 Binary files /dev/null and b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000..6ed2d933e Binary files /dev/null and b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000..4cd7b0099 Binary files /dev/null and b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000..fe730945a Binary files /dev/null and b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000..321773cd8 Binary files /dev/null and b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000..797d452e4 Binary files /dev/null and b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000..502f463a9 Binary files /dev/null and b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000..0ec303439 Binary files /dev/null and b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000..0ec303439 Binary files /dev/null and b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000..e9f5fea27 Binary files /dev/null and b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000..84ac32ae7 Binary files /dev/null and b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000..8953cba09 Binary files /dev/null and b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000..0467bf12a Binary files /dev/null and b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/deriv_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..0bedcf2fd --- /dev/null +++ b/packages/deriv_logger/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/packages/deriv_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/deriv_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/deriv_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/deriv_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/deriv_logger/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/deriv_logger/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/packages/deriv_logger/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/deriv_logger/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/packages/deriv_logger/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/deriv_logger/example/ios/Runner/Base.lproj/Main.storyboard b/packages/deriv_logger/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/packages/deriv_logger/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/deriv_logger/example/ios/Runner/Info.plist b/packages/deriv_logger/example/ios/Runner/Info.plist new file mode 100644 index 000000000..7f553465b --- /dev/null +++ b/packages/deriv_logger/example/ios/Runner/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example + 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/packages/deriv_logger/example/ios/Runner/Runner-Bridging-Header.h b/packages/deriv_logger/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/packages/deriv_logger/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/deriv_logger/example/ios/RunnerTests/RunnerTests.swift b/packages/deriv_logger/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..86a7c3b1b --- /dev/null +++ b/packages/deriv_logger/example/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/packages/deriv_logger/example/lib/main.dart b/packages/deriv_logger/example/lib/main.dart new file mode 100644 index 000000000..cf0467181 --- /dev/null +++ b/packages/deriv_logger/example/lib/main.dart @@ -0,0 +1,173 @@ +import 'package:deriv_logger/deriv_logger.dart'; +import 'package:example/network_log_handler.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_deriv_api/api/response/active_symbols_response_result.dart'; +import 'package:flutter_deriv_api/api/response/ticks_response_result.dart'; +import 'package:flutter_deriv_api/basic_api/generated/api.dart'; +import 'package:flutter_deriv_api/services/connection/api_manager/binary_api.dart'; +import 'package:flutter_deriv_api/services/connection/api_manager/connection_information.dart'; +import 'package:flutter_deriv_api/state/connection/connection_cubit.dart' + as connection_cubit; + +void main() { + AppLogger.initialize(); + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + late connection_cubit.ConnectionCubit _connectionCubit; + + @override + void initState() { + super.initState(); + + _connectionCubit = connection_cubit.ConnectionCubit( + ConnectionInformation( + appId: '23789', + brand: 'deriv', + endpoint: 'green.derivws.com', + authEndpoint: '', + ), + ); + } + + @override + Widget build(BuildContext context) { + return BlocProvider.value( + value: _connectionCubit, + child: MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + useMaterial3: true, + ), + home: Builder(builder: (context) { + return BlocBuilder( + builder: (context, state) { + return state is connection_cubit.ConnectionConnectedState + ? DebugOverlay( + enabled: true, + callEmitter: + NetworkLogHandler(_connectionCubit.api as BinaryAPI) + .callLogEmitter, + subscriptionEmitter: + NetworkLogHandler(_connectionCubit.api as BinaryAPI) + .subscriptionLogEmitter, + builder: (_) => const MyHomePage( + title: 'Flutter Demo Home Page', + ), + ) + : const Scaffold( + body: Center( + child: Text( + 'Connecting to server...', + ), + ), + ); + }, + ); + }), + ), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Text(widget.title), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Run this app in debug mode, press any button below and check the debug button and your terminal', + textAlign: TextAlign.center, + ), + ElevatedButton( + onPressed: _makeApiCall, + child: const Text('Make api call for ActiveSymbol request'), + ), + ElevatedButton( + onPressed: _makeSubscriptionCall, + child: const Text('Make api call for Subscription call'), + ) + ], + ), + ), + floatingActionButton: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + FloatingActionButton( + onPressed: () => AppLogger.i('Information', title: 'Main page'), + tooltip: 'information', + child: const Text('Information'), + ), + FloatingActionButton( + onPressed: () => AppLogger.e('This is error!!', title: 'Main page'), + tooltip: 'error', + child: const Text('Error'), + ), + FloatingActionButton( + onPressed: () => + AppLogger.w('Showing some warning!!', title: 'Main page'), + tooltip: 'warning', + child: const Text('Warning'), + ), + FloatingActionButton( + onPressed: () => + AppLogger.f('This is a fatal message', title: ' Main Page'), + tooltip: 'fatal', + child: const Text('Fatal'), + ), + FloatingActionButton( + onPressed: () => + AppLogger.s('This is a success', title: ' Main Page'), + tooltip: 'success', + child: const Text('Success'), + ), + ], + ), + ); + } + + void _makeSubscriptionCall() async { + final value = TicksResponse.subscribeTick( + const TicksRequest(ticks: ['R_100', 'R_50']), + ); + + AppLogger.i(value, title: 'Active Symbol Payload'); + } + + void _makeApiCall() async { + ActiveSymbolsResponse.fetchActiveSymbols( + const ActiveSymbolsRequest( + activeSymbols: 'brief', + productType: 'basic', + landingCompany: 'svg', + ), + ); + } +} diff --git a/packages/deriv_logger/example/lib/network_log_handler.dart b/packages/deriv_logger/example/lib/network_log_handler.dart new file mode 100644 index 000000000..f9a442eff --- /dev/null +++ b/packages/deriv_logger/example/lib/network_log_handler.dart @@ -0,0 +1,75 @@ +import 'package:deriv_logger/services/network_service.dart'; +import 'package:flutter_deriv_api/services/connection/api_manager/binary_api.dart'; +import 'package:flutter_deriv_api/services/interfaces/call_history_provider.dart'; + +/// Class that is responsible to connect logger and network service +/// (BinaryAPI in this case). +class NetworkLogHandler { + /// Factory constructor to return the same instance + factory NetworkLogHandler(BinaryAPI api) { + _instance ??= NetworkLogHandler._internal(api); + return _instance!; + } + + /// Private constructor with parameters + NetworkLogHandler._internal(this.api); + + // Singleton instance + static NetworkLogHandler? _instance; + + /// Web Socket api instance. + final BinaryAPI api; + + /// Returns the [NetworkLogEmitter] for the call logs. + NetworkLogEmitter get callLogEmitter => _CallLogImpl(api); + + /// Returns the [NetworkLogEmitter] for the subscription logs. + NetworkLogEmitter get subscriptionLogEmitter => _SubscriptionLogImpl(api); +} + +/// Implementation of the [NetworkLogEmitter] interface. +class _CallLogImpl implements NetworkLogEmitter { + _CallLogImpl( + this.api, + ); + + /// Web Socket api instance. + final BinaryAPI api; + + @override + Stream get stream => + (api.callHistory as CallHistoryProvider).stream.map( + (NetworkPayload event) => NetworkLogPayload( + method: event.method, + body: event.body, + direction: event.direction == NetworkDirections.sent + ? LogDirection.sent + : LogDirection.received, + timeStamp: event.timeStamp, + ), + ); +} + +/// Implementation of the [NetworkLogEmitter] interface. +class _SubscriptionLogImpl implements NetworkLogEmitter { + /// + _SubscriptionLogImpl( + this.api, + ); + + /// Web Socket api instance. + final BinaryAPI api; + + @override + Stream get stream => + (api.subscriptionHistory as CallHistoryProvider).stream.map( + (NetworkPayload event) => NetworkLogPayload( + method: event.method, + body: event.body, + direction: event.direction == NetworkDirections.sent + ? LogDirection.sent + : LogDirection.received, + timeStamp: event.timeStamp, + ), + ); +} diff --git a/packages/deriv_logger/example/pubspec.yaml b/packages/deriv_logger/example/pubspec.yaml new file mode 100644 index 000000000..3a7a21e36 --- /dev/null +++ b/packages/deriv_logger/example/pubspec.yaml @@ -0,0 +1,97 @@ +name: example +description: A new Flutter project. +# 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. +version: 1.0.0+1 + +environment: + sdk: ">=3.0.2 <4.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 + flutter_bloc: ^8.1.2 + rxdart: ^0.27.7 + deriv_logger: + path: ../ + web_socket_channel: 2.4.0 + flutter_deriv_api: + git: + url: git@github.com:deriv-com/flutter-deriv-api.git + ref: v1.2.0 + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + +dev_dependencies: + 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: ^2.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/packages/deriv_logger/example/test/widget_test.dart b/packages/deriv_logger/example/test/widget_test.dart new file mode 100644 index 000000000..092d222f7 --- /dev/null +++ b/packages/deriv_logger/example/test/widget_test.dart @@ -0,0 +1,30 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:example/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/packages/deriv_logger/lib/controllers/call_log_controller.dart b/packages/deriv_logger/lib/controllers/call_log_controller.dart new file mode 100644 index 000000000..e551727e0 --- /dev/null +++ b/packages/deriv_logger/lib/controllers/call_log_controller.dart @@ -0,0 +1,90 @@ +import 'dart:convert'; + +import 'package:deriv_logger/controllers/network_logs_controller.dart'; +import 'package:deriv_logger/services/network_service.dart'; +import 'package:flutter/material.dart'; + +class CallLogController extends NetworkLogsController { + CallLogController({ + NetworkLogEmitter? callEmitter, + }) : super(emitter: callEmitter); + + final TextEditingController _searchController = TextEditingController(); + final List _callLogs = []; + + /// Search controller for searching logs. + TextEditingController get searchController => _searchController; + + @override + void incomingLog(NetworkLogPayload log) { + if (isRequest(log)) { + final CallLogVM vm = CallLogVM( + type: NetworkLogType.request, + title: log.method, + body: getReadableBody(log.body), + time: DateTime.fromMillisecondsSinceEpoch(log.timeStamp), + ); + _callLogs.add(vm); + notifyListeners(); + } else { + final CallLogVM vm = CallLogVM( + type: NetworkLogType.response, + title: log.method, + body: getReadableBody(log.body), + time: DateTime.fromMillisecondsSinceEpoch(log.timeStamp), + ); + final CallLogVM request = _callLogs.firstWhere( + (CallLogVM element) => + json.decode(element.body)['req_id'] == + json.decode(vm.body)['req_id'], + ); + // ignore: unnecessary_null_comparison + if (request != null) { + request.pair = vm; + } else { + _callLogs.add(vm); + } + notifyListeners(); + } + } + + /// List of network logs like request and response. + List get logs => _searchController.text.isEmpty + ? _callLogs.reversed.toList() + : _callLogs + .where((log) => + log.title.contains(_searchController.text.trim()) || + log.body.contains(_searchController.text.trim())) + .toList(); + + /// This will clear logs from the log list. + void clearLogs() { + _callLogs.clear(); + notifyListeners(); + } +} + +class CallLogVM extends NetworkLogVM { + CallLogVM? pair; + CallLogVM({ + required super.type, + required super.body, + required super.time, + super.title = '', + this.pair, + }) : assert(pair == null || pair.type == NetworkLogType.response); + + bool get hasResponse => pair != null; + + /// Get time in string format: HH:MM:SS:MS + String get getTimeString => pair != null + ? '${pair!.time.difference(time).inMilliseconds.toString()} ms' + : '-'; + + @override + bool get hasError => pair != null && jsonDecode(pair!.body)['error'] != null; + + @override + Color get getColor => + hasError ? Colors.redAccent[400] ?? Colors.red : super.getColor; +} diff --git a/packages/deriv_logger/lib/controllers/controllers.dart b/packages/deriv_logger/lib/controllers/controllers.dart new file mode 100644 index 000000000..06d18b95f --- /dev/null +++ b/packages/deriv_logger/lib/controllers/controllers.dart @@ -0,0 +1,4 @@ +export 'logger_controller.dart'; +export 'network_logs_controller.dart'; +export 'call_log_controller.dart'; +export 'subscription_log_controller.dart'; diff --git a/packages/deriv_logger/lib/controllers/logger_controller.dart b/packages/deriv_logger/lib/controllers/logger_controller.dart new file mode 100644 index 000000000..d556c53f2 --- /dev/null +++ b/packages/deriv_logger/lib/controllers/logger_controller.dart @@ -0,0 +1,116 @@ +import 'dart:convert'; + +import 'package:deriv_logger/core/app_logger.dart'; +import 'package:flutter/material.dart'; + +/// The controller for console logs that is responsible for managing the logs +/// into readable streams. +class ConsoleLogController extends ChangeNotifier { + /// Creates an instance of the controller. + ConsoleLogController(); + + final List _logs = []; + + /// This returns the logs in reverse order. + List get logs => _logs.reversed.toList(); + + /// This will initialize the controller. + void initialize() { + AppLogger.logStream.listen((LogDetails log) { + _logs.add( + ConsoleLogVM( + color: _getColorFromLevel(log.logLevel), + level: log.logLevel.name.toUpperCase(), + text: _getFormattedLog(log.message), + time: _getFormattedTime(log.time), + title: log.title, + stackTrace: log.stackTrace?.toString(), + ), + ); + notifyListeners(); + }); + } + + /// Clears all the logs from the log list. + void clearLogs() { + _logs.clear(); + notifyListeners(); + } + + String _getFormattedLog(dynamic object) { + try { + if (object is String) { + return object; + } else if (object is List || object is Map) { + final String spaces = ' ' * 2; + return JsonEncoder.withIndent(spaces).convert(object); + } else { + return object.toString(); + } + } on Exception catch (_) { + return object.toString(); + } + } + + String _getFormattedTime(DateTime dateTime) { + final DateTime dateInLocal = dateTime.toLocal(); + return dateInLocal.toString(); + } + + Color _getColorFromLevel(AppLogLevel level) { + switch (level) { + case AppLogLevel.debug: + return Colors.black; + case AppLogLevel.info: + return Colors.blue; + case AppLogLevel.warning: + return Colors.orange; + case AppLogLevel.error: + return Colors.red; + case AppLogLevel.fatal: + return Colors.pink; + case AppLogLevel.success: + return Colors.green; + } + } +} + +/// This is the view model for the console log. +class ConsoleLogVM { + /// This is the view model for the console log. + ConsoleLogVM({ + required this.level, + required this.text, + required this.time, + required this.color, + this.title, + this.stackTrace, + }); + + /// The color of the log based on its severity. + final Color color; + + /// The level of the log i.e + final String level; + + /// The actual message to print in the console. + final String text; + + /// THe time stamp + final String time; + + /// Title of the log. + final String? title; + + /// Stack trace of the log. + final String? stackTrace; + + @override + String toString() => jsonEncode({ + 'level': level, + 'time': time, + 'title': title, + 'text': text, + 'stackTrace': stackTrace, + }); +} diff --git a/packages/deriv_logger/lib/controllers/network_logs_controller.dart b/packages/deriv_logger/lib/controllers/network_logs_controller.dart new file mode 100644 index 000000000..a9e18743f --- /dev/null +++ b/packages/deriv_logger/lib/controllers/network_logs_controller.dart @@ -0,0 +1,74 @@ +import 'dart:convert'; + +import 'package:deriv_logger/services/network_service.dart'; +import 'package:flutter/material.dart'; + +/// The controller for network logs that is responsible for managing the logs +abstract class NetworkLogsController extends ChangeNotifier { + NetworkLogsController({NetworkLogEmitter? emitter}) { + emitter?.stream.listen(incomingLog); + } + + /// Incoming log from the stream. + void incomingLog(NetworkLogPayload log); + + /// converts raw message to readable json format. + String getReadableBody(Object message) { + const JsonEncoder encoder = JsonEncoder.withIndent(' '); + return encoder.convert(message); + } + + /// Checks if payload is request. + bool isRequest(NetworkLogPayload log) => log.direction == LogDirection.sent; + + /// It will trigger the rebuild of the widget. + void searchLogs(String value) { + notifyListeners(); + } +} + +/// View model for Network log +class NetworkLogVM { + /// Creates an instance of NetworkLogVM. + /// [type] is the type of log i.e REQUEST or RESPONSE. + /// [body] is the content of the log. + /// [time] is the time when the log was created. + /// [title] is the title of the log. + NetworkLogVM({ + required this.type, + required this.body, + required this.time, + this.title = '', + }); + + /// Type of log. + final NetworkLogType type; + + /// Content of the log. + final String body; + + /// Title of the log. + final String title; + + /// Time when the log was created. + final DateTime time; + + /// Returns true if the log is request. + bool get isRequest => type == NetworkLogType.request; + + Color get getColor => + isRequest ? Colors.deepPurple[300] ?? Colors.deepPurple : Colors.green; + + String get methodValue => jsonDecode(body)[title]?.toString() ?? ''; + + bool get hasError => jsonDecode(body)['error'] != null; +} + +/// Type of network log. +enum NetworkLogType { + /// Request log + request, + + /// Response log + response, +} diff --git a/packages/deriv_logger/lib/controllers/subscription_log_controller.dart b/packages/deriv_logger/lib/controllers/subscription_log_controller.dart new file mode 100644 index 000000000..f26094162 --- /dev/null +++ b/packages/deriv_logger/lib/controllers/subscription_log_controller.dart @@ -0,0 +1,65 @@ +import 'dart:convert'; + +import 'package:deriv_logger/controllers/network_logs_controller.dart'; +import 'package:deriv_logger/services/network_service.dart'; +import 'package:flutter/material.dart'; + +/// Controller for handling subscription logs. +class SubscriptionLogController extends NetworkLogsController { + /// Constructor for SubscriptionLogController. + SubscriptionLogController({ + NetworkLogEmitter? subscriptionEmitter, + }) : super(emitter: subscriptionEmitter); + + final List _logs = []; + + /// List of network logs like request and response. + List get logs => _logs.reversed.toList(); + + @override + void incomingLog(NetworkLogPayload log) { + if (isRequest(log)) { + _logs.add(SubscriptionLogVM( + type: NetworkLogType.request, + title: log.method, + body: getReadableBody(log.body), + time: DateTime.fromMillisecondsSinceEpoch(log.timeStamp), + )); + } else { + final body = log.body; + + if (body is Map) { + body.remove('echo_req'); + } + + final NetworkLogVM vm = NetworkLogVM( + title: log.method, + type: NetworkLogType.response, + body: getReadableBody(body), + time: DateTime.fromMillisecondsSinceEpoch(log.timeStamp), + ); + final SubscriptionLogVM existingLog = _logs.firstWhere((log) => + jsonDecode(log.body)['req_id'] == jsonDecode(vm.body)['req_id']); + existingLog.payloads = [...existingLog.payloads, vm]; + } + notifyListeners(); + } +} + +class SubscriptionLogVM extends NetworkLogVM { + List payloads; + + SubscriptionLogVM({ + required super.type, + required super.body, + required super.time, + super.title = '', + this.payloads = const [], + }); + + List get getPayloads => payloads.reversed.toList(); + + @override + Color get getColor => + hasError ? Colors.redAccent[400] ?? Colors.red : super.getColor; +} diff --git a/packages/deriv_logger/lib/core/app_logger.dart b/packages/deriv_logger/lib/core/app_logger.dart new file mode 100644 index 000000000..99c2e40b6 --- /dev/null +++ b/packages/deriv_logger/lib/core/app_logger.dart @@ -0,0 +1,171 @@ +import 'dart:async'; + +import 'package:logger/logger.dart'; + +enum AppLogLevel { + debug, + info, + warning, + error, + fatal, + success, +} + +const Map _packageLogLevelToAppLogLevel = { + Level.trace: AppLogLevel.success, + Level.info: AppLogLevel.info, + Level.warning: AppLogLevel.warning, + Level.error: AppLogLevel.error, + Level.fatal: AppLogLevel.fatal, +}; + +class AppLogger { + AppLogger._(); + static late Logger _logger; + static bool _enableDebugging = false; + + static late StreamController _logController; + + /// Initialize the logger. + static void initialize({bool enableDebugging = true}) { + _enableDebugging = enableDebugging; + + _logger = Logger( + filter: DevelopmentFilter(), + printer: PrettyPrinter( + methodCount: 0, + levelColors: { + Level.trace: const AnsiColor.fg(2), + }, + ), + ); + _logController = StreamController.broadcast(); + } + + /// Stream of logs. + static Stream get logStream => _logController.stream; + + /// This logs [error] messages with red color. + static void e( + dynamic message, { + DateTime? time, + String? title, + StackTrace? stackTrace, + }) { + _log( + level: Level.error, + message: message, + title: title, + time: time, + stackTrace: stackTrace ?? StackTrace.current, + ); + } + + /// This logs [information] message with blue color. + static void i( + dynamic message, { + DateTime? time, + String? title, + StackTrace? stackTrace, + }) { + _log( + message: message, + stackTrace: stackTrace ?? StackTrace.empty, + title: title, + time: time, + ); + } + + /// This logs [warning] message with yellow color. + static void w( + dynamic message, { + DateTime? time, + String? title, + StackTrace? stackTrace, + }) { + _log( + level: Level.warning, + message: message, + title: title, + time: time, + stackTrace: stackTrace ?? StackTrace.empty, + ); + } + + /// This logs [fatal] messages with purple color. + static void f( + dynamic message, { + DateTime? time, + String? title, + StackTrace? stackTrace, + }) { + _log( + level: Level.fatal, + message: message, + title: title, + stackTrace: stackTrace ?? StackTrace.current, + ); + } + + /// This logs [successful] events/message with green color. + static void s( + dynamic message, { + DateTime? time, + String? title, + }) { + _log( + level: Level.trace, + message: message, + title: title, + stackTrace: StackTrace.empty, + time: time, + ); + } + + void dispose() { + _logController.close(); + } + + static void _log({ + Level level = Level.info, + dynamic message, + DateTime? time, + String? title, + StackTrace? stackTrace, + }) { + if (!_enableDebugging) return; + _logger.log( + level, + message, + time: time, + error: title, + stackTrace: stackTrace, + ); + _logController.sink.add( + LogDetails( + title: title ?? '', + message: message, + time: time ?? DateTime.now(), + stackTrace: stackTrace, + logLevel: _packageLogLevelToAppLogLevel[level]!, + ), + ); + } +} + +class LogDetails { + /// Details of the logs. + LogDetails({ + required this.title, + required this.message, + required this.time, + required this.logLevel, + this.stackTrace, + }); + + final DateTime time; + final String title; + final dynamic message; + final AppLogLevel logLevel; + final StackTrace? stackTrace; +} diff --git a/packages/deriv_logger/lib/deriv_logger.dart b/packages/deriv_logger/lib/deriv_logger.dart new file mode 100644 index 000000000..3a09fc391 --- /dev/null +++ b/packages/deriv_logger/lib/deriv_logger.dart @@ -0,0 +1,6 @@ +library deriv_logger; + +export './controllers/logger_controller.dart'; +export './core/app_logger.dart'; +export './views/views.dart'; +export './widgets/debug_overlay.dart'; diff --git a/packages/deriv_logger/lib/services/network_service.dart b/packages/deriv_logger/lib/services/network_service.dart new file mode 100644 index 000000000..0702ab3df --- /dev/null +++ b/packages/deriv_logger/lib/services/network_service.dart @@ -0,0 +1,37 @@ +abstract interface class NetworkLogEmitter { + /// A Stream of network logs. + /// Logs are displayed in UI based on this stream and [NetworkPayload]. + Stream get stream; +} + +/// Network payload that is going out and coming in from the web socket. +class NetworkLogPayload { + /// Initializes [NetworkLogPayload] instance. + NetworkLogPayload({ + required this.method, + required this.body, + required this.direction, + required this.timeStamp, + }); + + /// name of the api. + final String method; + + /// content of the api. + final Object body; + + /// direction of the api i.e SENT or RECEIVED. + final LogDirection direction; + + /// time of the api. + final int timeStamp; +} + +/// Network payload that is going out and coming in from the web socket. +enum LogDirection { + /// Outgoing log to external network. + sent, + + /// Incoming log from external network to the app. + received, +} diff --git a/packages/deriv_logger/lib/views/console_log_view.dart b/packages/deriv_logger/lib/views/console_log_view.dart new file mode 100644 index 000000000..049fdc2cc --- /dev/null +++ b/packages/deriv_logger/lib/views/console_log_view.dart @@ -0,0 +1,138 @@ +import 'package:deriv_logger/controllers/logger_controller.dart'; +import 'package:deriv_logger/views/logger_theme.dart'; +import 'package:deriv_logger/widgets/controller_provider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +/// Page for Console logs. +class ConsoleLogsView extends StatelessWidget { + /// Creates a new instance of the ConsoleLogsView. + const ConsoleLogsView({ + required this.theme, + super.key, + }); + + /// theme + final DebugOverlayTheme theme; + + @override + Widget build(BuildContext context) { + var controller = ControllerProvider.of(context)!.consoleLogController; + return ListenableBuilder( + listenable: controller, + builder: (BuildContext context, _) => SafeArea( + child: Scaffold( + appBar: AppBar(title: const Text('Console logs')), + floatingActionButton: controller.logs.isEmpty + ? const SizedBox() + : FloatingActionButton( + onPressed: () => controller.clearLogs(), + child: const Icon(Icons.delete), + ), + backgroundColor: theme.backgroundColor, + body: controller.logs.isEmpty + ? Center( + child: Text( + 'No logs available!', + style: theme.bodyTextStyle, + ), + ) + : ListView.separated( + physics: const BouncingScrollPhysics(), + itemCount: controller.logs.length, + padding: const EdgeInsets.symmetric(vertical: 16), + separatorBuilder: (_, __) => const SizedBox(height: 12), + itemBuilder: (_, int index) => ConsoleLogCard( + log: controller.logs[index], + theme: theme, + ), + ), + ), + ), + ); + } +} + +/// Log item UI. +class ConsoleLogCard extends StatelessWidget { + /// Creates a UI for log messages. + const ConsoleLogCard({ + super.key, + required this.log, + required this.theme, + }); + final ConsoleLogVM log; + final DebugOverlayTheme theme; + + @override + Widget build(BuildContext context) => Container( + padding: const EdgeInsets.all(12), + margin: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + color: log.color.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeader(context), + Divider(thickness: 0.2, height: 12, color: log.color), + if (log.title != null) ...[ + _buildLogTitle(context), + const SizedBox(height: 8), + ], + _buildLogContent(context), + if (log.stackTrace != null) ...[ + const Divider(thickness: 0.5, height: 12), + _buildStackTrace(context), + ], + ], + ), + ); + + Widget _buildHeader(BuildContext context) => Row( + children: [ + Text( + log.level, + style: TextStyle(fontWeight: FontWeight.bold, color: log.color), + ), + const Spacer(), + const SizedBox(width: 16), + Text( + log.time, + style: theme.subtitleTextStyle + .copyWith(fontStyle: FontStyle.italic, color: log.color), + ), + const SizedBox(width: 16), + GestureDetector( + onTap: () => Clipboard.setData(ClipboardData(text: log.toString())), + child: Icon( + Icons.copy, + size: 18, + color: log.color, + ), + ), + ], + ); + + Widget _buildLogTitle(BuildContext context) => Text( + log.title!, + style: theme.bodyTextStyle + .copyWith(fontWeight: FontWeight.bold, color: log.color), + ); + + Widget _buildLogContent(BuildContext context) => + Text(log.text, style: theme.bodyTextStyle); + + Widget _buildStackTrace(BuildContext context) => Scrollbar( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + physics: const BouncingScrollPhysics(), + child: Text( + log.stackTrace!, + style: theme.subtitleTextStyle.copyWith(), + maxLines: 10, + ), + ), + ); +} diff --git a/packages/deriv_logger/lib/views/logger_theme.dart b/packages/deriv_logger/lib/views/logger_theme.dart new file mode 100644 index 000000000..3af0a630b --- /dev/null +++ b/packages/deriv_logger/lib/views/logger_theme.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; + +/// Theme for debug. +class DebugOverlayTheme { + /// Creates a mini theme for debug UI. + const DebugOverlayTheme({ + this.primaryColor = Colors.green, + this.backgroundColor = Colors.white, + this.cardColor = Colors.white, + this.headerTextStyle = const TextStyle( + color: Colors.black, + fontSize: 20, + fontWeight: FontWeight.w600, + fontFamily: 'Monospace', + ), + this.bodyTextStyle = const TextStyle( + color: Colors.black, + fontSize: 14, + fontWeight: FontWeight.w400, + fontFamily: 'Monospace', + ), + this.subtitleTextStyle = const TextStyle( + color: Colors.black54, + fontSize: 12, + fontWeight: FontWeight.w400, + fontFamily: 'Monospace', + ), + }); + + /// Primary color for the ui. + final Color primaryColor; + + /// Background color + final Color backgroundColor; + + ///color of the card. + final Color cardColor; + + /// Text style for the header. + final TextStyle headerTextStyle; + + /// Body text style + final TextStyle bodyTextStyle; + + /// subtitle text style. + final TextStyle subtitleTextStyle; +} + +final List defaultShadow = [ + BoxShadow( + blurRadius: 12, + offset: const Offset(0, 2), + color: Colors.black.withOpacity(0.1), + ) +]; diff --git a/packages/deriv_logger/lib/views/main_debug_view.dart b/packages/deriv_logger/lib/views/main_debug_view.dart new file mode 100644 index 000000000..ad514330a --- /dev/null +++ b/packages/deriv_logger/lib/views/main_debug_view.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; + +import 'views.dart'; + +/// Main view for Debug feature. +class MainDebugView extends StatefulWidget { + /// Creates a new instance of the DebugView. + const MainDebugView({required this.theme, super.key}); + + /// Theme for debug view. + final DebugOverlayTheme theme; + + @override + State createState() => _MainDebugViewState(); +} + +class _MainDebugViewState extends State { + late final List _views; + int _currentIndex = 0; + + @override + void initState() { + super.initState(); + + _views = [ + ConsoleLogsView(theme: widget.theme), + NetworkLogsView(theme: widget.theme), + SubscriptionLogsView(theme: widget.theme), + ]; + } + + @override + Widget build(BuildContext context) => Scaffold( + body: _views[_currentIndex], + bottomNavigationBar: BottomNavigationBar( + onTap: _changeView, + currentIndex: _currentIndex, + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.light_mode_sharp), + label: 'Console logs', + ), + BottomNavigationBarItem( + icon: Icon(Icons.light_mode_sharp), + label: 'Network Logs', + ), + BottomNavigationBarItem( + icon: Icon(Icons.lightbulb_outline_sharp), + label: 'Subscriptions Logs', + ), + ], + ), + ); + + void _changeView(int index) { + setState(() { + _currentIndex = index; + }); + } +} diff --git a/packages/deriv_logger/lib/views/network_logs_view.dart b/packages/deriv_logger/lib/views/network_logs_view.dart new file mode 100644 index 000000000..b52091c27 --- /dev/null +++ b/packages/deriv_logger/lib/views/network_logs_view.dart @@ -0,0 +1,182 @@ +import 'package:colored_json/colored_json.dart'; +import 'package:deriv_logger/controllers/controllers.dart'; +import 'package:deriv_logger/views/logger_theme.dart'; +import 'package:deriv_logger/widgets/controller_provider.dart'; +import 'package:flutter/material.dart'; + +/// The view for network logs that will be displayed in the debug overlay. +class NetworkLogsView extends StatelessWidget { + /// Creates a new instance of the NetworkLogsView. + const NetworkLogsView({ + required this.theme, + super.key, + }); + + /// theme + final DebugOverlayTheme theme; + + @override + Widget build(BuildContext context) { + final controller = ControllerProvider.of(context)!.callLogController!; + return ListenableBuilder( + listenable: controller, + builder: (BuildContext context, _) => SafeArea( + child: Scaffold( + appBar: AppBar( + title: const Text('Network logs'), + actions: [ + IconButton( + onPressed: () => controller.clearLogs(), + icon: const Icon(Icons.delete), + ), + ], + ), + backgroundColor: theme.backgroundColor, + body: Column( + children: [ + Expanded( + child: controller.logs.isEmpty + ? Center( + child: Text( + 'No logs available!', + style: theme.bodyTextStyle, + ), + ) + : ListView.separated( + physics: const BouncingScrollPhysics(), + itemCount: controller.logs.length, + padding: const EdgeInsets.symmetric(vertical: 16), + separatorBuilder: (_, __) => const SizedBox(height: 12), + itemBuilder: (_, int index) => _NetworkLogUI( + logVM: controller.logs[index], + theme: theme, + ), + ), + ), + Container( + decoration: BoxDecoration(color: Colors.deepPurple[100]), + child: TextField( + style: theme.bodyTextStyle.copyWith(color: Colors.black), + decoration: InputDecoration( + hintText: 'Search for api name...', + hintStyle: + theme.bodyTextStyle.copyWith(color: Colors.black), + border: InputBorder.none, + ), + onTapOutside: (event) => FocusScope.of(context).unfocus(), + controller: controller.searchController, + onChanged: (value) => controller.searchLogs(value), + ), + ), + ], + ), + ), + ), + ); + } +} + +class _NetworkLogUI extends StatelessWidget { + const _NetworkLogUI({ + required this.logVM, + required this.theme, + }); + + final CallLogVM logVM; + final DebugOverlayTheme theme; + + @override + Widget build(BuildContext context) => ExpansionTile( + tilePadding: const EdgeInsets.all(0), + iconColor: Colors.black, + backgroundColor: logVM.getColor.withOpacity(0.1), + collapsedBackgroundColor: logVM.getColor.withOpacity(0.1), + title: _Title( + logVM: logVM, + theme: theme, + ), + children: [ + Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.only(left: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('REQUEST:', style: theme.bodyTextStyle), + const SizedBox(height: 8.0), + ColoredJson(data: logVM.body), + if (logVM.pair != null) ...[ + Text('RESPONSE:', style: theme.bodyTextStyle), + const SizedBox(height: 8.0), + ColoredJson( + data: logVM.pair!.body, + stringColor: logVM.hasError ? Colors.red : Colors.green, + ) + ], + ], + ), + ), + ), + ], + ); +} + +class _Title extends StatelessWidget { + const _Title({ + required this.logVM, + required this.theme, + }); + + final CallLogVM logVM; + final DebugOverlayTheme theme; + + @override + Widget build(BuildContext context) => Row( + children: [ + const SizedBox(width: 4.0), + _buildLogIcon(), + const SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text(logVM.title, style: theme.bodyTextStyle), + const SizedBox(height: 4.0), + Text(logVM.methodValue, style: theme.subtitleTextStyle) + ], + ), + const Spacer(), + Text( + logVM.getTimeString, + style: const TextStyle( + color: Colors.black, + ), + overflow: TextOverflow.fade, + softWrap: true, + ), + ], + ); + + Widget _buildLogIcon() { + if (!logVM.hasResponse) { + return const Icon( + Icons.circle_outlined, + color: Colors.blueGrey, + size: 18, + ); + } else if (logVM.hasResponse && logVM.hasError) { + return const Icon( + Icons.warning_amber, + color: Colors.red, + size: 18, + ); + } else { + return const Icon( + Icons.check_rounded, + color: Colors.green, + size: 18, + ); + } + } +} diff --git a/packages/deriv_logger/lib/views/subscription_log_view.dart b/packages/deriv_logger/lib/views/subscription_log_view.dart new file mode 100644 index 000000000..1167ba0c0 --- /dev/null +++ b/packages/deriv_logger/lib/views/subscription_log_view.dart @@ -0,0 +1,265 @@ +import 'package:colored_json/colored_json.dart'; +import 'package:deriv_logger/controllers/controllers.dart'; +import 'package:deriv_logger/views/logger_theme.dart'; +import 'package:deriv_logger/widgets/controller_provider.dart'; +import 'package:flutter/material.dart'; + +/// This widget is used to display the subscription logs with request payload and +/// all the responses. +class SubscriptionLogsView extends StatelessWidget { + /// Constructor for SubscriptionLogsView. + const SubscriptionLogsView({ + required this.theme, + super.key, + }); + + /// theme + final DebugOverlayTheme theme; + @override + Widget build(BuildContext context) { + var controller = ControllerProvider.of(context)!.subscriptionLogController!; + return ListenableBuilder( + listenable: controller, + builder: (BuildContext context, _) => SafeArea( + child: Scaffold( + appBar: AppBar(title: const Text('Subscription logs')), + backgroundColor: theme.backgroundColor, + body: controller.logs.isEmpty + ? Center( + child: Text( + 'No logs available!', + style: theme.bodyTextStyle, + ), + ) + : ListView.separated( + physics: const BouncingScrollPhysics(), + itemCount: controller.logs.length, + padding: const EdgeInsets.symmetric(vertical: 16), + separatorBuilder: (_, __) => const SizedBox(height: 12), + itemBuilder: (_, int index) => InkWell( + onTap: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => SubscriptionLogDetail( + networkLogsController: controller, + logVM: controller.logs[index], + theme: theme, + ), + ), + ), + child: _NetworkLogUI( + logVM: controller.logs[index], + theme: theme, + ), + ), + ), + ), + ), + ); + } +} + +class _NetworkLogUI extends StatelessWidget { + const _NetworkLogUI({ + required this.logVM, + required this.theme, + }); + + final SubscriptionLogVM logVM; + final DebugOverlayTheme theme; + + @override + Widget build(BuildContext context) => Container( + padding: const EdgeInsets.all(6), + color: logVM.getColor.withOpacity(0.1), + child: _Title( + logVM: logVM, + theme: theme, + ), + ); +} + +class _Title extends StatelessWidget { + const _Title({ + required this.logVM, + required this.theme, + }); + + final SubscriptionLogVM logVM; + final DebugOverlayTheme theme; + + @override + Widget build(BuildContext context) => Row( + children: [ + const SizedBox(width: 4.0), + logVM.getPayloads.isNotEmpty + ? const Icon( + Icons.circle, + color: Colors.green, + size: 18, + ) + : const Icon( + Icons.circle_outlined, + color: Colors.blueGrey, + size: 18, + ), + const SizedBox(width: 8), + Text(logVM.title, style: theme.bodyTextStyle), + const Spacer(), + Text( + logVM.payloads.length.toString(), + style: const TextStyle( + color: Colors.black, + ), + overflow: TextOverflow.fade, + softWrap: true, + ), + ], + ); +} + +class SubscriptionLogDetail extends StatelessWidget { + /// Controller for networklogs. + final NetworkLogsController networkLogsController; + + final SubscriptionLogVM logVM; + + /// theme + final DebugOverlayTheme theme; + + const SubscriptionLogDetail({ + required this.networkLogsController, + required this.logVM, + required this.theme, + super.key, + }); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(logVM.title), + ), + backgroundColor: theme.backgroundColor, + body: AnimatedBuilder( + animation: networkLogsController, + builder: (BuildContext context, _) => SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _RequestUI(logVM), + Expanded( + child: ListView.separated( + physics: const BouncingScrollPhysics(), + itemCount: logVM.getPayloads.length, + padding: const EdgeInsets.symmetric(vertical: 16), + separatorBuilder: (_, __) => const SizedBox(height: 12), + itemBuilder: (_, int index) => DetailItem( + logVM: logVM.getPayloads[index], + theme: theme, + ), + ), + ) + ], + ), + ), + ), + ); + } +} + +class _RequestUI extends StatelessWidget { + const _RequestUI(this.log); + + final SubscriptionLogVM log; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(12), + margin: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + color: Colors.blueGrey.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Request', + style: TextStyle(fontWeight: FontWeight.bold, color: Colors.black), + ), + const Divider(thickness: 0.2, height: 12, color: Colors.black38), + Container( + constraints: const BoxConstraints(maxHeight: 200), + width: double.infinity, + child: SingleChildScrollView( + child: ColoredJson( + data: log.body, + ), + ), + ) + ], + ), + ); + } +} + +class DetailItem extends StatelessWidget { + const DetailItem({ + super.key, + required this.logVM, + required this.theme, + }); + + final NetworkLogVM logVM; + + /// theme + final DebugOverlayTheme theme; + + @override + Widget build(BuildContext context) { + return ExpansionTile( + tilePadding: const EdgeInsets.all(0), + iconColor: Colors.black, + backgroundColor: logVM.getColor.withOpacity(0.1), + collapsedBackgroundColor: logVM.getColor.withOpacity(0.1), + title: _DetailTitle( + logVM: logVM, + theme: theme, + ), + children: [ + Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.only(left: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ColoredJson(data: logVM.body), + ], + ), + ), + ), + ], + ); + } +} + +class _DetailTitle extends StatelessWidget { + const _DetailTitle({ + required this.logVM, + required this.theme, + }); + + final NetworkLogVM logVM; + + /// theme + final DebugOverlayTheme theme; + + @override + Widget build(BuildContext context) => Text( + logVM.methodValue, + style: theme.bodyTextStyle, + overflow: TextOverflow.ellipsis, + ); +} diff --git a/packages/deriv_logger/lib/views/views.dart b/packages/deriv_logger/lib/views/views.dart new file mode 100644 index 000000000..46be13025 --- /dev/null +++ b/packages/deriv_logger/lib/views/views.dart @@ -0,0 +1,5 @@ +export 'console_log_view.dart'; +export 'logger_theme.dart'; +export 'main_debug_view.dart'; +export 'network_logs_view.dart'; +export 'subscription_log_view.dart'; diff --git a/packages/deriv_logger/lib/widgets/controller_provider.dart b/packages/deriv_logger/lib/widgets/controller_provider.dart new file mode 100644 index 000000000..f4777c2a5 --- /dev/null +++ b/packages/deriv_logger/lib/widgets/controller_provider.dart @@ -0,0 +1,29 @@ +import 'package:deriv_logger/controllers/controllers.dart'; +import 'package:flutter/material.dart'; + +/// Inherited Widget that stores controller's instances +/// in its state. +class ControllerProvider extends InheritedWidget { + final ConsoleLogController consoleLogController; + final SubscriptionLogController? subscriptionLogController; + final CallLogController? callLogController; + + /// Inherited Widget that provides Controller's instances to the widget tree. + const ControllerProvider({ + super.key, + required this.consoleLogController, + this.subscriptionLogController, + this.callLogController, + required Widget child, + }) : super(child: child); + + @override + bool updateShouldNotify(ControllerProvider oldWidget) { + return true; + } + + /// Gets the Provider instance. + static ControllerProvider? of(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType(); + } +} diff --git a/packages/deriv_logger/lib/widgets/debug_button.dart b/packages/deriv_logger/lib/widgets/debug_button.dart new file mode 100644 index 000000000..477962326 --- /dev/null +++ b/packages/deriv_logger/lib/widgets/debug_button.dart @@ -0,0 +1,109 @@ +import 'package:deriv_logger/views/logger_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/physics.dart'; + +/// Button for debug overlay +class DebugOverlayButton extends StatefulWidget { + /// Instance of BUtton + const DebugOverlayButton({ + required this.child, + required this.onTap, + required this.theme, + super.key, + }); + final Widget child; + final Function() onTap; + final DebugOverlayTheme theme; + + @override + State createState() => _DebugOverlayButtonState(); +} + +class _DebugOverlayButtonState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + final SimulationCalculator _simulationCalculator = SimulationCalculator(); + Alignment _dragAlignment = Alignment.centerRight; + + void _runAnimation(Offset pixelsPerSecond, Size size) { + _controller + .drive(AlignmentTween(begin: _dragAlignment, end: _dragAlignment)); + _setSide(); + final SpringSimulation simulation = + _simulationCalculator.calculateSimulation(pixelsPerSecond, size); + _controller.animateWith(simulation); + } + + @override + void initState() { + super.initState(); + _controller = AnimationController(vsync: this); + _setSide(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final Size size = MediaQuery.of(context).size; + + return GestureDetector( + onPanDown: (DragDownDetails details) => _controller.stop(), + onPanUpdate: (DragUpdateDetails details) => _setPosition(details, size), + onPanEnd: (DragEndDetails details) => + _runAnimation(details.velocity.pixelsPerSecond, size), + onTap: widget.onTap, + child: Align( + alignment: _dragAlignment, + child: DecoratedBox( + decoration: BoxDecoration( + color: widget.theme.primaryColor, + shape: BoxShape.circle, + ), + child: widget.child, + ), + ), + ); + } + + void _setSide() { + final bool isLeft = _dragAlignment.x < 0.0; + if (isLeft) { + setState(() => _dragAlignment = Alignment(-0.95, _dragAlignment.y)); + } else { + setState(() => _dragAlignment = Alignment(0.95, _dragAlignment.y)); + } + } + + void _setPosition(DragUpdateDetails details, Size size) { + setState( + () { + _dragAlignment += Alignment( + details.delta.dx / (size.width / 2), + details.delta.dy / (size.height / 2), + ); + }, + ); + } +} + +/// Simulation calculator for UI. +class SimulationCalculator { + /// Calculates the position of the gesture. + SpringSimulation calculateSimulation(Offset pixelsPerSecond, Size size) { + final double unitsPerSecondX = pixelsPerSecond.dx / size.width; + final double unitsPerSecondY = pixelsPerSecond.dy / size.height; + final Offset unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY); + final double unitVelocity = unitsPerSecond.distance; + + const SpringDescription spring = + SpringDescription(mass: 30, stiffness: 1, damping: 1); + final SpringSimulation simulation = + SpringSimulation(spring, 0, 1, -unitVelocity); + return simulation; + } +} diff --git a/packages/deriv_logger/lib/widgets/debug_overlay.dart b/packages/deriv_logger/lib/widgets/debug_overlay.dart new file mode 100644 index 000000000..fa5f8cb60 --- /dev/null +++ b/packages/deriv_logger/lib/widgets/debug_overlay.dart @@ -0,0 +1,98 @@ +import 'package:deriv_logger/controllers/controllers.dart'; +import 'package:deriv_logger/deriv_logger.dart'; +import 'package:deriv_logger/services/network_service.dart'; +import 'package:deriv_logger/widgets/controller_provider.dart'; +import 'package:deriv_logger/widgets/widgets.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +/// DebugOverlay +class DebugOverlay extends StatefulWidget { + /// Overlay UI + const DebugOverlay({ + required this.builder, + super.key, + this.icon = const Icon(Icons.bug_report_outlined), + this.enabled = kDebugMode, + this.callEmitter, + this.subscriptionEmitter, + }); + + /// Widget that is displayed at DebugOverlay action header. + final Widget icon; + + /// Should return your application widget for which DebugOverlay is applied. + final WidgetBuilder builder; + + /// When this field is [true] - debug overlay is running in your app. + /// By default, this field get value from const [kDebugMode]. + final bool enabled; + + final NetworkLogEmitter? callEmitter; + final NetworkLogEmitter? subscriptionEmitter; + + @override + State createState() => _DebugOverlayState(); +} + +class _DebugOverlayState extends State { + final ConsoleLogController consoleLogsController = ConsoleLogController(); + late final CallLogController _callLogController; + late final SubscriptionLogController _subscriptionLogController; + + @override + void initState() { + super.initState(); + + if (widget.enabled) { + WidgetsBinding.instance.addPostFrameCallback( + (Duration timeStamp) => _insertOverlay(context)); + } + + _callLogController = CallLogController(callEmitter: widget.callEmitter); + _subscriptionLogController = SubscriptionLogController( + subscriptionEmitter: widget.subscriptionEmitter, + ); + + consoleLogsController.initialize(); + } + + @override + Widget build(BuildContext context) => widget.builder.call(context); + + void _insertOverlay(BuildContext context) { + const DebugOverlayTheme theme = DebugOverlayTheme(); + + return Overlay.of(context).insert( + OverlayEntry( + builder: (BuildContext context) => DebugOverlayButton( + theme: theme, + onTap: () => _onButtonTap(context, theme), + child: widget.icon, + ), + ), + ); + } + + void _onButtonTap(BuildContext context, DebugOverlayTheme theme) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: theme.backgroundColor, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(12)), + ), + builder: (BuildContext ctx) => ControllerProvider( + consoleLogController: consoleLogsController, + callLogController: _callLogController, + subscriptionLogController: _subscriptionLogController, + child: FractionallySizedBox( + heightFactor: 0.9, + child: widget.callEmitter != null + ? MainDebugView(theme: theme) + : ConsoleLogsView(theme: theme), + ), + ), + ); + } +} diff --git a/packages/deriv_logger/lib/widgets/widgets.dart b/packages/deriv_logger/lib/widgets/widgets.dart new file mode 100644 index 000000000..08368fbbc --- /dev/null +++ b/packages/deriv_logger/lib/widgets/widgets.dart @@ -0,0 +1,2 @@ +export 'debug_button.dart'; +export 'debug_overlay.dart'; diff --git a/packages/deriv_logger/pubspec.yaml b/packages/deriv_logger/pubspec.yaml new file mode 100644 index 000000000..068cbdc30 --- /dev/null +++ b/packages/deriv_logger/pubspec.yaml @@ -0,0 +1,54 @@ +name: deriv_logger +description: A debugging tool for deriv applications. +version: 0.0.3+1 + +environment: + sdk: ">=3.0.2 <4.0.0" + +dependencies: + flutter: + sdk: flutter + logger: ^2.3.0 + colored_json: ^1.1.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.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: + + # To add assets to your 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 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/packages/deriv_mobile_chart_wrapper/.gitignore b/packages/deriv_mobile_chart_wrapper/.gitignore new file mode 100644 index 000000000..96486fd93 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/.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/packages/deriv_mobile_chart_wrapper/.metadata b/packages/deriv_mobile_chart_wrapper/.metadata new file mode 100644 index 000000000..fa347fc6a --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/.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: f468f3366c26a5092eb964a230ce7892fda8f2f8 + channel: stable + +project_type: package diff --git a/packages/deriv_mobile_chart_wrapper/CHANGELOG.md b/packages/deriv_mobile_chart_wrapper/CHANGELOG.md new file mode 100644 index 000000000..df46279e4 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/CHANGELOG.md @@ -0,0 +1,259 @@ +## 0.1.8+1 + + - **FIX**: [DRGO-1367] Apply Chart Tool Globally for Indicators and Asset-Specific for Drawing Tools ([#900](https://github.com/regentmarkets/flutter-deriv-packages/issues/900)). ([8ea63c8e](https://github.com/regentmarkets/flutter-deriv-packages/commit/8ea63c8e0c3cc73f2420e7b0069b3b60b54bf9b4)) + +## 0.1.8 + + - **FEAT**(analytics): Add indicator events ([#846](https://github.com/regentmarkets/flutter-deriv-packages/issues/846)). ([1d30ddc9](https://github.com/regentmarkets/flutter-deriv-packages/commit/1d30ddc9c7510e280cc90fc7e1308b945d5758ed)) + +## 0.1.7+4 + + - **REFACTOR**(mobile_chart_wrapper): [DRGO-1222]update chart version ([#894](https://github.com/regentmarkets/flutter-deriv-packages/issues/894)). ([10f931f2](https://github.com/regentmarkets/flutter-deriv-packages/commit/10f931f25eef529cb5168b15143e3364d04f63ad)) + +## 0.1.7+3 + + - **FIX**(deriv_mobile_chart_wrapper): [DRGO-193] remove unimplemented settings button ([#890](https://github.com/regentmarkets/flutter-deriv-packages/issues/890)). ([095f96aa](https://github.com/regentmarkets/flutter-deriv-packages/commit/095f96aa10723358142f59dd71af29b98202d69f)) + +## 0.1.7+2 + + - **REFACTOR**: [DRGO-193] Wire up of line drawing tool ([#883](https://github.com/regentmarkets/flutter-deriv-packages/issues/883)). ([a498619c](https://github.com/regentmarkets/flutter-deriv-packages/commit/a498619c2ce6913b26e4dbd1f4d1857064862508)) + +## 0.1.7+1 + + - Update a dependency to the latest release. + +## 0.1.7 + + - **FEAT**(deriv_mobile_chart_wrapper): [DRGO-75] Add_bottom_sheet_landing_logic ([#841](https://github.com/regentmarkets/flutter-deriv-packages/issues/841)). ([5cf2429e](https://github.com/regentmarkets/flutter-deriv-packages/commit/5cf2429e959d9fa7093999bb4c20daf973cf8920)) + +## 0.1.6+1 + + - Update a dependency to the latest release. + +## 0.1.6 + + - **FEAT**(deriv_mobile_chart_wrapper): Update pubspec.yaml ([#877](https://github.com/regentmarkets/flutter-deriv-packages/issues/877)). ([79518161](https://github.com/regentmarkets/flutter-deriv-packages/commit/7951816199178bcdc1eb507b784e713da9f60e8d)) + +## 0.1.5 + + - **FEAT**(deriv_mobile_chart_wrapper): Add the ability to hide indicators. ([#866](https://github.com/regentmarkets/flutter-deriv-packages/issues/866)). ([2cf1b4f6](https://github.com/regentmarkets/flutter-deriv-packages/commit/2cf1b4f6e255b1058996015513ad39aca4b29b71)) + +## 0.1.4 + + - **FEAT**(deriv_mobile_chart_wrapper): DRGO-896 Add_counting_logic_to_bottom_sheet ([#835](https://github.com/regentmarkets/flutter-deriv-packages/issues/835)). ([2195701d](https://github.com/regentmarkets/flutter-deriv-packages/commit/2195701d6f76e356c7e158a240cf1951ea70047c)) + +## 0.1.3+6 + + - **FIX**(deriv_date_range_picker): iupgrade intl version ([#859](https://github.com/regentmarkets/flutter-deriv-packages/issues/859)). ([c500b57d](https://github.com/regentmarkets/flutter-deriv-packages/commit/c500b57d1558b10d7d60603d8de36af88db3dfb7)) + +## 0.1.3+5 + + - Update a dependency to the latest release. + +## 0.1.3+4 + + - Update a dependency to the latest release. + +## 0.1.3+3 + + - Update a dependency to the latest release. + +## 0.1.3+2 + + - Update a dependency to the latest release. + +## 0.1.3+1 + + - Update a dependency to the latest release. + +## 0.1.3 + + - **FEAT**(deriv_mobile_chart_wrapper): [DRGO-73] Added line drawing guide banner ([#826](https://github.com/regentmarkets/flutter-deriv-packages/issues/826)). ([b2b018ec](https://github.com/regentmarkets/flutter-deriv-packages/commit/b2b018ec78d9f2de0dcc71b2a76f1343581f4f03)) + +## 0.1.2 + + - **FEAT**(deriv_mobile_chart_wrapper): [DRGO-81] Functionality to add dots for drawing tool. ([#810](https://github.com/regentmarkets/flutter-deriv-packages/issues/810)). ([05d5c85f](https://github.com/regentmarkets/flutter-deriv-packages/commit/05d5c85f9b5c204ec54eb3828b262fe7c0293ac0)) + +## 0.1.1 + + - **FEAT**(deriv_mobile_chart_wrapper): [DRGO-74] Add drawing tools bottom sheet UI ([#815](https://github.com/regentmarkets/flutter-deriv-packages/issues/815)). ([ebd9ab92](https://github.com/regentmarkets/flutter-deriv-packages/commit/ebd9ab92707630df3bc185aab5a503399df786b4)) + +## 0.1.0+4 + + - Update a dependency to the latest release. + +## 0.1.0+3 + + - **FIX**(deriv_mobile_chart_wrapper): fix text alignment ([#816](https://github.com/regentmarkets/flutter-deriv-packages/issues/816)). ([8fd35364](https://github.com/regentmarkets/flutter-deriv-packages/commit/8fd35364760d7bc1b101f9414a2d9d340a1b5a32)) + +## 0.1.0+2 + + - Update a dependency to the latest release. + +## 0.1.0+1 + + - **FIX**(deriv_ui): fix value selector issues. ([#800](https://github.com/regentmarkets/flutter-deriv-packages/issues/800)). ([9c783d58](https://github.com/regentmarkets/flutter-deriv-packages/commit/9c783d58d52e753ede1ee40a9da4ce038ed1c991)) + +## 0.1.0 + +> Note: This release has breaking changes. + + - **BREAKING** **REFACTOR**(deriv_ui): make deriv bottom sheet height dynamic and fix color selector issues ([#797](https://github.com/regentmarkets/flutter-deriv-packages/issues/797)). ([636a7185](https://github.com/regentmarkets/flutter-deriv-packages/commit/636a7185ae3ce461647f7deb8f62c55acaad3a65)) + +## 0.0.15 + + - **FEAT**(deriv_mobile_chart_wrapper): wire up indicators ([#760](https://github.com/regentmarkets/flutter-deriv-packages/issues/760)). ([4ff1747b](https://github.com/regentmarkets/flutter-deriv-packages/commit/4ff1747b76e168710768be84d851276db5884c29)) + +## 0.0.14 + + - **FEAT**(deriv_mobile_chart_wrapper): Created a new icon button widget for drawing tools ([#792](https://github.com/regentmarkets/flutter-deriv-packages/issues/792)). ([fe1a19c1](https://github.com/regentmarkets/flutter-deriv-packages/commit/fe1a19c10ea88323911a5507ad37b25ba71aedaa)) + +## 0.0.13+3 + + - Update a dependency to the latest release. + +## 0.0.13+2 + + - Update a dependency to the latest release. + +## 0.0.13+1 + + - Update a dependency to the latest release. + +## 0.0.13 + + - **FEAT**(deriv_mobile_chart_wrapper): add drawing tools repo to chart wrapper ([#784](https://github.com/regentmarkets/flutter-deriv-packages/issues/784)). ([8df713c9](https://github.com/regentmarkets/flutter-deriv-packages/commit/8df713c958299820b94190f4f1a665efcfa8401b)) + +## 0.0.12+5 + + - Update a dependency to the latest release. + +## 0.0.12+4 + + - Update a dependency to the latest release. + +## 0.0.12+3 + + - Update a dependency to the latest release. + +## 0.0.12+2 + + - Update a dependency to the latest release. + +## 0.0.12+1 + + - Update a dependency to the latest release. + +## 0.0.12 + + - **FEAT**(deriv_mobile_chart_wrapper): bolinger and macd settings page ui ([#727](https://github.com/regentmarkets/flutter-deriv-packages/issues/727)). ([4be589cf](https://github.com/regentmarkets/flutter-deriv-packages/commit/4be589cf84c3593cf29438ca6c6e5613f4eafa49)) + +## 0.0.11 + + - **FEAT**(deriv_mobile_chart_wrapper): Implement MA setting page. ([#728](https://github.com/regentmarkets/flutter-deriv-packages/issues/728)). ([fa66abe8](https://github.com/regentmarkets/flutter-deriv-packages/commit/fa66abe81af1d016359b7d30b0fa870e8ebda61b)) + +## 0.0.10+1 + + - Update a dependency to the latest release. + +## 0.0.10 + + - **FEAT**(deriv_mobile_chart_wrapper): add description bottomsheet for settings page ([#749](https://github.com/regentmarkets/flutter-deriv-packages/issues/749)). ([6ffd5128](https://github.com/regentmarkets/flutter-deriv-packages/commit/6ffd512845276a0e240ea07ad3aa299a39d16b55)) + +## 0.0.9 + + - **FEAT**(deriv_mobile_chart_wrapper): add isResetEnabled to indicator bottomsheet ([#737](https://github.com/regentmarkets/flutter-deriv-packages/issues/737)). ([7105c272](https://github.com/regentmarkets/flutter-deriv-packages/commit/7105c272ea8f58b185a2fd899a0c71ccec78655b)) + +## 0.0.8+3 + + - Update a dependency to the latest release. + +## 0.0.8+2 + + - **FIX**(deriv_mobile_chart_wrapper): update mistyped value. ([#741](https://github.com/regentmarkets/flutter-deriv-packages/issues/741)). ([f60c782e](https://github.com/regentmarkets/flutter-deriv-packages/commit/f60c782ef8aa39b806fe676d6f17820de749777e)) + +## 0.0.8+1 + + - Update a dependency to the latest release. + +## 0.0.8 + + - **FEAT**(mobile_chart_wrapper): add RSI settings page ([#729](https://github.com/regentmarkets/flutter-deriv-packages/issues/729)). ([7f99cb1e](https://github.com/regentmarkets/flutter-deriv-packages/commit/7f99cb1e9b666b45bf63b9f2bcac6f4f38af26bf)) + +## 0.0.7+2 + + - **FIX**(deriv_mobile_chart_wraper): change from index to color in the on change callback ([#735](https://github.com/regentmarkets/flutter-deriv-packages/issues/735)). ([d1470d8c](https://github.com/regentmarkets/flutter-deriv-packages/commit/d1470d8c7ebb23f11531227a9fbb31bbcc068043)) + +## 0.0.7+1 + + - **FIX**(deriv_mobile_chart_wraper): improve color selector ([#732](https://github.com/regentmarkets/flutter-deriv-packages/issues/732)). ([45968888](https://github.com/regentmarkets/flutter-deriv-packages/commit/45968888edac04db08bafe44d6e8de87f447d6c5)) + +## 0.0.7 + + - **FEAT**(deriv_mobile_chart_wrapper): Add the base setting page. ([#730](https://github.com/regentmarkets/flutter-deriv-packages/issues/730)). ([71bd6164](https://github.com/regentmarkets/flutter-deriv-packages/commit/71bd616447f68ec7eaf26ab9ecb40882d7dde0d7)) + +## 0.0.6+2 + + - **REFACTOR**(deriv_mobile_chart_wrapper): change back the chart dependency to dev ([#722](https://github.com/regentmarkets/flutter-deriv-packages/issues/722)). ([d09d364b](https://github.com/regentmarkets/flutter-deriv-packages/commit/d09d364b61589cdfe16fbd0f7254827e9a8d5157)) + +## 0.0.6+1 + + - **REFACTOR**(deriv_mobile_chart_wrapper): Improve colour selector. ([#724](https://github.com/regentmarkets/flutter-deriv-packages/issues/724)). ([a752dd8e](https://github.com/regentmarkets/flutter-deriv-packages/commit/a752dd8ed66d70878959418679ff2b9a7e28db25)) + +## 0.0.6 + + - **FEAT**(deriv_mobile_chart_wrapper): update the chart package to get the new icons. ([#688](https://github.com/regentmarkets/flutter-deriv-packages/issues/688)). ([849afe72](https://github.com/regentmarkets/flutter-deriv-packages/commit/849afe72a4a52230de3d06ca1194ecf017f34e11)) + +## 0.0.5 + + - **FEAT**(deriv_ui): add deriv bottom sheet. feat(deriv_mobile_chart_wrapper): add indicator description ([#713](https://github.com/regentmarkets/flutter-deriv-packages/issues/713)). ([50cfbe20](https://github.com/regentmarkets/flutter-deriv-packages/commit/50cfbe2008076a073331e555228b98a11a35f2ed)) + +## 0.0.4+4 + + - **FIX**(deriv_mobile_chart_wrapper): move core widgets from chart wrapper to deriv ui ([#712](https://github.com/regentmarkets/flutter-deriv-packages/issues/712)). ([d870e2c9](https://github.com/regentmarkets/flutter-deriv-packages/commit/d870e2c93f1157fae1692f836fc9c5bee85b8e21)) + +## 0.0.4+3 + + - Update a dependency to the latest release. + +## 0.0.4+2 + + - **REFACTOR**(deriv_mobile_chart_wrapper): Ramin/integrate with new version of chart package ([#702](https://github.com/regentmarkets/flutter-deriv-packages/issues/702)). ([b4a38374](https://github.com/regentmarkets/flutter-deriv-packages/commit/b4a38374dd47e8bde9c1bdc02e93ad78a1e64bd1)) + +## 0.0.4+1 + + - Update a dependency to the latest release. + +## 0.0.4 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + - **FIX**(deriv_mobile_chart_wrapper): update readme file ([#672](https://github.com/regentmarkets/flutter-deriv-packages/issues/672)). ([13e6b3f3](https://github.com/regentmarkets/flutter-deriv-packages/commit/13e6b3f35ba863098fd9785daaa8ccc7cb23b388)) + - **FEAT**(deriv_mobile_chart_wrapper): Add Indicator bottom sheet and categories_ ([#683](https://github.com/regentmarkets/flutter-deriv-packages/issues/683)). ([a32c7ed0](https://github.com/regentmarkets/flutter-deriv-packages/commit/a32c7ed0f61e1c9965cc7d92f12e64eacb0faf52)) + +## 0.0.3 + + - **FEAT**(deriv_mobile_chart_wrapper): Add Indicator bottom sheet and categories_ ([#683](https://github.com/regentmarkets/flutter-deriv-packages/issues/683)). ([a32c7ed0](https://github.com/regentmarkets/flutter-deriv-packages/commit/a32c7ed0f61e1c9965cc7d92f12e64eacb0faf52)) + +## 0.0.2+4 + + - Update a dependency to the latest release. + +## 0.0.2+3 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +## 0.0.2+2 + + - Update a dependency to the latest release. + +## 0.0.2+1 + + - **FIX**(deriv_mobile_chart_wrapper): update readme file ([#672](https://github.com/regentmarkets/flutter-deriv-packages/issues/672)). ([13e6b3f3](https://github.com/regentmarkets/flutter-deriv-packages/commit/13e6b3f35ba863098fd9785daaa8ccc7cb23b388)) + +## 0.0.2 + + - **FEAT**(deriv_mobile_chart_wrapper): [DERG-2498] create deriv_mobile_chart_wrapper package ([#626](https://github.com/regentmarkets/flutter-deriv-packages/issues/626)). ([01aec1ed](https://github.com/regentmarkets/flutter-deriv-packages/commit/01aec1edbd8f02e21951918ec86f00fb28ef2c58)) + +## 0.0.1 + +* Setup the package. diff --git a/packages/deriv_mobile_chart_wrapper/LICENSE b/packages/deriv_mobile_chart_wrapper/LICENSE new file mode 100644 index 000000000..ba75c69f7 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/packages/deriv_mobile_chart_wrapper/README.md b/packages/deriv_mobile_chart_wrapper/README.md new file mode 100644 index 000000000..b70e54f27 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/README.md @@ -0,0 +1,22 @@ +A wrapper package around package _**deriv_chart**_ to implement any functionality specific to mobile and can be wrapped around the main chart package. +Since the main [chart package](https://github.com/regentmarkets/flutter-chart) is used in both mobile app and the web platform, for any feature that is specific to mobile, to keep the size of the main chart package small, we implement it in this package. + +## Features +Menu and interfaces to add/remove indicators and drawing tools. + +## Dependencies +- [deriv_chart](https://github.com/regentmarkets/flutter-chart) +- [deriv_theme](https://github.com/regentmarkets/flutter-deriv-packages/tree/master/packages/deriv_theme) +- [deriv_localizations](https://github.com/regentmarkets/flutter-deriv-packages/tree/master/packages/deriv_localizations) +- [deriv_ui](https://github.com/regentmarkets/flutter-deriv-packages/tree/master/packages/deriv_ui) + +## Get started +Adding the depedency: +``` +dependencies: + deriv_mobile_chart_wrapper: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_mobile_chart_wrapper + ref: [latest_version] +``` diff --git a/packages/deriv_mobile_chart_wrapper/analysis_options.yaml b/packages/deriv_mobile_chart_wrapper/analysis_options.yaml new file mode 100644 index 000000000..a5744c1cf --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/deriv_mobile_chart_wrapper/assets/icons/ic_bollinger_bands.svg b/packages/deriv_mobile_chart_wrapper/assets/icons/ic_bollinger_bands.svg new file mode 100644 index 000000000..2abf85f5d --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/assets/icons/ic_bollinger_bands.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/deriv_mobile_chart_wrapper/assets/icons/ic_close_filled.svg b/packages/deriv_mobile_chart_wrapper/assets/icons/ic_close_filled.svg new file mode 100644 index 000000000..5ec743674 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/assets/icons/ic_close_filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/deriv_mobile_chart_wrapper/assets/icons/ic_drawing_tool.svg b/packages/deriv_mobile_chart_wrapper/assets/icons/ic_drawing_tool.svg new file mode 100644 index 000000000..c8e9a4c80 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/assets/icons/ic_drawing_tool.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/packages/deriv_mobile_chart_wrapper/assets/icons/ic_drawing_tools_empty_state.svg b/packages/deriv_mobile_chart_wrapper/assets/icons/ic_drawing_tools_empty_state.svg new file mode 100644 index 000000000..f1cb865c6 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/assets/icons/ic_drawing_tools_empty_state.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/packages/deriv_mobile_chart_wrapper/assets/icons/ic_indicators_empty_state.svg b/packages/deriv_mobile_chart_wrapper/assets/icons/ic_indicators_empty_state.svg new file mode 100644 index 000000000..5ce9f7911 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/assets/icons/ic_indicators_empty_state.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/deriv_mobile_chart_wrapper/assets/icons/ic_indicators_menu.svg b/packages/deriv_mobile_chart_wrapper/assets/icons/ic_indicators_menu.svg new file mode 100644 index 000000000..58ad8a51c --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/assets/icons/ic_indicators_menu.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/deriv_mobile_chart_wrapper/assets/icons/ic_line.svg b/packages/deriv_mobile_chart_wrapper/assets/icons/ic_line.svg new file mode 100644 index 000000000..559f43486 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/assets/icons/ic_line.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/deriv_mobile_chart_wrapper/assets/icons/ic_macd.svg b/packages/deriv_mobile_chart_wrapper/assets/icons/ic_macd.svg new file mode 100644 index 000000000..b4da2a46a --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/assets/icons/ic_macd.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/deriv_mobile_chart_wrapper/assets/icons/ic_moving_average.svg b/packages/deriv_mobile_chart_wrapper/assets/icons/ic_moving_average.svg new file mode 100644 index 000000000..f15853309 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/assets/icons/ic_moving_average.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/deriv_mobile_chart_wrapper/assets/icons/ic_rsi.svg b/packages/deriv_mobile_chart_wrapper/assets/icons/ic_rsi.svg new file mode 100644 index 000000000..b602ca188 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/assets/icons/ic_rsi.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/deriv_mobile_chart_wrapper/lib/deriv_mobile_chart_wrapper.dart b/packages/deriv_mobile_chart_wrapper/lib/deriv_mobile_chart_wrapper.dart new file mode 100644 index 000000000..5e044ca3c --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/deriv_mobile_chart_wrapper.dart @@ -0,0 +1,9 @@ +library deriv_mobile_chart_wrapper; + +export 'src/mobile_chart_wrapper.dart'; +export 'src/mobile_tools_ui/mobile_tools_ui.dart'; +export 'package:deriv_chart/deriv_chart.dart'; +export 'src/mobile_tools_ui/color_selector.dart'; +export 'src/pages/rsi_setting_page.dart'; +export 'src/indicator_event_service.dart'; +export 'src/constants.dart'; diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/assets.dart b/packages/deriv_mobile_chart_wrapper/lib/src/assets.dart new file mode 100644 index 000000000..c24b71ad5 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/assets.dart @@ -0,0 +1,17 @@ +const String iconAssetsFolder = 'assets/icons/'; + +/// Indicators assets path. +const String macdIcon = '${iconAssetsFolder}ic_macd.svg'; +const String rsiIcon = '${iconAssetsFolder}ic_rsi.svg'; +const String bollingerBandsIcon = '${iconAssetsFolder}ic_bollinger_bands.svg'; +const String movingAverageIcon = '${iconAssetsFolder}ic_moving_average.svg'; +const String indicatorsMenuIcon = '${iconAssetsFolder}ic_indicators_menu.svg'; +const String emptyStateIndicatorsIcon = + '${iconAssetsFolder}ic_indicators_empty_state.svg'; + +/// Drawing tools assets path. +const String lineIcon = '${iconAssetsFolder}ic_line.svg'; +const String emptyStateDrawingToolsIcon = + '${iconAssetsFolder}ic_drawing_tools_empty_state.svg'; +const String drawingToolIcon = '${iconAssetsFolder}ic_drawing_tool.svg'; +const String closeFilledIcon = '${iconAssetsFolder}ic_close_filled.svg'; diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/constants.dart b/packages/deriv_mobile_chart_wrapper/lib/src/constants.dart new file mode 100644 index 000000000..2f79b333d --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/constants.dart @@ -0,0 +1,13 @@ +import 'package:deriv_chart/deriv_chart.dart'; + +// A set of drawing tool types that has to show drawing tool info bar. +const Set drawingToolTypesToShowInfoBar = { + LineDrawingToolConfig, + RayDrawingToolConfig, +}; + +/// Default key used for indicators if no custom key is provided. +const String defaultIndicatorsStoreKey = 'default_indicators_store'; + +/// Default key used for drawing tools if no custom key is provided. +const String defaultDrawingToolsStoreKey = 'default_drawing_tools_store'; diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/core_widgets/no_glow_scroll_behavior.dart b/packages/deriv_mobile_chart_wrapper/lib/src/core_widgets/no_glow_scroll_behavior.dart new file mode 100644 index 000000000..45d4c43e8 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/core_widgets/no_glow_scroll_behavior.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +/// A custom scroll behavior that removes the glowing effect when scrolling. +class NoGlowScrollBehavior extends ScrollBehavior { + @override + Widget buildOverscrollIndicator( + BuildContext context, Widget child, ScrollableDetails details) { + return child; // This effectively removes the glow effect + } +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/core_widgets/setting_page_action_buttons.dart b/packages/deriv_mobile_chart_wrapper/lib/src/core_widgets/setting_page_action_buttons.dart new file mode 100644 index 000000000..5fd0dc0ce --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/core_widgets/setting_page_action_buttons.dart @@ -0,0 +1,62 @@ +import 'package:deriv_mobile_chart_wrapper/src/extensions.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; + +class SettingActionButtons extends StatelessWidget { + const SettingActionButtons({ + required this.onApply, + this.onReset, + super.key, + }); + + /// The callback function to be called when the configuration is applied. + final void Function() onApply; + + /// The callback function to be called when the configuration is reset. + final void Function()? onReset; + + @override + Widget build(BuildContext context) { + return _buildActionButtons(context); + } + + Widget _buildActionButtons(BuildContext context) => Padding( + padding: const EdgeInsets.symmetric( + vertical: ThemeProvider.margin16, + ), + child: Row( + children: [ + Expanded( + child: SecondaryButton( + onPressed: onReset, + isEnabled: onReset != null, + child: Text( + context.mobileChartWrapperLocalizations.labelReset, + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: onReset != null + ? context.theme.colors.prominent + : context.theme.colors.active, + ), + ), + ), + ), + const SizedBox( + width: ThemeProvider.margin08, + ), + Expanded( + child: PrimaryButton( + onPressed: onApply, + child: Text( + context.mobileChartWrapperLocalizations.labelApply, + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent, + ), + )), + ) + ], + ), + ); +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/enums.dart b/packages/deriv_mobile_chart_wrapper/lib/src/enums.dart new file mode 100644 index 000000000..d79612617 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/enums.dart @@ -0,0 +1,6 @@ +/// Specifies which category the indicator belongs to. +enum IndicatorCategory { + momentum, + volatility, + movingAverages, +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/extensions.dart b/packages/deriv_mobile_chart_wrapper/lib/src/extensions.dart new file mode 100644 index 000000000..35b539e12 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/extensions.dart @@ -0,0 +1,12 @@ +import 'package:deriv_localizations/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; + +/// Extension for [BuildContext]. +extension ContextExtension on BuildContext { + /// Get DerivMobileChartWrapperLocalizations. + DerivMobileChartWrapperLocalizations get mobileChartWrapperLocalizations => + DerivMobileChartWrapperLocalizations.of(this); + + ThemeProvider get themeProvider => DerivThemeProvider.getTheme(this); +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/helpers.dart b/packages/deriv_mobile_chart_wrapper/lib/src/helpers.dart new file mode 100644 index 000000000..09d053905 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/helpers.dart @@ -0,0 +1,210 @@ +import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_mobile_chart_wrapper/src/assets.dart'; +import 'package:deriv_mobile_chart_wrapper/src/enums.dart'; +import 'package:deriv_mobile_chart_wrapper/src/extensions.dart'; +import 'package:deriv_mobile_chart_wrapper/src/indicator_event_service.dart'; +import 'package:deriv_mobile_chart_wrapper/src/models/drawing_tool_item_model.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/utils/popup_dialogs_helper.dart'; +import 'package:flutter/material.dart'; + +/// Returns abbreviation name of the indicator for the given [config]. +String getIndicatorAbbreviation(IndicatorConfig config, BuildContext context) { + // TODO(Ramin): use config.shortTitle after updating to the new version of + // chart package. + switch (config.runtimeType) { + case MACDIndicatorConfig: + return context.mobileChartWrapperLocalizations.labelMACD; + case RSIIndicatorConfig: + return context.mobileChartWrapperLocalizations.labelRSI; + case BollingerBandsIndicatorConfig: + return context.mobileChartWrapperLocalizations.labelBB; + case MAIndicatorConfig: + return context.mobileChartWrapperLocalizations.labelMA; + default: + return ''; + } +} + +String getIndicatorAbbreviationWithCount( + IndicatorConfig config, BuildContext context) => + '${getIndicatorAbbreviation(config, context)} ${config.number > 0 ? config.number : ''}'; + +/// Returns the path to the icon of the indicator for the given [config]. +String getIndicatorIconPath(IndicatorConfig config) { + switch (config.runtimeType) { + case MACDIndicatorConfig: + return macdIcon; + case RSIIndicatorConfig: + return rsiIcon; + case BollingerBandsIndicatorConfig: + return bollingerBandsIcon; + case MAIndicatorConfig: + return movingAverageIcon; + default: + return ''; + } +} + +/// Returns the path to the icon of the drawing tool +/// for the given [drawingToolType]. +String getDrawingToolIconPath(Type drawingToolType) { + switch (drawingToolType) { + case LineDrawingToolConfig: + return lineIcon; + case RayDrawingToolConfig: + return rsiIcon; + default: + return ''; + } +} + +/// Returns the list of drawing tools available for the chart. +List getDrawingToolsList(BuildContext context) { + List drawingTools = [ + DrawingToolItemModel( + title: context.mobileChartWrapperLocalizations.labelLine, + icon: lineIcon, + config: const LineDrawingToolConfig( + lineStyle: LineStyle(thickness: 0.9, color: BrandColors.coral), + ), + ), + // Add more drawing tools here. + ]; + + return drawingTools; +} + +String getDrawingToolTitleWithCount( + BuildContext context, + DrawingToolConfig config, +) => + '${getDrawingToolTitle( + context, + config, + )} ${config.number > 0 ? config.number : ''}'; + +String getDrawingToolTitle( + BuildContext context, + DrawingToolConfig config, +) { + switch (config.runtimeType) { + case LineDrawingToolConfig: + return context.mobileChartWrapperLocalizations.labelLine; + case RayDrawingToolConfig: + return context.mobileChartWrapperLocalizations.labelRay; + default: + return ''; + } +} + +Map getSourcesOptions(BuildContext context) => { + 'close': context.mobileChartWrapperLocalizations.labelClose, + 'open': context.mobileChartWrapperLocalizations.labelOpen, + 'high': context.mobileChartWrapperLocalizations.labelHigh, + 'low': context.mobileChartWrapperLocalizations.labelLow, + 'Hl/2': context.mobileChartWrapperLocalizations.labelHl2, + 'HlC/3': context.mobileChartWrapperLocalizations.labelHlc3, + 'HlCC/4': context.mobileChartWrapperLocalizations.labelHlcc4, + 'OHlC/4': context.mobileChartWrapperLocalizations.labelOhlc4, + }; + +Map getTypesOptions(BuildContext context) => { + MovingAverageType.simple: + context.mobileChartWrapperLocalizations.labelSimple, + MovingAverageType.exponential: + context.mobileChartWrapperLocalizations.labelExponential, + MovingAverageType.weighted: + context.mobileChartWrapperLocalizations.labelWeighted, + MovingAverageType.hull: context.mobileChartWrapperLocalizations.labelHull, + MovingAverageType.zeroLag: + context.mobileChartWrapperLocalizations.labelZeroLag, + MovingAverageType.timeSeries: + context.mobileChartWrapperLocalizations.labelTimeSeries, + MovingAverageType.wellesWilder: + context.mobileChartWrapperLocalizations.labelWellesWilder, + MovingAverageType.variable: + context.mobileChartWrapperLocalizations.labelVariable, + MovingAverageType.triangular: + context.mobileChartWrapperLocalizations.labelTriangular, + MovingAverageType.doubleExponential: + context.mobileChartWrapperLocalizations.label2Exponential, + MovingAverageType.tripleExponential: + context.mobileChartWrapperLocalizations.label3Exponential, + }; + +Map getMAOptions(BuildContext context) => { + MovingAverageType.simple: + context.mobileChartWrapperLocalizations.labelSimple, + MovingAverageType.exponential: + context.mobileChartWrapperLocalizations.labelExponential, + MovingAverageType.weighted: + context.mobileChartWrapperLocalizations.labelWeighted, + MovingAverageType.hull: context.mobileChartWrapperLocalizations.labelHull, + MovingAverageType.zeroLag: + context.mobileChartWrapperLocalizations.labelZeroLag, + MovingAverageType.timeSeries: + context.mobileChartWrapperLocalizations.labelTimeSeries, + MovingAverageType.wellesWilder: + context.mobileChartWrapperLocalizations.labelWellesWilder, + MovingAverageType.variable: + context.mobileChartWrapperLocalizations.labelVariable, + MovingAverageType.triangular: + context.mobileChartWrapperLocalizations.labelTriangular, + MovingAverageType.doubleExponential: + context.mobileChartWrapperLocalizations.label2Exponential, + MovingAverageType.tripleExponential: + context.mobileChartWrapperLocalizations.label3Exponential, + }; + +Future showResetIndicatorDialog( + BuildContext context, { + required IndicatorConfig config, + required Function() onResetPressed, + ChartEventTracker? eventTracker, +}) { + return showAlertDialog( + context: context, + title: context.mobileChartWrapperLocalizations.labelResetIndicator( + getIndicatorAbbreviationWithCount(config, context), + ), + content: Text( + context.mobileChartWrapperLocalizations.infoResetIndicators( + getIndicatorAbbreviationWithCount(config, context), + ), + style: TextStyles.subheading, + ), + positiveActionLabel: context.mobileChartWrapperLocalizations.labelReset, + negativeButtonLabel: context.mobileChartWrapperLocalizations.labelCancel, + showLoadingIndicator: false, + onPositiveActionPressed: () { + onResetPressed.call(); + Navigator.pop(context); + eventTracker?.logResetIndicatorSettings( + config.title, + getIndicatorCategoryTitle(config.title), + ); + }, + onNegativeActionPressed: () { + Navigator.pop(context); + }); +} + +String getIndicatorCategoryTitle(String indicatorTitle) { + switch (indicatorTitle) { + case 'MACD': + return IndicatorCategory.momentum.name; + + case 'Relative Strength Index (RSI)': + return IndicatorCategory.momentum.name; + + case 'Bollinger Bands': + return IndicatorCategory.volatility.name; + + case 'Moving Average': + return IndicatorCategory.movingAverages.name; + + default: + return ''; + } +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/indicator_event_service.dart b/packages/deriv_mobile_chart_wrapper/lib/src/indicator_event_service.dart new file mode 100644 index 000000000..95200b691 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/indicator_event_service.dart @@ -0,0 +1,65 @@ +/// Service for logging indicator events. +abstract class ChartEventTracker { + /// Log when user opens indicator types bottom sheet. + void logOpenIndicatorTypesBottomSheet(); + + /// Log when user closes indicator types bottom sheet. + void logCloseIndicatorTypesBottomSheet(); + + /// Log when user cleans all active indicators. + void logCleanAllActiveIndicator(); + + /// Log when user clicks on indicator type. + void logAddIndicatorByClickIndicatorType( + String indicatorTitle, + String categoryName, + ); + + /// Log when user clicks on indicator info Add button. + void logAddIndicatorByClickAddOnIndicatorInfo( + String indicatorTitle, + String categoryName, + ); + + /// Log when user deletes active indicator. + void logDeleteActiveIndicator( + String indicatorTitle, + String categoryName, + ); + + /// Log when user deletes active indicator from indicator's settings. + void logDeleteActiveIndicatorFromSettings( + String indicatorTitle, + String categoryName, + ); + + /// Log when user edits indicator settings. + void logEditIndicatorSettings( + String indicatorTitle, + String categoryName, + ); + + /// Log when user resets indicator settings. + void logResetIndicatorSettings( + String indicatorTypeName, + String categoryName, + ); + + /// Log when user opens indicator info from indicators list. + void logOpenIndicatorInfoFromIndicatorsList( + String indicatorTypeName, + String categoryName, + ); + + /// Log when user opens indicator info from indicator settings. + void logOpenIndicatorInfoFromIndicatorSettings( + String indicatorTypeName, + String categoryName, + ); + + /// Log when user closes indicator info. + void logCloseIndicatorInfo( + String indicatorTypeName, + String categoryName, + ); +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/mobile_chart_wrapper.dart b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_chart_wrapper.dart new file mode 100644 index 000000000..46be41b63 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_chart_wrapper.dart @@ -0,0 +1,399 @@ +import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_mobile_chart_wrapper/src/constants.dart'; +import 'package:deriv_mobile_chart_wrapper/src/extensions.dart'; +import 'package:deriv_mobile_chart_wrapper/src/indicator_event_service.dart'; +import 'package:deriv_mobile_chart_wrapper/src/mobile_tools_ui/drawing_tools/drawing_tools_selector.dart'; +import 'package:deriv_mobile_chart_wrapper/src/models/config_item_model.dart'; +import 'package:deriv_mobile_chart_wrapper/src/models/indicator_tab_label.dart'; +import 'package:deriv_ui/components/components.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'mobile_tools_ui/mobile_tools_bottom_sheet_content.dart'; +import 'mobile_tools_ui/tools_controller.dart'; + +/// The mobile version wrapper around the [Chart] which handles adding/removing +/// indicators and drawing tools to the chart. +class MobileChartWrapper extends StatefulWidget { + /// Initializes [MobileChartWrapper]. + const MobileChartWrapper({ + required this.mainSeries, + required this.granularity, + this.indicatorsStoreKey = defaultIndicatorsStoreKey, + this.drawingToolsStoreKey = defaultDrawingToolsStoreKey, + this.toolsController, + this.markerSeries, + this.controller, + this.onCrosshairAppeared, + this.onCrosshairDisappeared, + this.onCrosshairHover, + this.onVisibleAreaChanged, + this.onQuoteAreaChanged, + this.theme, + this.isLive = false, + this.dataFitEnabled = false, + this.showCrosshair = true, + this.annotations, + this.opacity = 1.0, + this.pipSize = 4, + this.chartAxisConfig = const ChartAxisConfig(), + this.maxCurrentTickOffset, + this.msPerPx, + this.minIntervalWidth, + this.maxIntervalWidth, + this.dataFitPadding, + this.currentTickAnimationDuration, + this.quoteBoundsAnimationDuration, + this.showCurrentTickBlinkAnimation, + this.verticalPaddingFraction, + this.bottomChartTitleMargin, + this.showDataFitButton, + this.showScrollToLastTickButton, + this.loadingAnimationColor, + this.eventTracker, + this.showIndicators = true, + Key? key, + }) : super(key: key); + + /// Chart's main data series + final DataSeries mainSeries; + + /// Open position marker series. + final MarkerSeries? markerSeries; + + /// The key used to store the selected indicators in shared preferences. + /// If no key is provided, the default key `'default'` is used. + /// + /// By using the same key when selecting indicators, the previously saved + /// state will be restored, ensuring the tools are in the same configuration + /// as before. + final String indicatorsStoreKey; + + /// The key used to store the selected drawing tools in shared preferences. + /// If no key is provided, the default key `'default'` is used. + /// + /// By using the same key when applying drawing tools, the previously applied + /// tools state will be restored, ensuring the tools are in the same + /// configuration as before. + final String drawingToolsStoreKey; + + /// Chart's controller + final ChartController? controller; + + /// Chart's tools controller. + final ToolsController? toolsController; + + /// Number of digits after decimal point in price. + final int pipSize; + + /// For candles: Duration of one candle in ms. + /// For ticks: Average ms difference between two consecutive ticks. + final int granularity; + + /// Called when crosshair details appear after long press. + final VoidCallback? onCrosshairAppeared; + + /// Called when the crosshair is dismissed. + final VoidCallback? onCrosshairDisappeared; + + /// Called when the crosshair cursor is hovered/moved. + final OnCrosshairHoverCallback? onCrosshairHover; + + /// Called when chart is scrolled or zoomed. + final VisibleAreaChangedCallback? onVisibleAreaChanged; + + /// Callback provided by library user. + final VisibleQuoteAreaChangedCallback? onQuoteAreaChanged; + + /// Chart's theme. + final ChartTheme? theme; + + /// Chart's annotations + final List>? annotations; + + /// Configurations for chart's axes. + final ChartAxisConfig chartAxisConfig; + + /// Whether the chart should be showing live data or not. + /// In case of being true the chart will keep auto-scrolling when its visible + /// area is on the newest ticks/candles. + final bool isLive; + + /// Starts in data fit mode and adds a data-fit button. + final bool dataFitEnabled; + + /// Chart's opacity, Will be applied on the [mainSeries]. + final double opacity; + + /// Whether the crosshair should be shown or not. + final bool showCrosshair; + + /// Max distance between rightBoundEpoch and nowEpoch in pixels. + final double? maxCurrentTickOffset; + + /// Specifies the zoom level of the chart. + final double? msPerPx; + + /// Specifies the minimum interval width + /// that is used for calculating the maximum msPerPx. + final double? minIntervalWidth; + + /// Specifies the maximum interval width + /// that is used for calculating the maximum msPerPx. + final double? maxIntervalWidth; + + /// Padding around data used in data-fit mode. + final EdgeInsets? dataFitPadding; + + /// Duration of the current tick animated transition. + final Duration? currentTickAnimationDuration; + + /// Duration of quote bounds animated transition. + final Duration? quoteBoundsAnimationDuration; + + /// Whether to show current tick blink animation or not. + final bool? showCurrentTickBlinkAnimation; + + /// Fraction of the chart's height taken by top or bottom padding. + /// Quote scaling (drag on quote area) is controlled by this variable. + final double? verticalPaddingFraction; + + /// Specifies the margin to prevent overlap. + final EdgeInsets? bottomChartTitleMargin; + + /// Whether the data fit button is shown or not. + final bool? showDataFitButton; + + /// Whether to show the scroll to last tick button or not. + final bool? showScrollToLastTickButton; + + /// The color of the loading animation. + final Color? loadingAnimationColor; + + /// Indicator event service. + final ChartEventTracker? eventTracker; + + /// Show indicators flag. + final bool showIndicators; + + @override + MobileChartWrapperState createState() => MobileChartWrapperState(); +} + +/// The state of the [MobileChartWrapper]. +class MobileChartWrapperState extends State { + AddOnsRepository? _indicatorsRepo; + AddOnsRepository? _drawingToolsRepo; + final DrawingTools _drawingTools = DrawingTools(); + + @override + void initState() { + super.initState(); + + _initRepos(); + } + + @override + void didUpdateWidget(covariant MobileChartWrapper oldWidget) { + super.didUpdateWidget(oldWidget); + + // Reload indicators if the indicators key has changed + if (widget.indicatorsStoreKey != oldWidget.indicatorsStoreKey) { + _loadSavedIndicators(); + } + + // Reload drawing tools if the drawing tools key has changed + if (widget.drawingToolsStoreKey != oldWidget.drawingToolsStoreKey) { + _loadSavedDrawingTools(); + } + } + + void _setupController() { + widget.toolsController?.onShowIndicatorsToolsMenu = () { + if (_indicatorsRepo != null) { + _showIndicatorsSheet(_indicatorsRepo!); + } + }; + widget.toolsController?.onShowDrawingToolsMenu = () { + if (_drawingToolsRepo != null) { + _showDrawingToolsSheet(_drawingToolsRepo!); + } + }; + + _updateConfigs(); + _updateDrawingTools(); + + _indicatorsRepo?.addListener(_updateConfigs); + _drawingToolsRepo?.addListener(_updateConfigs); + } + + void _initRepos() async { + if (widget.toolsController?.indicatorsEnabled ?? false) { + _indicatorsRepo = AddOnsRepository( + createAddOn: (Map map) => + IndicatorConfig.fromJson(map), + onEditCallback: (_) => _showIndicatorsSheet(_indicatorsRepo!), + sharedPrefKey: widget.indicatorsStoreKey, + ); + + await _loadSavedIndicators(); + } + + if (widget.toolsController?.drawingToolsEnabled ?? false) { + _drawingToolsRepo = AddOnsRepository( + createAddOn: (Map map) => + DrawingToolConfig.fromJson(map), + onEditCallback: (_) => _showDrawingToolsSheet(_drawingToolsRepo!), + sharedPrefKey: widget.drawingToolsStoreKey, + ); + + await _loadSavedDrawingTools(); + } + + _setupController(); + } + + Future _loadSavedIndicators() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + if (_indicatorsRepo != null) { + try { + _indicatorsRepo!.loadFromPrefs(prefs, widget.indicatorsStoreKey); + } on Exception { + _showLoadErrorDialog(message: 'Failed loading indicators'); + } + } + } + + Future _loadSavedDrawingTools() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + if (_drawingToolsRepo != null) { + try { + _drawingToolsRepo!.loadFromPrefs(prefs, widget.drawingToolsStoreKey); + } on Exception { + _showLoadErrorDialog(message: 'Failed loading drawing tools'); + } + } + } + + void _showLoadErrorDialog({required String message}) { + // Show error dialog + showDialog( + context: context, + builder: (BuildContext context) => AnimatedPopupDialog( + child: Center( + child: Text(message), + ), + ), + ); + } + + void _showIndicatorsSheet(AddOnsRepository indicatorsRepo) { + // Show indicators menu as modal bottom sheet so it's dismissible by tapping + // outside. + widget.eventTracker?.logOpenIndicatorTypesBottomSheet(); + showModalBottomSheet( + context: context, + builder: (_) => + ChangeNotifierProvider>.value( + value: indicatorsRepo, + child: SafeArea( + child: DerivBottomSheet( + title: context.mobileChartWrapperLocalizations.labelIndicators, + child: MobileToolsBottomSheetContent( + selectedTab: indicatorsRepo.items.isEmpty + ? IndicatorTabLabel.all + : IndicatorTabLabel.active, + eventTracker: widget.eventTracker, + ), + ), + ), + ), + ); + } + + void _showDrawingToolsSheet( + AddOnsRepository drawingToolsRepo) { + setState(() { + _drawingTools + ..init() + ..drawingToolsRepo = drawingToolsRepo; + }); + + showModalBottomSheet( + context: context, + builder: (_) => + ChangeNotifierProvider>.value( + value: drawingToolsRepo, + child: SafeArea( + child: DerivBottomSheet( + title: context.mobileChartWrapperLocalizations.labelDrawingTools, + child: DrawingToolsSelector( + onDrawingToolSelected: (DrawingToolConfig selectedDrawingTool) { + _drawingTools.onDrawingToolSelection(selectedDrawingTool); + _updateDrawingTools(); + Navigator.of(context).pop(); + }, + ), + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) => + // TODO(Ramin): Check if we can consider using Chart widget directly. + DerivChart( + indicatorsRepo: widget.showIndicators && _indicatorsRepo != null + ? _indicatorsRepo + : AddOnsRepository( + createAddOn: (Map map) => + IndicatorConfig.fromJson(map), + sharedPrefKey: widget.indicatorsStoreKey, + ), + drawingToolsRepo: _drawingToolsRepo ?? + AddOnsRepository( + createAddOn: (Map map) => + DrawingToolConfig.fromJson(map), + sharedPrefKey: widget.drawingToolsStoreKey, + ), + drawingTools: _drawingTools, + controller: widget.controller, + mainSeries: widget.mainSeries, + markerSeries: widget.markerSeries, + pipSize: widget.pipSize, + granularity: widget.granularity, + onVisibleAreaChanged: widget.onVisibleAreaChanged, + isLive: widget.isLive, + dataFitEnabled: widget.dataFitEnabled, + opacity: widget.opacity, + chartAxisConfig: widget.chartAxisConfig, + annotations: widget.annotations, + // TODO: The 'activeSymbol' property will be deprecated in a future + // release. It is currently irrelevant in the current chart package + // implementation, as the AddOnsRepository is initialized externally + // in the implementation. + activeSymbol: 'default', + ); + + @override + void dispose() { + _indicatorsRepo?.removeListener(_updateConfigs); + _drawingToolsRepo?.removeListener(_updateConfigs); + super.dispose(); + } + + /// Update the configs in the tools controller. + void _updateConfigs() { + widget.toolsController?.updateConfigs(ConfigItemModel( + indicatorConfigs: _indicatorsRepo?.items ?? [], + drawingToolConfigs: _drawingToolsRepo?.items ?? [], + )); + } + + /// Update the drawing tools data in the tool controller. + void _updateDrawingTools() { + widget.toolsController?.updateDrawingToolsData(_drawingTools); + } +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/active_indicator_list_item.dart b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/active_indicator_list_item.dart new file mode 100644 index 000000000..25a09ea25 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/active_indicator_list_item.dart @@ -0,0 +1,77 @@ +import 'package:deriv_mobile_chart_wrapper/src/extensions.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +/// List item for the active indicator list. +class ActiveIndicatorListItem extends StatelessWidget { + /// Initializes the active indicator list item. + const ActiveIndicatorListItem({ + required this.iconAssetPath, + required this.title, + required this.subtitle, + required this.onTapSetting, + required this.onTapDelete, + super.key, + }); + + /// The path to the SVG icon asset. + final String iconAssetPath; + + /// The title of the indicator. + final String title; + + /// The subtitle of the indicator to show its properties. + final String subtitle; + + /// The callback which will be called when the + /// indicator setting button is tapped. + final VoidCallback onTapSetting; + + /// The callback which will be called when the + /// indicator delete button is tapped. + final VoidCallback onTapDelete; + + @override + Widget build(BuildContext context) { + return Card( + margin: EdgeInsets.zero, + color: context.themeProvider.colors.secondary, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(ThemeProvider.borderRadius08), + ), + ), + child: ListTile( + title: Row( + children: [ + SvgPicture.asset( + iconAssetPath, + width: ThemeProvider.margin24, + height: ThemeProvider.margin24, + package: 'deriv_mobile_chart_wrapper', + ), + const SizedBox(width: ThemeProvider.margin08), + Text(title), + ], + ), + subtitle: Text(subtitle), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: onTapSetting, + color: context.themeProvider.colors.prominent, + icon: const Icon(Icons.settings_outlined), + ), + IconButton( + onPressed: onTapDelete, + color: context.themeProvider.colors.prominent, + icon: const Icon(Icons.delete_outline), + ), + ], + ), + ), + ); + } +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/chart_setting_button_with_background.dart b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/chart_setting_button_with_background.dart new file mode 100644 index 000000000..8252388d2 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/chart_setting_button_with_background.dart @@ -0,0 +1,40 @@ +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; + +/// A button with a background. +class ChartSettingButtonWithBackground extends StatelessWidget { + /// Creates a button with a background. + const ChartSettingButtonWithBackground({ + required this.child, + required this.onTap, + super.key, + }); + + /// The button content. + final Widget child; + + /// The callback function to be called when the button is tapped. + final VoidCallback onTap; + + @override + Widget build(BuildContext context) => Container( + width: ThemeProvider.margin32, + height: ThemeProvider.margin32, + decoration: BoxDecoration( + color: context.theme.colors.secondary, + borderRadius: BorderRadius.circular(ThemeProvider.borderRadius04), + ), + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(ThemeProvider.borderRadius04), + child: InkWell( + borderRadius: BorderRadius.circular(ThemeProvider.borderRadius04), + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(ThemeProvider.margin08), + child: child, + ), + ), + ), + ); +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/color_selector.dart b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/color_selector.dart new file mode 100644 index 000000000..0de2819b5 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/color_selector.dart @@ -0,0 +1,124 @@ +import 'package:deriv_mobile_chart_wrapper/src/extensions.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; + +import 'colours_grid.dart'; + +/// A widget that allows the user to select a color from a list of colors. +class ColorSelector extends StatefulWidget { + ColorSelector({ + this.colors = const [ + Color(0xFFFFFFFF), // White + Color(0xFFF39230), // Orange + Color(0xFFEF6B53), // Deep Orange + Color(0xFFD73737), // Red + Color(0xFF03BFF0), // Light Blue + Color(0xFF3271B4), // Blue + Color(0xFF2FBCB5), // Teal + Color(0xFF8EC648), // Light Green + Color(0xFF48A25C), // Green + Color(0xFFFFF224), // Yellow + Color(0xFFEE6EA9), // Pink + Color(0xFF853289), // Purple + ], + required this.title, + this.selectedColor, + required this.onColorChanged, + }) : super(key: ValueKey(selectedColor)); + + final String title; + + final Color? selectedColor; + + final List colors; + + final void Function(Color selectedColor) onColorChanged; + + @override + State createState() => _ColorSelectorState(); +} + +class _ColorSelectorState extends State { + Color? _selectedColor; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + setState(() { + _selectedColor = widget.selectedColor; + }); + } + + @override + Widget build(BuildContext context) => GestureDetector( + onTap: () { + showModalBottomSheet( + context: context, + builder: (_) => StatefulBuilder(builder: (context, state) { + return SafeArea( + child: DerivBottomSheet( + title: widget.title, + hasActionButton: true, + actionButtonLabel: + context.mobileChartWrapperLocalizations.labelOK, + onActionButtonPressed: _selectedColor == null + ? null + : () { + widget.onColorChanged(_selectedColor!); + Navigator.of(context).pop(); + }, + child: ColoredBox( + color: context.theme.colors.primary, + child: ColoursGrid( + onColorSelected: (int index) { + state(() { + _selectedColor = widget.colors[index]; + }); + }, + colors: widget.colors, + selectedColor: _selectedColor, + ), + ), + ), + ); + }), + ); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(ThemeProvider.borderRadius08), + border: Border.all(color: context.theme.colors.active), + ), + padding: const EdgeInsets.all(ThemeProvider.margin08), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(widget.title, + style: context.theme.textStyle( + textStyle: TextStyles.caption, + color: context.theme.colors.general, + )), + Row( + children: [ + Container( + width: ThemeProvider.margin24, + height: ThemeProvider.margin24, + decoration: _selectedColor == null + ? null + : BoxDecoration( + color: _selectedColor, + borderRadius: BorderRadius.circular( + ThemeProvider.borderRadius04, + ), + ), + ), + const SizedBox(width: ThemeProvider.margin08), + const Icon(Icons.keyboard_arrow_down_rounded), + ], + ) + ], + ), + ), + ); +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/colours_grid.dart b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/colours_grid.dart new file mode 100644 index 000000000..682d3176b --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/colours_grid.dart @@ -0,0 +1,62 @@ +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; + +/// +class ColoursGrid extends StatelessWidget { + final void Function(int index) onColorSelected; + + const ColoursGrid( + {super.key, + required this.colors, + this.selectedColor, + required this.onColorSelected}); + + final List colors; + + final Color? selectedColor; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20.0, + vertical: 16.0, + ), + child: GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 6, + crossAxisSpacing: 4, + mainAxisSpacing: 10, + ), + padding: const EdgeInsets.all(0), + itemCount: colors.length, + itemBuilder: (context, index) { + return GestureDetector( + onTap: () { + onColorSelected(index); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + border: selectedColor == colors[index] + ? Border.all(color: context.theme.colors.blue, width: 1) + : null, + ), + child: Padding( + padding: const EdgeInsets.all(5.0), + child: Container( + decoration: BoxDecoration( + color: colors[index], + borderRadius: BorderRadius.circular(4), + ), + ), + ), + ), + ); + }, + ), + ); + } +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/custom_draggable_sheet.dart b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/custom_draggable_sheet.dart new file mode 100644 index 000000000..443f9815c --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/custom_draggable_sheet.dart @@ -0,0 +1,145 @@ +import 'package:flutter/material.dart'; + +/// A widget to manage the over-scroll to dismiss for a scrollable inside its +/// [child] that being shown by calling [showBottomSheet()]. +/// +/// This widget will listen to [OverscrollNotification] inside its [child] to +/// detect that it has reached its top scroll limit. when user is closing the +/// [child] by over-scrolling, it will call [Navigator.pop()], to fully dismiss +/// the [BottomSheet]. +class CustomDraggableSheet extends StatefulWidget { + /// Initializes a widget to manage the over-scroll to dismiss for a scrollable + /// inside its [child]. + const CustomDraggableSheet({ + required this.child, + Key? key, + this.animationDuration = const Duration(milliseconds: 100), + this.introAnimationDuration = const Duration(milliseconds: 300), + }) : super(key: key); + + /// The sheet that was popped-up inside a [BottomSheet] throw calling + /// [showBottomSheet]. + final Widget child; + + /// The duration of animation whether sheet will fling back to top or dismiss. + final Duration animationDuration; + + /// The duration of the starting animation. + final Duration introAnimationDuration; + + @override + State createState() => _CustomDraggableSheetState(); +} + +class _CustomDraggableSheetState extends State + with SingleTickerProviderStateMixin { + late AnimationController _animationController; + + final GlobalKey> _sheetKey = GlobalKey(); + + Size? _sheetSize; + + bool _overScrolled = false; + + @override + void initState() { + super.initState(); + + _animationController = AnimationController.unbounded(vsync: this, value: 1) + ..addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.completed && + _animationController.value > 0.9) { + Navigator.of(context).pop(); + } + }); + + WidgetsFlutterBinding.ensureInitialized().addPostFrameCallback((_) { + _sheetSize = _initSizes(); + _animationController.animateTo( + 0, + duration: widget.introAnimationDuration, + curve: Curves.easeOut, + ); + }); + } + + Size _initSizes() { + final RenderBox chartBox = + _sheetKey.currentContext!.findRenderObject() as RenderBox; + return chartBox.size; + } + + @override + Widget build(BuildContext context) => AnimatedBuilder( + key: _sheetKey, + animation: _animationController, + builder: (BuildContext context, Widget? child) => FractionalTranslation( + translation: Offset(0, _animationController.value), + child: child, + ), + child: NotificationListener( + onNotification: _handleScrollNotification, + child: widget.child, + ), + ); + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + bool _handleScrollNotification(Notification notification) { + if (_sheetSize != null && notification is OverscrollNotification) { + _overScrolled = true; + _panToBottom(notification); + } else if (notification is ScrollUpdateNotification && _overScrolled) { + _panToTop(notification); + } else if (!_animationController.isAnimating && + notification is ScrollEndNotification && + _overScrolled) { + _overScrolled = false; + _flingToTopOrBottom(); + } + + return false; + } + + void _panToTop(ScrollUpdateNotification notification) { + final double deltaPercent = notification.scrollDelta! / _sheetSize!.height; + + if (deltaPercent > 0) { + _updateSheetHeightBy(deltaPercent); + } + } + + void _panToBottom(OverscrollNotification notification) { + final double deltaPercent = notification.overscroll / _sheetSize!.height; + + if (deltaPercent < 0) { + _updateSheetHeightBy(deltaPercent); + } + } + + void _updateSheetHeightBy(double deltaPercent) { + _animationController.value -= deltaPercent; + final double value = _animationController.value.clamp(0.0, 1.0); + _animationController.value = value; + } + + void _flingToTopOrBottom() { + if (_animationController.value > 0.5) { + _animationController.animateTo( + 1, + duration: widget.animationDuration, + curve: Curves.easeOut, + ); + } else { + _animationController.animateTo( + 0, + duration: widget.animationDuration, + curve: Curves.easeOut, + ); + } + } +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/drawing_tools/active_drawing_tool_item.dart b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/drawing_tools/active_drawing_tool_item.dart new file mode 100644 index 000000000..8c1b5699e --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/drawing_tools/active_drawing_tool_item.dart @@ -0,0 +1,67 @@ +import 'package:deriv_mobile_chart_wrapper/src/extensions.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +/// List item for the active drawing tools list. +class ActiveDrawingToolItem extends StatelessWidget { + /// Initializes the active indicator list item. + const ActiveDrawingToolItem({ + required this.iconAssetPath, + required this.title, + required this.onTapSettings, + required this.onTapDelete, + super.key, + }); + + /// The path to the SVG icon asset. + final String iconAssetPath; + + /// The title of the drawing tool. + final String title; + + /// The callback which will be called when the + /// drawing tool settings button is tapped. + final VoidCallback? onTapSettings; + + /// The callback which will be called when the + /// drawing tool delete button is tapped. + final VoidCallback? onTapDelete; + + @override + Widget build(BuildContext context) { + return Card( + margin: EdgeInsets.zero, + color: context.themeProvider.colors.secondary, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(ThemeProvider.borderRadius08), + ), + ), + child: ListTile( + leading: SvgPicture.asset( + iconAssetPath, + height: ThemeProvider.iconSize24, + package: 'deriv_mobile_chart_wrapper', + ), + title: Text(title), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + /// TODO: Uncomment this when the settings action is implemented. + // IconButton( + // onPressed: onTapSettings, + // color: context.themeProvider.colors.prominent, + // icon: const Icon(Icons.settings_outlined), + // ), + IconButton( + onPressed: onTapDelete, + color: context.themeProvider.colors.prominent, + icon: const Icon(Icons.delete_outline), + ), + ], + ), + ), + ); + } +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/drawing_tools/drawing_tool_icon_button.dart b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/drawing_tools/drawing_tool_icon_button.dart new file mode 100644 index 000000000..bc2a0da32 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/drawing_tools/drawing_tool_icon_button.dart @@ -0,0 +1,41 @@ +import 'package:deriv_mobile_chart_wrapper/src/assets.dart'; +import 'package:deriv_mobile_chart_wrapper/src/mobile_tools_ui/chart_setting_button_with_background.dart'; +import 'package:deriv_mobile_chart_wrapper/src/mobile_tools_ui/tools_controller.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +/// Drawing tool Icon button. +class DrawingToolIconButton extends StatelessWidget { + /// Initializes a new instance of [DrawingToolIconButton]. + const DrawingToolIconButton({ + required this.toolsController, + Key? key, + }) : super(key: key); + + /// Tools controller. + final ToolsController toolsController; + + @override + Widget build(BuildContext context) => AnimatedBuilder( + animation: toolsController, + builder: (_, __) { + final activeDrawingToolsCount = + toolsController.activeDrawingToolsCount; + + return DerivBadge( + count: activeDrawingToolsCount, + child: ChartSettingButtonWithBackground( + onTap: toolsController.showDrawingToolsMenu, + child: SvgPicture.asset( + drawingToolIcon, + width: ThemeProvider.iconSize18, + height: ThemeProvider.iconSize18, + package: 'deriv_mobile_chart_wrapper', + ), + ), + ); + }, + ); +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/drawing_tools/drawing_tool_info_bar.dart b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/drawing_tools/drawing_tool_info_bar.dart new file mode 100644 index 000000000..a9e8edd88 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/drawing_tools/drawing_tool_info_bar.dart @@ -0,0 +1,154 @@ +import 'package:deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper.dart'; +import 'package:deriv_mobile_chart_wrapper/src/assets.dart'; +import 'package:deriv_mobile_chart_wrapper/src/extensions.dart'; +import 'package:deriv_mobile_chart_wrapper/src/models/drawing_tool_info_bar_model.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +/// A widget that displays the drawing tool info bar. +class DrawingToolInfoBar extends StatelessWidget + implements PreferredSizeWidget { + /// Creates a new [DrawingToolInfoBar]. + const DrawingToolInfoBar({ + required this.toolsController, + this.onClosed, + super.key, + }); + + /// The controller for drawing tools. + final ToolsController toolsController; + + /// Callback invoked when the info bar is closed. + final VoidCallback? onClosed; + + @override + Size get preferredSize => const Size.fromHeight(ThemeProvider.margin56); + + @override + Widget build(BuildContext context) => AnimatedBuilder( + animation: toolsController, + builder: (context, _) { + final DrawingToolConfig? drawingTool = + toolsController.drawingToolsData?.selectedDrawingTool; + + if (drawingTool == null) { + return const SizedBox.shrink(); + } else { + return SafeArea( + child: Container( + color: context.theme.colors.secondary, + padding: const EdgeInsets.symmetric( + horizontal: ThemeProvider.margin16, + vertical: ThemeProvider.margin08, + ), + child: _buildInfoBarContent(context, drawingTool), + ), + ); + } + }, + ); + + Widget _buildInfoBarContent( + BuildContext context, + DrawingToolConfig drawingTool, + ) { + final DrawingToolInfoBarModel infoBarData = + _getInfoBarData(context, drawingTool: drawingTool); + + return Container( + padding: const EdgeInsets.all(ThemeProvider.margin08), + decoration: BoxDecoration( + color: context.theme.colors.active, + borderRadius: BorderRadius.circular(ThemeProvider.borderRadius08), + ), + child: Row( + children: [ + _buildStepIndicator(context, infoBarData), + const SizedBox(width: ThemeProvider.margin08), + _buildTitle(context, title: infoBarData.title), + const SizedBox(width: ThemeProvider.margin08), + _buildCloseButton(context), + ], + ), + ); + } + + Widget _buildStepIndicator( + BuildContext context, + DrawingToolInfoBarModel infoBarData, + ) => + Container( + padding: const EdgeInsets.all(ThemeProvider.margin04), + decoration: BoxDecoration( + color: context.theme.colors.lessProminent, + borderRadius: BorderRadius.circular(ThemeProvider.borderRadius04), + ), + child: Text( + _getStepIndicatorTitle( + context, + step: infoBarData.step, + totalSteps: infoBarData.totalSteps, + ), + style: context.theme.textStyle(textStyle: TextStyles.caption), + ), + ); + + Widget _buildTitle(BuildContext context, {required String title}) => Expanded( + child: Text( + title, + style: context.theme.textStyle(textStyle: TextStyles.body1), + textAlign: TextAlign.center, + ), + ); + + Widget _buildCloseButton(BuildContext context) => GestureDetector( + child: SvgPicture.asset( + closeFilledIcon, + width: ThemeProvider.iconSize24, + height: ThemeProvider.iconSize24, + package: 'deriv_mobile_chart_wrapper', + ), + onTap: () { + toolsController.drawingToolsData?.init(); + toolsController + .updateDrawingToolsData(toolsController.drawingToolsData); + onClosed?.call(); + }, + ); + + DrawingToolInfoBarModel _getInfoBarData( + BuildContext context, { + required DrawingToolConfig drawingTool, + }) { + if (drawingTool is LineDrawingToolConfig || + drawingTool is RayDrawingToolConfig) { + final bool isDrawingToolSelected = + toolsController.drawingToolsData?.selectedDrawingTool?.drawingData == + null; + final bool isDrawingToolFinished = toolsController.drawingToolsData + ?.selectedDrawingTool?.drawingData?.isDrawingFinished ?? + true; + + return DrawingToolInfoBarModel( + step: isDrawingToolSelected ? '1' : '2', + totalSteps: 2, + title: !isDrawingToolFinished + ? context.mobileChartWrapperLocalizations.informTapToSetFinalPoint + : context.mobileChartWrapperLocalizations.informTapToSetFirstPoint, + ); + } + + // Default case for unsupported drawing tools. + return DrawingToolInfoBarModel(step: '-', totalSteps: 0, title: ''); + } + + String _getStepIndicatorTitle( + BuildContext context, { + required String step, + required int totalSteps, + }) { + return '$step ${context.mobileChartWrapperLocalizations.labelOf} ' + '$totalSteps'; + } +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/drawing_tools/drawing_tool_item.dart b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/drawing_tools/drawing_tool_item.dart new file mode 100644 index 000000000..b58ebb24d --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/drawing_tools/drawing_tool_item.dart @@ -0,0 +1,73 @@ +import 'package:deriv_mobile_chart_wrapper/src/extensions.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/widgets/widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +/// List item widget to show a drawing tool. +class DrawingToolItem extends StatelessWidget { + /// Constructor of the widget + const DrawingToolItem({ + required this.iconAssetPath, + required this.title, + required this.onTap, + this.count = 0, + super.key, + }); + + /// The path to the SVG icon asset. + final String iconAssetPath; + + /// The title of the drawing tool. + final String title; + + /// The callback which will be called when the drawing tool item is tapped. + final VoidCallback onTap; + + /// Number of added drawing tools of this type. + /// + /// It will show in the item if it's greater than 0. + final int count; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + behavior: HitTestBehavior.translucent, + child: Padding( + padding: const EdgeInsets.all(ThemeProvider.margin16), + child: Row( + children: [ + _buildDrawingToolIcon(), + const SizedBox(width: Dimens.margin08), + _buildDrawingToolTitle(context), + const SizedBox(width: Dimens.margin08), + _buildDrawingToolBadge(count), + ], + ), + ), + ); + } + + Widget _buildDrawingToolIcon() => SvgPicture.asset( + iconAssetPath, + width: ThemeProvider.margin24, + height: ThemeProvider.margin24, + package: 'deriv_mobile_chart_wrapper', + ); + + Widget _buildDrawingToolTitle(BuildContext context) => Text( + title, + style: context.themeProvider.textStyle( + textStyle: TextStyles.body1, + color: context.themeProvider.colors.general, + ), + ); + + Widget _buildDrawingToolBadge(int count) { + return DerivBadge( + count: count, + enabled: count > 0, + ); + } +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/drawing_tools/drawing_tools.dart b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/drawing_tools/drawing_tools.dart new file mode 100644 index 000000000..23ed89928 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/drawing_tools/drawing_tools.dart @@ -0,0 +1,5 @@ +export 'drawing_tool_icon_button.dart'; +export 'active_drawing_tool_item.dart'; +export 'drawing_tool_item.dart'; +export 'drawing_tools_selector.dart'; +export 'drawing_tool_info_bar.dart'; diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/drawing_tools/drawing_tools_selector.dart b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/drawing_tools/drawing_tools_selector.dart new file mode 100644 index 000000000..6754e3113 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/drawing_tools/drawing_tools_selector.dart @@ -0,0 +1,325 @@ +import 'package:collection/collection.dart'; +import 'package:deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper.dart'; +import 'package:deriv_mobile_chart_wrapper/src/assets.dart'; +import 'package:deriv_mobile_chart_wrapper/src/extensions.dart'; +import 'package:deriv_mobile_chart_wrapper/src/helpers.dart'; +import 'package:deriv_mobile_chart_wrapper/src/models/drawing_tool_item_model.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:provider/provider.dart'; + +/// Drawing tools selector widget. It allows users to select a drawing tool, +/// view active drawing tools, configure drawing tools, and delete all drawing +/// tools. +class DrawingToolsSelector extends StatefulWidget { + const DrawingToolsSelector({ + required this.onDrawingToolSelected, + super.key, + }); + + /// Drawing tools selection callback. + final void Function(DrawingToolConfig drawingToolConfig)? + onDrawingToolSelected; + + @override + State createState() => _DrawingToolsSelectorState(); +} + +class _DrawingToolsSelectorState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + + late List _drawingTools; + + late AddOnsRepository drawingToolsRepo; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 2, vsync: this); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + drawingToolsRepo = Provider.of>( + context, + ); + _drawingTools = getDrawingToolsList(context); + + // Only change the tab index if the active drawing tools list is empty and + // the current tab is not the same 1st index tab. + if (drawingToolsRepo.items.isEmpty && _tabController.index != 1) { + _tabController.index = 1; + } + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildTabBar(context), + Expanded( + child: Ink( + color: context.theme.colors.primary, + child: TabBarView( + controller: _tabController, + children: [ + _buildActiveDrawingToolsTab(context), + _buildDrawingToolListTab(context), + ], + ), + ), + ), + ], + ); + + Widget _buildTabBar(BuildContext context) => Stack( + alignment: Alignment.bottomCenter, + children: [ + Container( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: context.theme.colors.disabled, + width: ThemeProvider.margin02, + ), + ), + ), + ), + TabBar( + controller: _tabController, + indicatorColor: context.theme.colors.danger, + labelStyle: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent, + ), + unselectedLabelStyle: context.theme.textStyle( + textStyle: TextStyles.body1, + color: context.theme.colors.general, + ), + tabs: [ + Tab( + text: '${context.mobileChartWrapperLocalizations.labelActive}' + ' (${drawingToolsRepo.items.length})'), + Tab(text: context.mobileChartWrapperLocalizations.labelTools), + ], + ), + ], + ); + + Widget _buildActiveDrawingToolsTab(BuildContext context) { + final List activeDrawingTools = drawingToolsRepo.items; + + if (activeDrawingTools.isEmpty) { + return _buildEmptyActiveDrawingTools(); + } else { + return _buildActiveDrawingToolsList(activeDrawingTools); + } + } + + Widget _buildDrawingToolListTab(BuildContext context) { + final activeDrawingTools = drawingToolsRepo.items; + final toolCounts = _computeToolCounts(activeDrawingTools); + + return ListView.builder( + itemCount: _drawingTools.length, + itemBuilder: (context, index) { + final toolItem = _drawingTools[index]; + final count = toolCounts[toolItem.config.runtimeType] ?? 0; + + return DrawingToolItem( + iconAssetPath: toolItem.icon, + title: toolItem.title, + count: count, + onTap: () => widget.onDrawingToolSelected?.call(toolItem.config), + ); + }, + ); + } + + Widget _buildEmptyActiveDrawingTools() => Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + emptyStateDrawingToolsIcon, + height: Dimens.iconSize48, + package: 'deriv_mobile_chart_wrapper', + ), + const SizedBox(height: ThemeProvider.margin08), + Text( + context.mobileChartWrapperLocalizations + .informNoActiveDrawingTools, + style: context.themeProvider.textStyle( + textStyle: TextStyles.body1, + color: context.theme.colors.lessProminent, + ), + ), + ], + ), + ), + ), + Container( + color: context.theme.colors.secondary, + padding: const EdgeInsets.all(ThemeProvider.margin16), + child: PrimaryButton( + child: Text( + context.mobileChartWrapperLocalizations.actionAddDrawingTool, + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent, + ), + ), + onPressed: () => _tabController.animateTo(1), + ), + ), + ], + ); + + Widget _buildActiveDrawingToolsList( + List activeDrawingTools, + ) => + Column( + children: [ + _buildActiveDrawingToolsActionBar(activeDrawingTools), + Expanded( + child: ListView.builder( + itemCount: activeDrawingTools.length, + itemBuilder: (BuildContext context, int index) { + final activeDrawingToolItem = activeDrawingTools[index]; + final DrawingToolItemModel? drawingToolItem = + _drawingTools.firstWhereOrNull((element) => + element.config.runtimeType == + activeDrawingToolItem.runtimeType); + + if (drawingToolItem != null) { + return _buildActiveDrawingToolItem( + context, + toolIcon: drawingToolItem.icon, + title: getDrawingToolTitleWithCount( + context, + activeDrawingToolItem, + ), + onTapDelete: () { + drawingToolsRepo.removeAt(index); + if (drawingToolsRepo.items.isEmpty) { + Navigator.pop(context); + } + }, + onTapSettings: () {}, + ); + } else { + return const SizedBox.shrink(); + } + }, + ), + ), + ], + ); + + Widget _buildActiveDrawingToolsActionBar( + List activeDrawingTools, + ) => + Padding( + padding: const EdgeInsets.only( + top: ThemeProvider.margin16, + bottom: ThemeProvider.margin08, + left: ThemeProvider.margin16, + right: ThemeProvider.margin16, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Visibility( + visible: activeDrawingTools.isNotEmpty, + maintainSize: true, + maintainState: true, + maintainAnimation: true, + child: SecondaryButton( + onPressed: _showDeleteAllDrawingToolsDialog, + child: Center( + child: Text( + context.mobileChartWrapperLocalizations.labelDeleteAll, + style: context.themeProvider.textStyle( + textStyle: TextStyles.caption, + color: context.themeProvider.colors.prominent, + ), + ), + ), + ), + ), + ], + ), + ); + + Widget _buildActiveDrawingToolItem( + BuildContext context, { + required String toolIcon, + required String title, + required VoidCallback? onTapDelete, + required VoidCallback? onTapSettings, + }) => + Card( + margin: const EdgeInsets.symmetric( + horizontal: ThemeProvider.margin16, + vertical: ThemeProvider.margin08, + ), + color: context.themeProvider.colors.secondary, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(ThemeProvider.borderRadius08), + ), + ), + child: ActiveDrawingToolItem( + iconAssetPath: toolIcon, + title: title, + onTapDelete: onTapDelete, + onTapSettings: onTapSettings, + ), + ); + + Map _computeToolCounts( + List activeDrawingTools, + ) => + activeDrawingTools.fold( + {}, + (counts, tool) { + counts[tool.runtimeType] = (counts[tool.runtimeType] ?? 0) + 1; + return counts; + }, + ); + + void _showDeleteAllDrawingToolsDialog() => showAlertDialog( + context: context, + title: + context.mobileChartWrapperLocalizations.labelDeleteAllDrawingTools, + content: Text( + context.mobileChartWrapperLocalizations.informDeleteAllDrawingTools, + style: TextStyles.subheading, + ), + positiveActionLabel: + context.mobileChartWrapperLocalizations.labelDeleteAll, + negativeButtonLabel: + context.mobileChartWrapperLocalizations.labelCancel, + showLoadingIndicator: false, + onPositiveActionPressed: () { + drawingToolsRepo.clear(); + Navigator.pop(context); + }, + onNegativeActionPressed: () => Navigator.pop(context), + ); +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/indicator_description_bottom_sheet.dart b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/indicator_description_bottom_sheet.dart new file mode 100644 index 000000000..7ef75b8de --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/indicator_description_bottom_sheet.dart @@ -0,0 +1,49 @@ +import 'package:deriv_mobile_chart_wrapper/src/extensions.dart'; +import 'package:deriv_mobile_chart_wrapper/src/models/indicator_item_model.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; + +class IndicatorDescriptionBottomSheet extends StatelessWidget { + const IndicatorDescriptionBottomSheet({ + super.key, + required this.indicator, + required this.onAddIndicatorPressed, + }); + + final IndicatorItemModel indicator; + final VoidCallback onAddIndicatorPressed; + + @override + Widget build(BuildContext context) { + return DerivBottomSheet( + title: indicator.title, + showBackButton: true, + hasActionButton: true, + actionButtonLabel: + context.mobileChartWrapperLocalizations.infoAddSelectedIndicator( + indicator.subtitle, + ), + onActionButtonPressed: onAddIndicatorPressed, + child: SizedBox( + height: MediaQuery.of(context).size.height * 0.5, + child: ColoredBox( + color: context.theme.colors.primary, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: ThemeProvider.margin16, + horizontal: ThemeProvider.margin16, + ), + child: Text( + indicator.description, + style: context.theme.textStyle( + textStyle: TextStyles.body1, + color: context.theme.colors.general, + ), + ), + ), + ), + ), + ); + } +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/indicator_list_item.dart b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/indicator_list_item.dart new file mode 100644 index 000000000..b8bdd3540 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/indicator_list_item.dart @@ -0,0 +1,83 @@ +import 'package:deriv_mobile_chart_wrapper/src/extensions.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/widgets/widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +/// List item widget to show an indicator. +class IndicatorListItem extends StatelessWidget { + /// Constructor of the widget + const IndicatorListItem({ + required this.iconAssetPath, + required this.title, + required this.onTap, + required this.onInfoIconTapped, + this.count = 0, + super.key, + }); + + /// The path to the SVG icon asset. + final String iconAssetPath; + + /// The title of the indicator. + final String title; + + /// The callback which will be called when the indicator item is tapped. + final VoidCallback onTap; + + /// The callback which will be called when the info icon is tapped. + final VoidCallback onInfoIconTapped; + + /// Number of added indicators of this type. + /// + /// It will show in the item if it's greater than 0. + final int count; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + behavior: HitTestBehavior.translucent, + child: Padding( + padding: const EdgeInsets.all(ThemeProvider.margin16), + child: Row( + children: [ + _buildIndicatorIcon(), + const SizedBox(width: Dimens.margin08), + _buildIndicatorTitle(context), + const SizedBox(width: Dimens.margin08), + _buildIndicatorBadge(count), + const Spacer(), + IconButton( + icon: const Icon(Icons.info_outline), + color: context.themeProvider.colors.prominent, + onPressed: onInfoIconTapped, + ), + ], + ), + ), + ); + } + + Widget _buildIndicatorIcon() => SvgPicture.asset( + iconAssetPath, + width: ThemeProvider.margin24, + height: ThemeProvider.margin24, + package: 'deriv_mobile_chart_wrapper', + ); + + Widget _buildIndicatorTitle(BuildContext context) => Text( + title, + style: context.themeProvider.textStyle( + textStyle: TextStyles.body1, + color: context.themeProvider.colors.general, + ), + ); + + Widget _buildIndicatorBadge(int count) { + return DerivBadge( + count: count, + enabled: count > 0, + ); + } +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/indicator_menu_button.dart b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/indicator_menu_button.dart new file mode 100644 index 000000000..9f47109d0 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/indicator_menu_button.dart @@ -0,0 +1,55 @@ +import 'package:deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper.dart'; +import 'package:deriv_mobile_chart_wrapper/src/assets.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +/// A button that opens the indicator menu. +class IndicatorMenuButton extends StatefulWidget { + /// Initializes the indicator menu button. + const IndicatorMenuButton({ + required this.toolsController, + super.key, + }); + + final ToolsController toolsController; + + @override + State createState() => _IndicatorMenuButtonState(); +} + +class _IndicatorMenuButtonState extends State { + late ValueNotifier _count; + + @override + void initState() { + super.initState(); + _count = ValueNotifier( + widget.toolsController.configs?.indicatorConfigs.length); + widget.toolsController.addListener(() { + _count.value = widget.toolsController.configs?.indicatorConfigs.length; + }); + } + + @override + Widget build(BuildContext context) => ValueListenableBuilder( + valueListenable: _count, + builder: (context, count, _) { + return DerivBadge( + count: count, + enabled: count != null, + child: ChartSettingButtonWithBackground( + onTap: () { + widget.toolsController.showIndicatorsToolsMenu(); + }, + child: SvgPicture.asset( + indicatorsMenuIcon, + width: ThemeProvider.margin18, + height: ThemeProvider.margin18, + package: 'deriv_mobile_chart_wrapper', + ), + ), + ); + }); +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/indicator_settings_bottom_sheet.dart b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/indicator_settings_bottom_sheet.dart new file mode 100644 index 000000000..0bc7e17eb --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/indicator_settings_bottom_sheet.dart @@ -0,0 +1,141 @@ +import 'package:deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class IndicatorSettingsBottomSheet extends StatefulWidget { + const IndicatorSettingsBottomSheet({ + required this.settings, + required this.indicator, + required this.onTapInfo, + required this.onTapDelete, + this.theme, + super.key, + }); + + /// The theme of the chart which the bottom sheet is being placed inside. + final ChartTheme? theme; + + /// The settings widget of the indicator. + final Widget settings; + + /// The indicator name. + final String indicator; + + /// The callback when the info button is pressed. + final VoidCallback onTapInfo; + + /// The callback when the delete button is pressed. + final VoidCallback onTapDelete; + + @override + State createState() => + _IndicatorSettingsBottomSheetState(); +} + +class _IndicatorSettingsBottomSheetState + extends State { + late ChartTheme _theme; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + _theme = widget.theme ?? + (Theme.of(context).brightness == Brightness.dark + ? ChartDefaultDarkTheme() + : ChartDefaultLightTheme()); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: SizedBox( + height: MediaQuery.of(context).size.height, + child: Provider.value( + value: _theme, + child: ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(_theme.borderRadius24Chart), + topRight: Radius.circular(_theme.borderRadius24Chart), + ), + child: Material( + elevation: 8, + color: _theme.base07Color, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: ThemeProvider.margin16, + ), + child: Column( + children: [ + _buildTopHandle(), + _buildHeader(), + Expanded( + child: widget.settings, + ), + ], + ), + ), + ), + ), + ), + ), + ); + } + + Widget _buildTopHandle() => Container( + padding: EdgeInsets.symmetric(vertical: _theme.margin08Chart), + width: double.infinity, + child: Center( + child: Container( + width: ThemeProvider.margin40, + height: ThemeProvider.margin04, + decoration: BoxDecoration( + color: _theme.base05Color, + borderRadius: BorderRadius.circular(_theme.borderRadius04Chart), + ), + ), + ), + ); + + Widget _buildHeader() => Padding( + padding: EdgeInsets.symmetric(vertical: _theme.margin12Chart), + child: Row( + children: [ + /// make it downward + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: const Icon( + Icons.keyboard_arrow_down_rounded, + ), + ), + const SizedBox( + width: ThemeProvider.margin16, + ), + Text( + widget.indicator, + style: context.theme.textStyle( + textStyle: TextStyles.title, + color: context.theme.colors.prominent, + ), + ), + const Spacer(), + IconButton( + icon: const Icon(Icons.info_outline), + color: context.theme.colors.prominent, + onPressed: widget.onTapInfo, + ), + const SizedBox( + width: ThemeProvider.margin12, + ), + IconButton( + onPressed: widget.onTapDelete, + color: context.theme.colors.prominent, + icon: const Icon(Icons.delete_outline), + ), + ], + ), + ); +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/indicator_settings_description_bottom_sheet.dart b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/indicator_settings_description_bottom_sheet.dart new file mode 100644 index 000000000..aacf8f994 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/indicator_settings_description_bottom_sheet.dart @@ -0,0 +1,81 @@ +import 'package:deriv_mobile_chart_wrapper/src/models/indicator_item_model.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; + +class IndicatorSettingsDescriptionBottomSheet extends StatelessWidget { + const IndicatorSettingsDescriptionBottomSheet({ + super.key, + required this.indicator, + }); + + final IndicatorItemModel indicator; + + @override + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(ThemeProvider.borderRadius16), + topRight: Radius.circular(ThemeProvider.borderRadius16), + ), + child: Material( + elevation: 8, + color: context.theme.colors.secondary, + child: ListView( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + children: [ + _buildTopHandle(context), + _buildTitle(context), + _buildDescription(context), + ], + ), + ), + ); + } + + Widget _buildTopHandle(BuildContext context) => Container( + padding: const EdgeInsets.symmetric(vertical: ThemeProvider.margin08), + width: double.infinity, + child: Center( + child: Container( + width: ThemeProvider.margin40, + height: ThemeProvider.margin04, + decoration: BoxDecoration( + color: context.theme.colors.disabled, + borderRadius: BorderRadius.circular( + ThemeProvider.borderRadius04, + ), + ), + ), + ), + ); + + Widget _buildTitle(BuildContext context) => Padding( + padding: const EdgeInsets.symmetric(vertical: ThemeProvider.margin14), + child: Text( + indicator.title, + style: context.theme.textStyle( + textStyle: TextStyles.subheading, + color: context.theme.colors.prominent, + ), + textAlign: TextAlign.center, + ), + ); + + Widget _buildDescription(BuildContext context) => ColoredBox( + color: context.theme.colors.primary, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: ThemeProvider.margin16, + horizontal: ThemeProvider.margin16, + ), + child: Text( + indicator.description, + style: context.theme.textStyle( + textStyle: TextStyles.body1, + color: context.theme.colors.general, + ), + ), + ), + ); +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/mobile_tools_bottom_sheet_content.dart b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/mobile_tools_bottom_sheet_content.dart new file mode 100644 index 000000000..9119ffaf6 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/mobile_tools_bottom_sheet_content.dart @@ -0,0 +1,588 @@ +import 'package:deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper.dart'; +import 'package:deriv_mobile_chart_wrapper/src/assets.dart'; +import 'package:deriv_mobile_chart_wrapper/src/enums.dart'; +import 'package:deriv_mobile_chart_wrapper/src/extensions.dart'; +import 'package:deriv_mobile_chart_wrapper/src/helpers.dart'; +import 'package:deriv_mobile_chart_wrapper/src/indicator_event_service.dart'; +import 'package:deriv_mobile_chart_wrapper/src/mobile_tools_ui/active_indicator_list_item.dart'; +import 'package:deriv_mobile_chart_wrapper/src/mobile_tools_ui/indicator_settings_description_bottom_sheet.dart'; +import 'package:deriv_mobile_chart_wrapper/src/models/indicator_item_model.dart'; +import 'package:deriv_mobile_chart_wrapper/src/models/indicator_tab_label.dart'; +import 'package:deriv_mobile_chart_wrapper/src/pages/bb_settings_page.dart'; +import 'package:deriv_mobile_chart_wrapper/src/pages/ma_settings_page.dart'; +import 'package:deriv_mobile_chart_wrapper/src/pages/macd_settings_page.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:provider/provider.dart'; + +import '../core_widgets/no_glow_scroll_behavior.dart'; +import 'indicator_description_bottom_sheet.dart'; +import 'indicator_settings_bottom_sheet.dart'; + +/// Bottom sheet content to show the list of support tools (indicators/ drawing +/// tools) for the mobile version. +class MobileToolsBottomSheetContent extends StatefulWidget { + /// Initializes the bottom sheet content. + const MobileToolsBottomSheetContent({ + this.selectedTab = IndicatorTabLabel.all, + this.eventTracker, + super.key, + }); + + final IndicatorTabLabel selectedTab; + + final ChartEventTracker? eventTracker; + + @override + State createState() => + _MobileToolsBottomSheetContentState(); +} + +class _MobileToolsBottomSheetContentState + extends State { + late IndicatorTabLabel _selectedChip; + + /// Returns `true` if the limit of active indicators is reached. + bool get isLimitReached => indicatorsRepo.items.length >= 3; + + late AddOnsRepository indicatorsRepo; + + late List indicators; + + late IndicatorConfig _updatedConfig; + + @override + void initState() { + super.initState(); + _selectedChip = widget.selectedTab; + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + indicatorsRepo = Provider.of>(context); + indicators = [ + IndicatorItemModel( + category: IndicatorCategory.momentum, + title: context.mobileChartWrapperLocalizations.labelMACD, + subtitle: context.mobileChartWrapperLocalizations.labelMACD, + icon: macdIcon, + config: const MACDIndicatorConfig(), + description: context.mobileChartWrapperLocalizations.infoMACD, + ), + IndicatorItemModel( + category: IndicatorCategory.momentum, + title: + context.mobileChartWrapperLocalizations.labelRelativeStrengthIndex, + subtitle: context.mobileChartWrapperLocalizations.labelRSI, + icon: rsiIcon, + config: const RSIIndicatorConfig(), + description: context.mobileChartWrapperLocalizations.infoRSI, + ), + IndicatorItemModel( + category: IndicatorCategory.volatility, + title: context.mobileChartWrapperLocalizations.labelBollingerBands, + subtitle: context.mobileChartWrapperLocalizations.labelBB, + icon: bollingerBandsIcon, + config: const BollingerBandsIndicatorConfig(), + description: context.mobileChartWrapperLocalizations.infoBB, + ), + IndicatorItemModel( + category: IndicatorCategory.movingAverages, + title: context.mobileChartWrapperLocalizations.labelMovingAverage, + subtitle: context.mobileChartWrapperLocalizations.labelMA, + icon: movingAverageIcon, + config: const MAIndicatorConfig(), + description: context.mobileChartWrapperLocalizations.infoMA, + ), + ]; + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Ink( + color: context.theme.colors.primary, + child: Column( + children: [ + const SizedBox(height: ThemeProvider.margin16), + _buildChipsList(), + const SizedBox(height: ThemeProvider.margin16), + if (isLimitReached && _selectedChip != IndicatorTabLabel.active) + _buildLimitInfoBanner(), + Expanded( + child: _selectedChip == IndicatorTabLabel.active + ? _buildIndicatorsActiveTab() + : _buildIndicatorsList( + getFilteredIndicators(indicators), + ), + ), + ], + ), + ), + ), + ], + ); + } + + List getFilteredIndicators( + List indicators) { + return _selectedChip == IndicatorTabLabel.all + ? indicators + : indicators + .where( + //TODO(Ramin): Check if we can only have one enum to use for + // labels and indicators' model category. + (indicator) => + indicator.category == _selectedChip.toIndicatorCategory, + ) + .toList(); + } + + Widget _buildActiveTabHeader() { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: ThemeProvider.margin16, + ), + child: Row( + children: [ + Expanded( + child: Text( + context + .mobileChartWrapperLocalizations.infoUpto3indicatorsAllowed, + style: context.themeProvider.textStyle( + textStyle: TextStyles.caption, + color: context.themeProvider.colors.general, + ), + textAlign: TextAlign.start, + maxLines: 3, + ), + ), + const SizedBox(width: ThemeProvider.margin16), + Visibility( + visible: indicatorsRepo.items.isNotEmpty, + maintainSize: true, + maintainState: true, + maintainAnimation: true, + child: SecondaryButton( + onPressed: _showDeleteAllIndicatorsDialog, + child: Center( + child: Text( + context.mobileChartWrapperLocalizations.labelDeleteAll, + style: context.themeProvider.textStyle( + textStyle: TextStyles.caption, + color: context.themeProvider.colors.prominent, + ), + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildIndicatorsList(List filteredIndicators) { + return ListView.builder( + itemCount: filteredIndicators.length, + itemBuilder: (_, index) { + final IndicatorItemModel indicator = filteredIndicators[index]; + + return Interaction( + isEnabled: !isLimitReached, + child: IndicatorListItem( + iconAssetPath: indicator.icon, + title: indicator.title, + count: _getIndicatorCount(indicator), + onInfoIconTapped: () { + _showIndicatorInfoBottomSheet(indicator); + }, + onTap: () { + indicatorsRepo.add( + indicator.config.copyWith( + number: indicatorsRepo.getNumberForNewAddOn(indicator.config), + ), + ); + widget.eventTracker?.logAddIndicatorByClickIndicatorType( + indicator.config.title, + indicator.category.name, + ); + }, + ), + ); + }, + ); + } + + Widget _buildIndicatorsActiveTab() { + return Column( + children: [ + _buildActiveTabHeader(), + const SizedBox(height: ThemeProvider.margin16), + Expanded( + child: indicatorsRepo.items.isEmpty + ? _buildIndicatorEmptyState() + : _buildActiveIndicatorsList(), + ), + ], + ); + } + + Widget _buildActiveIndicatorsList() { + return Padding( + padding: const EdgeInsets.only( + left: ThemeProvider.margin16, + right: ThemeProvider.margin16, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: ListView.separated( + itemCount: indicatorsRepo.items.length, + separatorBuilder: (_, __) => + const SizedBox(height: ThemeProvider.margin08), + itemBuilder: (_, index) { + final IndicatorConfig indicatorConfig = + indicatorsRepo.items[index]; + + return ActiveIndicatorListItem( + iconAssetPath: getIndicatorIconPath(indicatorConfig), + title: getIndicatorAbbreviationWithCount( + indicatorConfig, context), + subtitle: '(${indicatorConfig.configSummary})', + onTapSetting: () { + widget.eventTracker?.logEditIndicatorSettings( + indicatorsRepo.items[index].title, + getIndicatorCategoryTitle( + indicatorsRepo.items[index].title, + ), + ); + _updatedConfig = indicatorConfig; + showDerivModalBottomSheet( + context, + (context) => IndicatorSettingsBottomSheet( + indicator: getIndicatorAbbreviationWithCount( + indicatorConfig, + context, + ), + settings: _getConfigSettingPage(index, indicatorConfig), + onTapDelete: () async { + await _showDeleteIndicatorDialog( + indicatorConfig, + index, + onDelete: () => + widget.eventTracker?.logDeleteActiveIndicatorFromSettings( + indicatorConfig.title, + getIndicatorCategoryTitle(indicatorConfig.title), + ), + ); + + if (context.mounted) Navigator.pop(context); + }, + onTapInfo: () { + final IndicatorItemModel indicatorModel = + indicators.firstWhere((element) => + element.config.runtimeType == + indicatorConfig.runtimeType); + + widget.eventTracker + ?.logOpenIndicatorInfoFromIndicatorSettings( + indicatorModel.title, + indicatorModel.category.name, + ); + + showModalBottomSheet( + context: context, + builder: (context) { + return IndicatorSettingsDescriptionBottomSheet( + indicator: indicatorModel, + ); + }).then( + (value) => + widget.eventTracker?.logCloseIndicatorInfo( + indicatorModel.config.title, + indicatorModel.category.name, + ), + ); + }, + ), + showDragHandle: false, + ); + }, + onTapDelete: () => _showDeleteIndicatorDialog( + indicatorConfig, + index, + onDelete: () => + widget.eventTracker?.logDeleteActiveIndicator( + indicatorConfig.title, + getIndicatorCategoryTitle( + indicatorConfig.title, + ), + ), + ), + ); + }, + ), + ), + ], + ), + ); + } + + void _onConfigUpdated(IndicatorConfig config) { + _updatedConfig = config; + } + + void _onApply(int index, IndicatorConfig config) { + indicatorsRepo.updateAt(index, config); + Navigator.pop(context); + } + + Widget _getConfigSettingPage(int index, IndicatorConfig initialConfig) { + if (initialConfig is RSIIndicatorConfig) { + return RSISettingPage( + initialConfig: initialConfig, + onConfigUpdated: _onConfigUpdated, + onApply: () => _onApply(index, _updatedConfig), + onReset: () => widget.eventTracker?.logResetIndicatorSettings( + initialConfig.title, + getIndicatorCategoryTitle( + initialConfig.title, + ), + ), + ); + } else if (initialConfig is BollingerBandsIndicatorConfig) { + return BollingerBandsSettingsPage( + initialConfig: initialConfig, + onConfigUpdated: _onConfigUpdated, + onApply: () => _onApply(index, _updatedConfig), + onReset: () => widget.eventTracker?.logResetIndicatorSettings( + initialConfig.title, + getIndicatorCategoryTitle( + initialConfig.title, + ), + ), + ); + } else if (initialConfig is MACDIndicatorConfig) { + return MACDSettingsPage( + initialConfig: initialConfig, + onConfigUpdated: _onConfigUpdated, + onApply: () => _onApply(index, _updatedConfig), + onReset: () => widget.eventTracker?.logResetIndicatorSettings( + initialConfig.title, + getIndicatorCategoryTitle( + initialConfig.title, + ), + ), + ); + } else if (initialConfig is MAIndicatorConfig) { + return MASettingsPage( + initialConfig: initialConfig, + onConfigUpdated: _onConfigUpdated, + onApply: () => _onApply(index, _updatedConfig), + onReset: () => widget.eventTracker?.logResetIndicatorSettings( + initialConfig.title, + getIndicatorCategoryTitle( + initialConfig.title, + ), + ), + ); + } + return const SizedBox.shrink(); + } + + Widget _buildIndicatorEmptyState() { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: Center( + child: ListView( + children: [ + SvgPicture.asset( + emptyStateIndicatorsIcon, + height: Dimens.iconSize48, + package: 'deriv_mobile_chart_wrapper', + ), + const SizedBox(height: ThemeProvider.margin08), + Text( + context + .mobileChartWrapperLocalizations.infoNoActiveIndicators, + style: context.themeProvider.textStyle( + textStyle: TextStyles.body1, + color: const Color(0xFF999999), + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + Container( + color: context.theme.colors.secondary, + padding: const EdgeInsets.all(ThemeProvider.margin16), + child: PrimaryButton( + child: Text( + context.mobileChartWrapperLocalizations.infoAddIndicator, + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent, + ), + ), + onPressed: () { + setState(() { + _selectedChip = IndicatorTabLabel.all; + }); + }, + ), + ), + ], + ); + } + + Widget _buildLimitInfoBanner() { + return InfoBanner( + message: context + .mobileChartWrapperLocalizations.infoMaximumActiveIndicatorsAdded, + ); + } + + /// Returns the number of active indicators for specified [indicator]. + int _getIndicatorCount(IndicatorItemModel indicator) { + return indicatorsRepo.items + .where((item) => item.runtimeType == indicator.config.runtimeType) + .length; + } + + Widget _buildChipsList() { + // Overscroll behaviour of horizontal chips list sometimes triggers + // BottomSheet top <-> bottom dragging. That's why we're capturing the + // overscroll here so it doesn't propagate up to the BottomSheet. + return NotificationListener( + onNotification: (OverscrollNotification notification) { + return true; + }, + child: ScrollConfiguration( + behavior: NoGlowScrollBehavior(), + child: ChipsList( + isHorizontalPaddingEnabled: true, + horizontalPadding: Dimens.margin16, + items: [ + ...IndicatorTabLabel.values + .map((tabLabel) => tabLabel == + IndicatorTabLabel.active + ? CustomChip( + labelBuilder: (_, __) => IndicatorTabLabel.activeCount( + indicatorsRepo.items.length, + ), + value: IndicatorTabLabel.active, + onTap: _onChipTapped, + isSelected: _selectedChip == IndicatorTabLabel.active, + borderRadius: ThemeProvider.margin40, + ) + : CustomChip( + value: tabLabel, + labelBuilder: (_, __) => tabLabel.getTitle(context), + onTap: _onChipTapped, + isSelected: _selectedChip == tabLabel, + borderRadius: ThemeProvider.margin40, + )) + .toList(), + ], + ), + ), + ); + } + + void _showIndicatorInfoBottomSheet(IndicatorItemModel indicator) { + widget.eventTracker?.logOpenIndicatorInfoFromIndicatorsList( + indicator.config.title, + indicator.category.name, + ); + showModalBottomSheet( + context: context, + builder: (context) => IndicatorDescriptionBottomSheet( + indicator: indicator, + onAddIndicatorPressed: () { + indicatorsRepo.add(indicator.config); + Navigator.of(context).pop(); + widget.eventTracker?.logAddIndicatorByClickAddOnIndicatorInfo( + indicator.title, + indicator.category.name, + ); + }, + ), + ).then( + (_) => widget.eventTracker?.logCloseIndicatorInfo( + indicator.config.title, + indicator.category.name, + ), + ); + } + + void _onChipTapped(IndicatorTabLabel? value, String? title) => + setState(() => _selectedChip = value ?? IndicatorTabLabel.all); + + Future _showDeleteIndicatorDialog( + IndicatorConfig config, + int index, { + VoidCallback? onDelete, + }) => + showAlertDialog( + context: context, + title: context.mobileChartWrapperLocalizations.labelDeleteIndicator( + getIndicatorAbbreviationWithCount(config, context), + ), + content: Text( + context.mobileChartWrapperLocalizations.infoDeleteIndicator, + style: TextStyles.subheading, + ), + positiveActionLabel: + context.mobileChartWrapperLocalizations.labelDelete, + negativeButtonLabel: + context.mobileChartWrapperLocalizations.labelCancel, + showLoadingIndicator: false, + onPositiveActionPressed: () { + onDelete?.call(); + indicatorsRepo.removeAt(index); + Navigator.pop(context); + }, + onNegativeActionPressed: () { + Navigator.pop(context); + }); + + void _showDeleteAllIndicatorsDialog() { + showAlertDialog( + context: context, + title: context.mobileChartWrapperLocalizations.labelDeleteAllIndicators, + content: Text( + context.mobileChartWrapperLocalizations.infoDeleteAllIndicators, + style: TextStyles.subheading, + ), + positiveActionLabel: + context.mobileChartWrapperLocalizations.labelDeleteAll, + negativeButtonLabel: + context.mobileChartWrapperLocalizations.labelCancel, + showLoadingIndicator: false, + onPositiveActionPressed: () { + indicatorsRepo.clear(); + Navigator.pop(context); + widget.eventTracker?.logCleanAllActiveIndicator(); + }, + onNegativeActionPressed: () { + Navigator.pop(context); + }); + } + + @override + void dispose() { + widget.eventTracker?.logCloseIndicatorTypesBottomSheet(); + super.dispose(); + } +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/mobile_tools_ui.dart b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/mobile_tools_ui.dart new file mode 100644 index 000000000..18887d6db --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/mobile_tools_ui.dart @@ -0,0 +1,8 @@ +export 'chart_setting_button_with_background.dart'; +export 'colours_grid.dart'; +export 'custom_draggable_sheet.dart'; +export 'indicator_list_item.dart'; +export 'indicator_menu_button.dart'; +export 'mobile_tools_bottom_sheet_content.dart'; +export 'tools_controller.dart'; +export 'drawing_tools/drawing_tools.dart'; diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/tools_controller.dart b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/tools_controller.dart new file mode 100644 index 000000000..ecddaf873 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/mobile_tools_ui/tools_controller.dart @@ -0,0 +1,76 @@ +import 'package:deriv_chart/deriv_chart.dart'; +import 'package:deriv_mobile_chart_wrapper/src/constants.dart'; +import 'package:deriv_mobile_chart_wrapper/src/models/config_item_model.dart'; +import 'package:flutter/material.dart'; + +/// Controller for managing tools. +class ToolsController extends ChangeNotifier { + /// Creates a [ToolsController] with optional configuration for indicators and drawing tools. + ToolsController({ + this.indicatorsEnabled = true, + this.drawingToolsEnabled = true, + }); + + /// Whether indicators are enabled. + final bool indicatorsEnabled; + + /// Whether drawing tools are enabled. + final bool drawingToolsEnabled; + + /// Callback to show the indicators tools menu. + VoidCallback? onShowIndicatorsToolsMenu; + + /// Callback to show the drawing tools menu. + VoidCallback? onShowDrawingToolsMenu; + + /// Config items. + ConfigItemModel? _configs; + + /// Current config items. + ConfigItemModel? get configs => _configs; + + /// Drawing tools data. + DrawingTools? _drawingTools; + + /// Current drawing tools data. + DrawingTools? get drawingToolsData => _drawingTools; + + /// Updates the drawing tools data. + void updateDrawingToolsData(DrawingTools? drawingTools) { + _drawingTools = drawingTools; + notifyListeners(); + } + + /// Updates the config items. + void updateConfigs(ConfigItemModel configItemModel) { + _configs = configItemModel; + notifyListeners(); + } + + /// Shows the indicators tools menu. + void showIndicatorsToolsMenu() => onShowIndicatorsToolsMenu?.call(); + + /// Shows the drawing tools menu. + void showDrawingToolsMenu() => onShowDrawingToolsMenu?.call(); + + /// Returns the count of active drawing tools. + int get activeDrawingToolsCount => + _configs?.drawingToolConfigs + .where((DrawingToolConfig config) => + config.drawingData?.isDrawingFinished ?? false) + .length ?? + 0; + + /// Returns true if the drawing tool info bar should be shown. + bool get showDrawingToolInfoBar { + // Check if a drawing tool is selected. + final bool isDrawingToolSelected = + _drawingTools?.selectedDrawingTool != null; + + // Check if the selected drawing tool should show the info bar. + final bool isDrawingToolSupportsInfoBar = drawingToolTypesToShowInfoBar + .contains(_drawingTools?.selectedDrawingTool?.runtimeType); + + return isDrawingToolSelected && isDrawingToolSupportsInfoBar; + } +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/models/config_item_model.dart b/packages/deriv_mobile_chart_wrapper/lib/src/models/config_item_model.dart new file mode 100644 index 000000000..5cc169b48 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/models/config_item_model.dart @@ -0,0 +1,12 @@ +import 'package:deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper.dart'; + +class ConfigItemModel { + final List indicatorConfigs; + + final List drawingToolConfigs; + + ConfigItemModel({ + required this.indicatorConfigs, + required this.drawingToolConfigs, + }); +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/models/drawing_tool_info_bar_model.dart b/packages/deriv_mobile_chart_wrapper/lib/src/models/drawing_tool_info_bar_model.dart new file mode 100644 index 000000000..4dc568bc4 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/models/drawing_tool_info_bar_model.dart @@ -0,0 +1,12 @@ +/// Data class for holding info bar information. +class DrawingToolInfoBarModel { + DrawingToolInfoBarModel({ + required this.step, + required this.totalSteps, + required this.title, + }); + + final String step; + final int totalSteps; + final String title; +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/models/drawing_tool_item_model.dart b/packages/deriv_mobile_chart_wrapper/lib/src/models/drawing_tool_item_model.dart new file mode 100644 index 000000000..5aa9809bc --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/models/drawing_tool_item_model.dart @@ -0,0 +1,20 @@ +import 'package:deriv_chart/deriv_chart.dart'; + +/// Model class to keep the information of an drawing tool item. +class DrawingToolItemModel { + /// Initializes a drawing tool item model. + const DrawingToolItemModel({ + required this.title, + required this.icon, + required this.config, + }); + + /// The title. + final String title; + + /// The path to the SVG icon. + final String icon; + + /// The [config] property holds the drawing tool configuration. + final DrawingToolConfig config; +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/models/indicator_item_model.dart b/packages/deriv_mobile_chart_wrapper/lib/src/models/indicator_item_model.dart new file mode 100644 index 000000000..c83b06caf --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/models/indicator_item_model.dart @@ -0,0 +1,34 @@ +import 'package:deriv_chart/deriv_chart.dart'; + +import '../enums.dart'; + +/// Model class to keep the information of an indicator item. +class IndicatorItemModel { + /// Initializes an indicator item model. + const IndicatorItemModel({ + required this.title, + required this.subtitle, + required this.description, + required this.icon, + required this.category, + required this.config, + }); + + /// The title. + final String title; + + /// Short form of the title. + final String subtitle; + + /// The description of the indicator. + final String description; + + /// The path to the SVG icon. + final String icon; + + /// The category of indicator i.e., momentum, volatility, moving averages. + final IndicatorCategory category; + + /// The [config] property holds the indicator configuration. + final IndicatorConfig config; +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/models/indicator_tab_label.dart b/packages/deriv_mobile_chart_wrapper/lib/src/models/indicator_tab_label.dart new file mode 100644 index 000000000..9f09c9164 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/models/indicator_tab_label.dart @@ -0,0 +1,43 @@ +import 'package:deriv_mobile_chart_wrapper/src/enums.dart'; +import 'package:deriv_mobile_chart_wrapper/src/extensions.dart'; +import 'package:flutter/material.dart'; + +/// An enum to define label of indicators chip. +enum IndicatorTabLabel { + active, + all, + momentum, + volatility, + movingAverages; + + static String activeCount(int count) => 'Active ($count)'; + + String getTitle(BuildContext context) { + switch (this) { + case IndicatorTabLabel.active: + return context.mobileChartWrapperLocalizations.labelActive; + case IndicatorTabLabel.all: + return context.mobileChartWrapperLocalizations.labelAll; + case IndicatorTabLabel.momentum: + return context.mobileChartWrapperLocalizations.labelMomentum; + case IndicatorTabLabel.volatility: + return context.mobileChartWrapperLocalizations.labelVolatility; + case IndicatorTabLabel.movingAverages: + return context.mobileChartWrapperLocalizations.labelMovingAverages; + } + } + + /// Defines each tab label is equivalent to which [IndicatorCategory]. + IndicatorCategory? get toIndicatorCategory { + switch (this) { + case IndicatorTabLabel.momentum: + return IndicatorCategory.momentum; + case IndicatorTabLabel.volatility: + return IndicatorCategory.volatility; + case IndicatorTabLabel.movingAverages: + return IndicatorCategory.movingAverages; + default: + return null; + } + } +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/pages/base_setting_page.dart b/packages/deriv_mobile_chart_wrapper/lib/src/pages/base_setting_page.dart new file mode 100644 index 000000000..a68224108 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/pages/base_setting_page.dart @@ -0,0 +1,27 @@ +import 'package:deriv_chart/deriv_chart.dart'; +import 'package:flutter/material.dart'; + +/// The base class for the indicator setting page. +abstract class BaseIndicatorSettingPage + extends StatefulWidget { + /// Initializes the indicator setting page. + const BaseIndicatorSettingPage({ + super.key, + required this.initialConfig, + required this.onConfigUpdated, + required this.onApply, + this.onReset, + }); + + /// The initial configuration of the indicator. + final T initialConfig; + + /// The callback function to be called when the configuration is updated. + final void Function(T config) onConfigUpdated; + + /// The callback function to be called when the configuration is applied. + final void Function() onApply; + + /// The callback function to be called when the configuration is reset. + final void Function()? onReset; +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/pages/bb_settings_page.dart b/packages/deriv_mobile_chart_wrapper/lib/src/pages/bb_settings_page.dart new file mode 100644 index 000000000..345fb6617 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/pages/bb_settings_page.dart @@ -0,0 +1,330 @@ +import 'package:deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper.dart'; +import 'package:deriv_mobile_chart_wrapper/src/core_widgets/setting_page_action_buttons.dart'; +import 'package:deriv_mobile_chart_wrapper/src/extensions.dart'; +import 'package:deriv_mobile_chart_wrapper/src/helpers.dart'; +import 'package:deriv_mobile_chart_wrapper/src/pages/base_setting_page.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; + +class BollingerBandsSettingsPage + extends BaseIndicatorSettingPage { + const BollingerBandsSettingsPage({ + required super.initialConfig, + required super.onConfigUpdated, + required super.onApply, + super.onReset, + super.key, + }); + + @override + State createState() => + _BollingerBandsSettingsPageState(); +} + +class _BollingerBandsSettingsPageState + extends State { + late Map _sourceOptions; + late Map _movingAverageTypeOptions; + late BollingerBandsIndicatorConfig _indicatorConfig; + + final int _minimumValueSelectorInput = 1; + final int _maximumValueSelectorInput = 100; + + @override + void initState() { + super.initState(); + _indicatorConfig = widget.initialConfig; + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + _sourceOptions = getSourcesOptions(context); + _movingAverageTypeOptions = getMAOptions(context); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildSettingSection(), + SettingActionButtons( + onApply: widget.onApply, + onReset: () { + showResetIndicatorDialog(context, config: _indicatorConfig, + onResetPressed: () { + setState(() { + _indicatorConfig = const BollingerBandsIndicatorConfig(); + }); + widget.onConfigUpdated(_indicatorConfig); + widget.onReset?.call(); + }); + }, + ), + ], + ); + } + + Widget _buildSettingSection() => Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + _buildBandsSection(context), + const SizedBox( + height: ThemeProvider.margin24, + ), + _buildChannelFillSection(context), + const SizedBox( + height: ThemeProvider.margin24, + ), + _buildPeriodSection(context), + ], + ), + ), + ); + + _buildBandsSection(BuildContext context) => GlowingContainer( + borderRadius: ThemeProvider.borderRadius04, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: ThemeProvider.margin16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + vertical: ThemeProvider.margin16, + ), + child: ColorSelector( + title: context + .mobileChartWrapperLocalizations.labelBollingerBandsTop, + selectedColor: _indicatorConfig.upperLineStyle.color, + onColorChanged: (selectedColor) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + upperLineStyle: _indicatorConfig.upperLineStyle.copyWith( + color: selectedColor, + ), + ); + widget.onConfigUpdated(_indicatorConfig); + }); + }, + ), + ), + ColorSelector( + title: context + .mobileChartWrapperLocalizations.labelBollingerBandsMedian, + selectedColor: _indicatorConfig.middleLineStyle.color, + onColorChanged: (selectedColor) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + middleLineStyle: _indicatorConfig.middleLineStyle.copyWith( + color: selectedColor, + ), + ); + widget.onConfigUpdated(_indicatorConfig); + }); + }, + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: ThemeProvider.margin16, + ), + child: ColorSelector( + title: context + .mobileChartWrapperLocalizations.labelBollingerBandsBottom, + selectedColor: _indicatorConfig.lowerLineStyle.color, + onColorChanged: (selectedColor) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + lowerLineStyle: _indicatorConfig.lowerLineStyle.copyWith( + color: selectedColor, + ), + ); + widget.onConfigUpdated(_indicatorConfig); + }); + }, + ), + ), + ], + ), + )); + + GlowingExpansionPanelList _buildChannelFillSection(BuildContext context) => + GlowingExpansionPanelList( + expansionCallback: (index, isExpanded) { + setState(() { + _indicatorConfig = + _indicatorConfig.copyWith(showChannelFill: !isExpanded); + widget.onConfigUpdated(_indicatorConfig); + }); + }, + children: [ + GlowingExpansionPanel( + headerBuilder: (context, isExpanded) { + return Padding( + padding: const EdgeInsets.all(ThemeProvider.margin16), + child: Text( + context.mobileChartWrapperLocalizations.labelChannelFill), + ); + }, + isExpanded: _indicatorConfig.showChannelFill, + shouldGlow: false, + body: Padding( + padding: const EdgeInsets.symmetric( + vertical: ThemeProvider.margin16, + horizontal: ThemeProvider.margin16), + child: ColorSelector( + title: context.mobileChartWrapperLocalizations.labelFillColor, + selectedColor: _indicatorConfig.fillColor, + onColorChanged: (selectedColor) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + fillColor: selectedColor, + ); + widget.onConfigUpdated(_indicatorConfig); + }); + }, + ), + ), + backgroundColor: Colors.transparent, + ), + ], + ); + + _buildPeriodSection(BuildContext context) => GlowingContainer( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: ThemeProvider.margin16, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: ThemeProvider.margin16, + ), + ValueSelector( + value: _indicatorConfig.period.toDouble(), + backgroundColor: context.theme.colors.active, + onChange: (value) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + period: value?.floor(), + ); + widget.onConfigUpdated(_indicatorConfig); + }); + }, + title: context.mobileChartWrapperLocalizations.labelPeriod, + numberPadLabel: NumberPadLabel( + actionOK: context.mobileChartWrapperLocalizations.labelOK, + warnValueCantBeGreaterThan: (input, maxAmount, currency) => + context.mobileChartWrapperLocalizations + .warnEnterValueBetweenMinMax(_maximumValueSelectorInput, + _minimumValueSelectorInput), + warnValueCantBeLessThan: (input, minAmount, currency) => context + .mobileChartWrapperLocalizations + .warnEnterValueBetweenMinMax( + _maximumValueSelectorInput, _minimumValueSelectorInput), + warnValueShouldBeInRange: (input, minAmountClear, currentSymbol, + maxAmount) => + context.mobileChartWrapperLocalizations.warnRangeMinMax( + _maximumValueSelectorInput, _minimumValueSelectorInput), + ), + showMaximumSubtitle: true, + maximumSubtitle: + context.mobileChartWrapperLocalizations.labelMaxRange, + maximum: _maximumValueSelectorInput.toDouble(), + showMinimumSubtitle: true, + minimumSubtitle: + context.mobileChartWrapperLocalizations.labelMinRange, + minimum: _minimumValueSelectorInput.toDouble(), + ), + const SizedBox( + height: ThemeProvider.margin16, + ), + ValueSelector( + value: _indicatorConfig.standardDeviation, + backgroundColor: context.theme.colors.active, + onChange: (value) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + standardDeviation: value?.floorToDouble(), + ); + widget.onConfigUpdated(_indicatorConfig); + }); + }, + title: context + .mobileChartWrapperLocalizations.labelStandardDeviations, + numberPadLabel: NumberPadLabel( + actionOK: context.mobileChartWrapperLocalizations.labelOK, + warnValueCantBeGreaterThan: (input, maxAmount, currency) => + context.mobileChartWrapperLocalizations + .warnEnterValueBetweenMinMax(_maximumValueSelectorInput, + _minimumValueSelectorInput), + warnValueCantBeLessThan: (input, minAmount, currency) => context + .mobileChartWrapperLocalizations + .warnEnterValueBetweenMinMax( + _maximumValueSelectorInput, _minimumValueSelectorInput), + warnValueShouldBeInRange: (input, minAmountClear, currentSymbol, + maxAmount) => + context.mobileChartWrapperLocalizations.warnRangeMinMax( + _maximumValueSelectorInput, _minimumValueSelectorInput), + ), + showMaximumSubtitle: true, + maximumSubtitle: + context.mobileChartWrapperLocalizations.labelMaxRange, + maximum: 100, + showMinimumSubtitle: true, + minimumSubtitle: + context.mobileChartWrapperLocalizations.labelMinRange, + minimum: 1, + ), + const SizedBox( + height: ThemeProvider.margin16, + ), + OptionSelector( + label: context.mobileChartWrapperLocalizations.labelSource, + options: _sourceOptions.entries.map((e) => e.value).toList(), + selectedIndex: _sourceOptions.keys.toList().indexOf( + _indicatorConfig.fieldType, + ), + onOptionSelected: (index) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + fieldType: _sourceOptions.keys.toList()[index], + ); + widget.onConfigUpdated(_indicatorConfig); + }); + }, + ), + const SizedBox( + height: ThemeProvider.margin16, + ), + OptionSelector( + label: context + .mobileChartWrapperLocalizations.labelMovingAverageType, + options: _movingAverageTypeOptions.entries + .map((e) => e.value) + .toList(), + selectedIndex: _movingAverageTypeOptions.keys.toList().indexOf( + _indicatorConfig.movingAverageType, + ), + onOptionSelected: (index) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + movingAverageType: + _movingAverageTypeOptions.keys.toList()[index], + ); + widget.onConfigUpdated(_indicatorConfig); + }); + }, + ), + const SizedBox( + height: ThemeProvider.margin16, + ), + ], + ), + )); +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/pages/ma_settings_page.dart b/packages/deriv_mobile_chart_wrapper/lib/src/pages/ma_settings_page.dart new file mode 100644 index 000000000..e50dc40b5 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/pages/ma_settings_page.dart @@ -0,0 +1,245 @@ +import 'package:deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper.dart'; +import 'package:deriv_mobile_chart_wrapper/src/core_widgets/setting_page_action_buttons.dart'; +import 'package:deriv_mobile_chart_wrapper/src/extensions.dart'; +import 'package:deriv_mobile_chart_wrapper/src/pages/base_setting_page.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; + +import '../helpers.dart'; + +class MASettingsPage extends BaseIndicatorSettingPage { + const MASettingsPage({ + required super.initialConfig, + required super.onConfigUpdated, + required super.onApply, + super.onReset, + super.key, + }); + + @override + State createState() => _MASettingsPageState(); +} + +class _MASettingsPageState extends State { + late MAIndicatorConfig _indicatorConfig; + + @override + void initState() { + super.initState(); + _indicatorConfig = widget.initialConfig; + } + + final int _minimumPeriodValue = 1; + final int _maximumPeriodValue = 100; + + final int _minimumOffsetValue = 0; + final int _maximumOffsetValue = 100; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildSettingSection(), + SettingActionButtons( + onApply: widget.onApply, + onReset: () { + showResetIndicatorDialog(context, config: _indicatorConfig, + onResetPressed: () { + setState(() { + _indicatorConfig = const MAIndicatorConfig(); + }); + widget.onConfigUpdated(_indicatorConfig); + widget.onReset?.call(); + }); + }, + ), + ], + ); + } + + Widget _buildSettingSection() => Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + _buildMAColorSelectionSection(context), + const SizedBox( + height: ThemeProvider.margin24, + ), + _buildPeriodAndOffsetSection(), + const SizedBox( + height: ThemeProvider.margin24, + ), + _buildSourceAndTypeSection(), + ], + ), + ), + ); + + Widget _buildMAColorSelectionSection(BuildContext context) => Padding( + padding: const EdgeInsets.symmetric( + vertical: ThemeProvider.margin08, + ), + child: ColorSelector( + title: context.mobileChartWrapperLocalizations.labelMALine, + selectedColor: _indicatorConfig.lineStyle.color, + onColorChanged: (color) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + lineStyle: _indicatorConfig.lineStyle.copyWith( + color: color, + ), + ); + widget.onConfigUpdated(_indicatorConfig); + }); + }, + ), + ); + + Widget _buildPeriodAndOffsetSection() => GlowingContainer( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: ThemeProvider.margin16, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: ThemeProvider.margin16, + ), + ValueSelector( + value: _indicatorConfig.period.toDouble(), + backgroundColor: context.theme.colors.active, + onChange: (value) { + setState(() { + _indicatorConfig = + _indicatorConfig.copyWith(period: value?.floor()); + widget.onConfigUpdated(_indicatorConfig); + }); + }, + title: context.mobileChartWrapperLocalizations.labelPeriod, + numberPadLabel: NumberPadLabel( + actionOK: context.mobileChartWrapperLocalizations.labelOK, + warnValueCantBeGreaterThan: (input, maxAmount, currency) => + context.mobileChartWrapperLocalizations + .warnEnterValueBetweenMinMax( + _maximumPeriodValue, _minimumPeriodValue), + warnValueCantBeLessThan: (input, minAmount, currency) => + context.mobileChartWrapperLocalizations + .warnEnterValueBetweenMinMax( + _maximumPeriodValue, _minimumPeriodValue), + warnValueShouldBeInRange: (input, minAmountClear, + currentSymbol, maxAmount) => + context.mobileChartWrapperLocalizations.warnRangeMinMax( + _maximumPeriodValue, _minimumPeriodValue), + ), + showMaximumSubtitle: true, + maximumSubtitle: + context.mobileChartWrapperLocalizations.labelMaxRange, + maximum: _maximumPeriodValue.toDouble(), + showMinimumSubtitle: true, + minimumSubtitle: + context.mobileChartWrapperLocalizations.labelMinRange, + minimum: _minimumPeriodValue.toDouble(), + ), + const SizedBox( + height: ThemeProvider.margin16, + ), + ValueSelector( + value: _indicatorConfig.offset.toDouble(), + backgroundColor: context.theme.colors.active, + onChange: (value) { + setState(() { + _indicatorConfig = + _indicatorConfig.copyWith(offset: value?.floor()); + widget.onConfigUpdated(_indicatorConfig); + }); + }, + title: context.mobileChartWrapperLocalizations.labelOffset, + numberPadLabel: NumberPadLabel( + actionOK: context.mobileChartWrapperLocalizations.labelOK, + warnValueCantBeGreaterThan: (input, maxAmount, currency) => + context.mobileChartWrapperLocalizations + .warnEnterValueBetweenMinMax( + _maximumOffsetValue, _minimumOffsetValue), + warnValueCantBeLessThan: (input, minAmount, currency) => + context.mobileChartWrapperLocalizations + .warnEnterValueBetweenMinMax( + _maximumOffsetValue, _minimumOffsetValue), + warnValueShouldBeInRange: (input, minAmountClear, + currentSymbol, maxAmount) => + context.mobileChartWrapperLocalizations.warnRangeMinMax( + _maximumOffsetValue, _minimumOffsetValue), + ), + showMaximumSubtitle: true, + maximumSubtitle: + context.mobileChartWrapperLocalizations.labelMaxRange, + maximum: _maximumOffsetValue.toDouble(), + showMinimumSubtitle: true, + minimumSubtitle: + context.mobileChartWrapperLocalizations.labelMinRange, + minimum: _minimumOffsetValue.toDouble(), + ), + const SizedBox( + height: ThemeProvider.margin16, + ), + ], + ), + ), + ); + + Widget _buildSourceAndTypeSection() => GlowingContainer( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: ThemeProvider.margin16, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: ThemeProvider.margin16, + ), + OptionSelector( + label: context.mobileChartWrapperLocalizations.labelSource, + options: getSourcesOptions(context).values.toList(), + selectedIndex: getSourcesOptions(context) + .keys + .toList() + .indexOf(_indicatorConfig.fieldType), + onOptionSelected: (index) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + fieldType: + getSourcesOptions(context).keys.toList()[index]); + widget.onConfigUpdated(_indicatorConfig); + }); + }, + ), + const SizedBox( + height: ThemeProvider.margin16, + ), + OptionSelector( + label: context.mobileChartWrapperLocalizations.labelType, + options: getTypesOptions(context).values.toList(), + selectedIndex: getTypesOptions(context) + .keys + .toList() + .indexOf(_indicatorConfig.movingAverageType), + onOptionSelected: (index) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + movingAverageType: + getTypesOptions(context).keys.toList()[index]); + widget.onConfigUpdated(_indicatorConfig); + }); + }, + ), + const SizedBox( + height: ThemeProvider.margin16, + ), + ], + ), + ), + ); +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/pages/macd_settings_page.dart b/packages/deriv_mobile_chart_wrapper/lib/src/pages/macd_settings_page.dart new file mode 100644 index 000000000..bbab4cdf2 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/pages/macd_settings_page.dart @@ -0,0 +1,305 @@ +import 'package:deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper.dart'; +import 'package:deriv_mobile_chart_wrapper/src/core_widgets/setting_page_action_buttons.dart'; +import 'package:deriv_mobile_chart_wrapper/src/extensions.dart'; +import 'package:deriv_mobile_chart_wrapper/src/helpers.dart'; +import 'package:deriv_mobile_chart_wrapper/src/pages/base_setting_page.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; + +class MACDSettingsPage extends BaseIndicatorSettingPage { + const MACDSettingsPage({ + required super.initialConfig, + required super.onConfigUpdated, + required super.onApply, + super.onReset, + super.key, + }); + + @override + State createState() => _MACDSettingsPageState(); +} + +class _MACDSettingsPageState extends State { + late MACDIndicatorConfig _indicatorConfig; + + final int _minimumValueSelectorInput = 1; + final int _maximumValueSelectorInput = 100; + + @override + void initState() { + super.initState(); + _indicatorConfig = widget.initialConfig; + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildSettingSection(), + SettingActionButtons( + onApply: widget.onApply, + onReset: () { + showResetIndicatorDialog(context, config: _indicatorConfig, + onResetPressed: () { + setState(() { + _indicatorConfig = const MACDIndicatorConfig(); + }); + widget.onConfigUpdated(_indicatorConfig); + widget.onReset?.call(); + }); + }, + ), + ], + ); + } + + Widget _buildSettingSection() => Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + _buildMALineSection(context), + const SizedBox( + height: ThemeProvider.margin24, + ), + _buildSignalSection(context), + const SizedBox( + height: ThemeProvider.margin24, + ), + _buildBarsSection(context), + ], + ), + ), + ); + + _buildMALineSection(BuildContext context) => GlowingContainer( + borderRadius: ThemeProvider.borderRadius04, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: ThemeProvider.margin16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: ThemeProvider.margin16, + ), + ColorSelector( + title: context.mobileChartWrapperLocalizations.labelMACDLine, + selectedColor: _indicatorConfig.lineStyle.color, + onColorChanged: (selectedColor) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + lineStyle: _indicatorConfig.lineStyle.copyWith( + color: selectedColor, + ), + ); + widget.onConfigUpdated(_indicatorConfig); + }); + }, + ), + const SizedBox( + height: ThemeProvider.margin16, + ), + ValueSelector( + value: _indicatorConfig.fastMAPeriod.toDouble(), + backgroundColor: context.theme.colors.active, + onChange: (value) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + fastMAPeriod: value?.floor(), + ); + widget.onConfigUpdated(_indicatorConfig); + }); + }, + title: context.mobileChartWrapperLocalizations.labelFastMAPeriod, + numberPadLabel: NumberPadLabel( + actionOK: context.mobileChartWrapperLocalizations.labelOK, + warnValueCantBeGreaterThan: (input, maxAmount, currency) => + context.mobileChartWrapperLocalizations + .warnEnterValueBetweenMinMax(_maximumValueSelectorInput, + _minimumValueSelectorInput), + warnValueCantBeLessThan: (input, minAmount, currency) => context + .mobileChartWrapperLocalizations + .warnEnterValueBetweenMinMax( + _maximumValueSelectorInput, _minimumValueSelectorInput), + warnValueShouldBeInRange: (input, minAmountClear, currentSymbol, + maxAmount) => + context.mobileChartWrapperLocalizations.warnRangeMinMax( + _maximumValueSelectorInput, _minimumValueSelectorInput), + ), + showMaximumSubtitle: true, + maximumSubtitle: + context.mobileChartWrapperLocalizations.labelMaxRange, + maximum: _maximumValueSelectorInput.toDouble(), + showMinimumSubtitle: true, + minimumSubtitle: + context.mobileChartWrapperLocalizations.labelMinRange, + minimum: _minimumValueSelectorInput.toDouble(), + ), + const SizedBox( + height: ThemeProvider.margin16, + ), + ValueSelector( + value: _indicatorConfig.slowMAPeriod.toDouble(), + backgroundColor: context.theme.colors.active, + onChange: (value) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + slowMAPeriod: value?.floor(), + ); + widget.onConfigUpdated(_indicatorConfig); + }); + }, + title: context.mobileChartWrapperLocalizations.labelSlowMAPeriod, + numberPadLabel: NumberPadLabel( + actionOK: context.mobileChartWrapperLocalizations.labelOK, + warnValueCantBeGreaterThan: (input, maxAmount, currency) => + context.mobileChartWrapperLocalizations + .warnEnterValueBetweenMinMax(_maximumValueSelectorInput, + _minimumValueSelectorInput), + warnValueCantBeLessThan: (input, minAmount, currency) => context + .mobileChartWrapperLocalizations + .warnEnterValueBetweenMinMax( + _maximumValueSelectorInput, _minimumValueSelectorInput), + warnValueShouldBeInRange: (input, minAmountClear, currentSymbol, + maxAmount) => + context.mobileChartWrapperLocalizations.warnRangeMinMax( + _maximumValueSelectorInput, _minimumValueSelectorInput), + ), + showMaximumSubtitle: true, + maximumSubtitle: + context.mobileChartWrapperLocalizations.labelMaxRange, + maximum: _maximumValueSelectorInput.toDouble(), + showMinimumSubtitle: true, + minimumSubtitle: + context.mobileChartWrapperLocalizations.labelMinRange, + minimum: _minimumValueSelectorInput.toDouble(), + ), + const SizedBox( + height: ThemeProvider.margin16, + ), + ], + ), + )); + + _buildSignalSection(BuildContext context) => GlowingContainer( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: ThemeProvider.margin16, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: ThemeProvider.margin16, + ), + ColorSelector( + title: context.mobileChartWrapperLocalizations.labelSignalLine, + selectedColor: _indicatorConfig.signalLineStyle.color, + onColorChanged: (index) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + signalLineStyle: _indicatorConfig.signalLineStyle.copyWith( + color: index, + ), + ); + widget.onConfigUpdated(_indicatorConfig); + }); + }, + ), + const SizedBox( + height: ThemeProvider.margin16, + ), + ValueSelector( + value: _indicatorConfig.signalPeriod.toDouble(), + backgroundColor: context.theme.colors.active, + onChange: (value) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + signalPeriod: value?.floor(), + ); + widget.onConfigUpdated(_indicatorConfig); + }); + }, + title: context.mobileChartWrapperLocalizations.labelSignalPeriod, + numberPadLabel: NumberPadLabel( + actionOK: context.mobileChartWrapperLocalizations.labelOK, + warnValueCantBeGreaterThan: (input, maxAmount, currency) => + context.mobileChartWrapperLocalizations + .warnEnterValueBetweenMinMax(_maximumValueSelectorInput, + _minimumValueSelectorInput), + warnValueCantBeLessThan: (input, minAmount, currency) => context + .mobileChartWrapperLocalizations + .warnEnterValueBetweenMinMax( + _maximumValueSelectorInput, _minimumValueSelectorInput), + warnValueShouldBeInRange: (input, minAmountClear, currentSymbol, + maxAmount) => + context.mobileChartWrapperLocalizations.warnRangeMinMax( + _maximumValueSelectorInput, _minimumValueSelectorInput), + ), + showMaximumSubtitle: true, + maximumSubtitle: + context.mobileChartWrapperLocalizations.labelMaxRange, + maximum: _maximumValueSelectorInput.toDouble(), + showMinimumSubtitle: true, + minimumSubtitle: + context.mobileChartWrapperLocalizations.labelMinRange, + minimum: _minimumValueSelectorInput.toDouble(), + ), + const SizedBox( + height: ThemeProvider.margin16, + ), + ], + ), + )); + + _buildBarsSection(BuildContext context) => GlowingContainer( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: ThemeProvider.margin16, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: ThemeProvider.margin16, + ), + ColorSelector( + title: context.mobileChartWrapperLocalizations.labelIncreasingBar, + selectedColor: _indicatorConfig.barStyle.positiveColor, + onColorChanged: (selectedColor) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + barStyle: _indicatorConfig.barStyle.copyWith( + positiveColor: selectedColor, + negativeColor: _indicatorConfig.barStyle.negativeColor), + ); + }); + widget.onConfigUpdated(_indicatorConfig); + }, + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: ThemeProvider.margin16, + ), + child: ColorSelector( + title: + context.mobileChartWrapperLocalizations.labelDecreasingBar, + selectedColor: _indicatorConfig.barStyle.negativeColor, + onColorChanged: (color) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + barStyle: _indicatorConfig.barStyle.copyWith( + negativeColor: color, + positiveColor: _indicatorConfig.barStyle.positiveColor, + ), + ); + }); + widget.onConfigUpdated(_indicatorConfig); + }, + ), + ), + ], + ), + )); +} diff --git a/packages/deriv_mobile_chart_wrapper/lib/src/pages/rsi_setting_page.dart b/packages/deriv_mobile_chart_wrapper/lib/src/pages/rsi_setting_page.dart new file mode 100644 index 000000000..d3a082253 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/lib/src/pages/rsi_setting_page.dart @@ -0,0 +1,303 @@ +import 'package:deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper.dart'; +import 'package:deriv_mobile_chart_wrapper/src/core_widgets/setting_page_action_buttons.dart'; +import 'package:deriv_mobile_chart_wrapper/src/extensions.dart'; +import 'package:deriv_mobile_chart_wrapper/src/helpers.dart'; +import 'package:deriv_mobile_chart_wrapper/src/pages/base_setting_page.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/components/components.dart'; +import 'package:deriv_ui/widgets/widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +/// RSI indicator settings page. +class RSISettingPage extends BaseIndicatorSettingPage { + /// Initializes [RSISettingPage]. + const RSISettingPage({ + required super.initialConfig, + required super.onConfigUpdated, + required super.onApply, + super.onReset, + super.key, + }); + + @override + State createState() => _RSISettingPageState(); +} + +class _RSISettingPageState extends State { + late RSIIndicatorConfig _indicatorConfig; + + final int _minimumValueSelectorInput = 1; + final int _maximumValueSelectorInput = 100; + + @override + void initState() { + super.initState(); + _indicatorConfig = widget.initialConfig; + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + _buildSettingSection(), + SettingActionButtons( + onApply: widget.onApply, + onReset: () { + showResetIndicatorDialog( + context, + config: _indicatorConfig, + onResetPressed: () { + setState(() { + _indicatorConfig = const RSIIndicatorConfig(); + }); + widget.onConfigUpdated(_indicatorConfig); + widget.onReset?.call(); + }, + ); + }, + ), + ], + ); + } + + Widget _buildSettingSection() => Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + _buildRSILineSection(), + _createZonesSection(), + ], + ), + ), + ); + + Widget _buildRSILineSection() => GlowingContainer( + padding: const EdgeInsets.all(ThemeProvider.margin16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ColorSelector( + title: context.mobileChartWrapperLocalizations.labelRSILine, + selectedColor: _indicatorConfig.lineStyle.color, + onColorChanged: (Color color) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + lineStyle: _indicatorConfig.lineStyle.copyWith( + color: color, + ), + ); + }); + widget.onConfigUpdated(_indicatorConfig); + }, + ), + const SizedBox(height: ThemeProvider.margin16), + ValueSelector( + backgroundColor: context.theme.colors.active.withOpacity(0.4), + numberPadLabel: NumberPadLabel( + actionOK: context.mobileChartWrapperLocalizations.labelOK, + warnValueCantBeGreaterThan: (input, maxAmount, currency) => + context.mobileChartWrapperLocalizations + .warnEnterValueBetweenMinMax(_maximumValueSelectorInput, + _minimumValueSelectorInput), + warnValueCantBeLessThan: (input, minAmount, currency) => context + .mobileChartWrapperLocalizations + .warnEnterValueBetweenMinMax( + _maximumValueSelectorInput, _minimumValueSelectorInput), + warnValueShouldBeInRange: (input, minAmountClear, currentSymbol, + maxAmount) => + context.mobileChartWrapperLocalizations.warnRangeMinMax( + _maximumValueSelectorInput, _minimumValueSelectorInput), + ), + minimum: _minimumValueSelectorInput.toDouble(), + maximum: _maximumValueSelectorInput.toDouble(), + showMaximumSubtitle: true, + showMinimumSubtitle: true, + title: context.mobileChartWrapperLocalizations.labelPeriod, + onChange: (value) { + setState(() { + _indicatorConfig = + _indicatorConfig.copyWith(period: value?.floor()); + }); + widget.onConfigUpdated(_indicatorConfig); + }, + value: _indicatorConfig.period.toDouble(), + ), + const SizedBox(height: ThemeProvider.margin16), + OptionSelector( + label: context.mobileChartWrapperLocalizations.labelSource, + options: getSourcesOptions(context).values.toList(), + selectedIndex: getSourcesOptions(context) + .keys + .toList() + .indexOf(_indicatorConfig.fieldType), + onOptionSelected: (index) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + fieldType: + getSourcesOptions(context).keys.toList()[index]); + }); + widget.onConfigUpdated(_indicatorConfig); + }) + ], + ), + ); + + GlowingExpansionPanelList _createZonesSection() => GlowingExpansionPanelList( + expansionCallback: (index, isExpanded) { + setState(() { + _indicatorConfig = + _indicatorConfig.copyWith(showZones: !isExpanded); + }); + widget.onConfigUpdated(_indicatorConfig); + }, + children: [ + GlowingExpansionPanel( + headerBuilder: (context, isExpanded) { + return Padding( + padding: const EdgeInsets.all(ThemeProvider.margin16), + child: Text( + context.mobileChartWrapperLocalizations.labelShowZones), + ); + }, + isExpanded: _indicatorConfig.showZones, + shouldGlow: false, + body: Padding( + padding: const EdgeInsets.all(ThemeProvider.margin16), + child: Column( + children: [ + _buildOverboughtSection(), + const SizedBox(height: ThemeProvider.margin16), + _buildOverSoldSection(), + ], + ), + ), + backgroundColor: Colors.transparent, + ), + ], + ); + + Widget _buildOverboughtSection() => Column( + children: [ + ValueSelector( + title: context.mobileChartWrapperLocalizations.labelOverbought, + backgroundColor: context.theme.colors.active.withOpacity(0.4), + value: _indicatorConfig.oscillatorLinesConfig.overboughtValue, + minimum: _minimumValueSelectorInput.toDouble(), + maximum: _maximumValueSelectorInput.toDouble(), + formatter: NumberFormat(), + showMaximumSubtitle: true, + showMinimumSubtitle: true, + minimumSubtitle: + context.mobileChartWrapperLocalizations.labelMinSize, + maximumSubtitle: + context.mobileChartWrapperLocalizations.labelMaxSize, + onChange: (value) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + oscillatorLinesConfig: _indicatorConfig.oscillatorLinesConfig + .copyWith(overboughtValue: value), + ); + }); + widget.onConfigUpdated(_indicatorConfig); + }, + numberPadLabel: NumberPadLabel( + actionOK: context.mobileChartWrapperLocalizations.labelOK, + warnValueCantBeGreaterThan: (input, maxAmount, currency) => + context.mobileChartWrapperLocalizations + .warnEnterValueBetweenMinMax(_maximumValueSelectorInput, + _minimumValueSelectorInput), + warnValueCantBeLessThan: (input, minAmount, currency) => context + .mobileChartWrapperLocalizations + .warnEnterValueBetweenMinMax( + _maximumValueSelectorInput, _minimumValueSelectorInput), + warnValueShouldBeInRange: (input, minAmountClear, currentSymbol, + maxAmount) => + context.mobileChartWrapperLocalizations.warnRangeMinMax( + _maximumValueSelectorInput, _minimumValueSelectorInput), + ), + ), + const SizedBox(height: ThemeProvider.margin16), + ColorSelector( + title: context.mobileChartWrapperLocalizations.labelOverboughtLine, + selectedColor: + _indicatorConfig.oscillatorLinesConfig.overboughtStyle.color, + onColorChanged: (color) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + oscillatorLinesConfig: + _indicatorConfig.oscillatorLinesConfig.copyWith( + overboughtStyle: _indicatorConfig + .oscillatorLinesConfig.overboughtStyle + .copyWith(color: color), + ), + ); + }); + widget.onConfigUpdated(_indicatorConfig); + }, + ), + ], + ); + + Widget _buildOverSoldSection() => Column( + children: [ + ValueSelector( + title: context.mobileChartWrapperLocalizations.labelOversold, + backgroundColor: context.theme.colors.active.withOpacity(0.4), + value: _indicatorConfig.oscillatorLinesConfig.oversoldValue, + formatter: NumberFormat(), + minimum: 1, + maximum: 100, + showMaximumSubtitle: true, + showMinimumSubtitle: true, + minimumSubtitle: + context.mobileChartWrapperLocalizations.labelMinSize, + maximumSubtitle: + context.mobileChartWrapperLocalizations.labelMaxSize, + onChange: (value) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + oscillatorLinesConfig: _indicatorConfig.oscillatorLinesConfig + .copyWith(oversoldValue: value), + ); + }); + widget.onConfigUpdated(_indicatorConfig); + }, + numberPadLabel: NumberPadLabel( + actionOK: context.mobileChartWrapperLocalizations.labelOK, + warnValueCantBeGreaterThan: (input, maxAmount, currency) => + context.mobileChartWrapperLocalizations + .warnEnterValueBetweenMinMax(_maximumValueSelectorInput, + _minimumValueSelectorInput), + warnValueCantBeLessThan: (input, minAmount, currency) => context + .mobileChartWrapperLocalizations + .warnEnterValueBetweenMinMax( + _maximumValueSelectorInput, _minimumValueSelectorInput), + warnValueShouldBeInRange: (input, minAmountClear, currentSymbol, + maxAmount) => + context.mobileChartWrapperLocalizations.warnRangeMinMax( + _maximumValueSelectorInput, _minimumValueSelectorInput), + ), + ), + const SizedBox(height: ThemeProvider.margin16), + ColorSelector( + title: context.mobileChartWrapperLocalizations.labelOversoldLine, + selectedColor: + _indicatorConfig.oscillatorLinesConfig.oversoldStyle.color, + onColorChanged: (color) { + setState(() { + _indicatorConfig = _indicatorConfig.copyWith( + oscillatorLinesConfig: + _indicatorConfig.oscillatorLinesConfig.copyWith( + oversoldStyle: _indicatorConfig + .oscillatorLinesConfig.oversoldStyle + .copyWith(color: color), + ), + ); + }); + widget.onConfigUpdated(_indicatorConfig); + }, + ), + ], + ); +} diff --git a/packages/deriv_mobile_chart_wrapper/pubspec.yaml b/packages/deriv_mobile_chart_wrapper/pubspec.yaml new file mode 100644 index 000000000..33d186df8 --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/pubspec.yaml @@ -0,0 +1,91 @@ +name: deriv_mobile_chart_wrapper +description: A new Flutter package project. +publish_to: "none" # Remove this line if you wish to publish to pub.dev +version: 0.1.8+1 +homepage: + +environment: + sdk: ">=3.3.3 <4.0.0" + +dependencies: + flutter: + sdk: flutter + + deriv_chart: + git: + url: git@github.com:regentmarkets/flutter-chart.git + ref: v0.3.2+2 + + deriv_theme: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_theme + ref: deriv_theme-v2.8.0 + + deriv_localizations: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_localizations + ref: deriv_localizations-v1.7.2 + + deriv_ui: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_ui + ref: deriv_ui-v0.1.1 + + provider: ^6.0.5 + flutter_svg: ^2.0.9 + shared_preferences: ^2.1.0 + intl: ^0.19.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + mockito: ^5.4.2 + +# 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: + uses-material-design: true + + assets: + - assets/icons/ic_macd.svg + - assets/icons/ic_rsi.svg + - assets/icons/ic_bollinger_bands.svg + - assets/icons/ic_moving_average.svg + - assets/icons/ic_indicators_menu.svg + - assets/icons/ic_indicators_empty_state.svg + - assets/icons/ic_drawing_tool.svg + - assets/icons/ic_drawing_tools_empty_state.svg + - assets/icons/ic_line.svg + - assets/icons/ic_close_filled.svg +# +# 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 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/packages/deriv_mobile_chart_wrapper/test/mobile_chart_wrapper_test.dart b/packages/deriv_mobile_chart_wrapper/test/mobile_chart_wrapper_test.dart new file mode 100644 index 000000000..9998e9b7f --- /dev/null +++ b/packages/deriv_mobile_chart_wrapper/test/mobile_chart_wrapper_test.dart @@ -0,0 +1,94 @@ +import 'package:deriv_localizations/l10n/generated/deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper_localizations.dart'; +import 'package:deriv_mobile_chart_wrapper/deriv_mobile_chart_wrapper.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class MockToolsController extends Mock implements ToolsController { + @override + bool get indicatorsEnabled => true; + + @override + bool get drawingToolsEnabled => true; +} + +class MockAddOnsRepository extends Mock + implements AddOnsRepository {} + +void main() { + group('MobileChartWrapper Tests', () { + SharedPreferences.setMockInitialValues({ + 'default': dynamic, + }); + testWidgets('MobileChartWrapper initializes correctly', + (WidgetTester tester) async { + await tester.pumpWidget(_TestWidget(toolsController: ToolsController())); + + // Verify initial state + expect(find.byType(DerivChart), findsOneWidget); + }); + + testWidgets('ToolsController showIndicatorsToolsMenu callback is set', + (WidgetTester tester) async { + final mockToolsController = MockToolsController(); + await tester.pumpWidget( + _TestWidget(toolsController: mockToolsController), + ); + + // Verify callback is set + verify(mockToolsController.onShowIndicatorsToolsMenu = any).called(1); + }); + + testWidgets('MobileChartWrapper shows indicators sheet', + (WidgetTester tester) async { + final toolsController = ToolsController(); + + await tester.pumpWidget( + _TestWidget( + toolsController: toolsController, + ), + ); + + // Trigger the callback to show the sheet + toolsController.showIndicatorsToolsMenu(); + await tester.pump(); + + // Verify the bottom sheet is displayed + expect(find.byType(MobileToolsBottomSheetContent), findsOneWidget); + }); + + testWidgets('ToolsController showDrawingToolsMenu callback is set', + (WidgetTester tester) async { + final mockToolsController = MockToolsController(); + await tester.pumpWidget( + _TestWidget(toolsController: mockToolsController), + ); + + // Verify callback is set + verify(mockToolsController.onShowDrawingToolsMenu = any).called(1); + }); + }); +} + +class _TestWidget extends StatelessWidget { + const _TestWidget({required this.toolsController}); + + final ToolsController toolsController; + + @override + Widget build(BuildContext context) { + return MaterialApp( + localizationsDelegates: const >[ + DerivMobileChartWrapperLocalizations.delegate, + ], + home: Material( + child: MobileChartWrapper( + mainSeries: LineSeries([]), + granularity: 60, + toolsController: toolsController, + ), + ), + ); + } +} diff --git a/packages/deriv_numpad/CHANGELOG.md b/packages/deriv_numpad/CHANGELOG.md new file mode 100644 index 000000000..0092fedb0 --- /dev/null +++ b/packages/deriv_numpad/CHANGELOG.md @@ -0,0 +1,82 @@ +## 1.1.11 + + - Update a dependency to the latest release. + +## 1.1.10 + + - **FIX**(deriv_date_range_picker): iupgrade intl version ([#859](https://github.com/regentmarkets/flutter-deriv-packages/issues/859)). ([c500b57d](https://github.com/regentmarkets/flutter-deriv-packages/commit/c500b57d1558b10d7d60603d8de36af88db3dfb7)) + +## 1.1.9 + + - Update a dependency to the latest release. + +## 1.1.8 + + - Update a dependency to the latest release. + +## 1.1.7 + + - Update a dependency to the latest release. + +## 1.1.6 + + - Update a dependency to the latest release. + +## 1.1.5 + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + +## 1.1.4 + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + +## 1.1.3 + + - Update a dependency to the latest release. + +## 1.1.2 + + - Update a dependency to the latest release. + +## 1.1.1 + + - **REFACTOR**(deriv_numpad): pass validation result to numpad. ([1e44cd04](https://github.com/regentmarkets/flutter-deriv-packages/commit/1e44cd04437eee9db677ca12b0d3cfbc094ca613)) + - **FIX**(deriv_numpad): fix asset not found issue. ([1af28c77](https://github.com/regentmarkets/flutter-deriv-packages/commit/1af28c775adf1d516161c9f5d80c2958b53a0ef5)) + +## 1.1.0 + + - **REFACTOR**(deriv_numpad): UI polishing. ([f78c4668](https://github.com/regentmarkets/flutter-deriv-packages/commit/f78c46684d060d304913c945157d1847ec511c98)) + - **REFACTOR**(deriv_numpad): update spacing in textField UI. ([caa25dce](https://github.com/regentmarkets/flutter-deriv-packages/commit/caa25dce509356cf709393282253ed8b68b82ff3)) + - **REFACTOR**(deriv_numpad): add mock stream value in `example` app for exchange rate. ([0876e700](https://github.com/regentmarkets/flutter-deriv-packages/commit/0876e700d6abf672932b5a008234aa2227aef690)) + - **REFACTOR**(deriv_numpad): add `onClose` callback to exchanger. ([14ff5224](https://github.com/regentmarkets/flutter-deriv-packages/commit/14ff5224dfdd7b111b6fba0b2638cc3eb9c9aa60)) + - **REFACTOR**(deriv_numpad): update ui spacing. ([0890e6b8](https://github.com/regentmarkets/flutter-deriv-packages/commit/0890e6b819d74df89211135d2329511cd80b16db)) + - **REFACTOR**(deriv_numpad): add return type for currency exchange. ([0e24f5f7](https://github.com/regentmarkets/flutter-deriv-packages/commit/0e24f5f7ffdb7f26f1237d0e279a72a8eb242dea)) + - **REFACTOR**(deriv_numpad): Minor UI adjustments. ([3830bb3b](https://github.com/regentmarkets/flutter-deriv-packages/commit/3830bb3be7f9e49b268ab5322a0eb677dd42f5b8)) + - **REFACTOR**(deriv_numpad): update `example` app. ([7e6a70c8](https://github.com/regentmarkets/flutter-deriv-packages/commit/7e6a70c83632cab98f7a820120560ca2544fc9c0)) + - **REFACTOR**(deriv_numpad): update notifier instance fetching logic. ([036f7c65](https://github.com/regentmarkets/flutter-deriv-packages/commit/036f7c65e090ed27a7d8ba36e54eab837431129b)) + - **FIX**(deriv_numpad): handle exchange rate change when field is empty. ([9b0ff0b0](https://github.com/regentmarkets/flutter-deriv-packages/commit/9b0ff0b0079c4c90683bf4d868eed96287ed6fd1)) + - **FIX**(deriv_numpad): fix secondary currency type issue. ([6821bd2b](https://github.com/regentmarkets/flutter-deriv-packages/commit/6821bd2b9a0bd8a81bc1c16bc2a4e8fc30674842)) + - **FIX**(deriv_numpad): fix range validation for default case. ([4b403dff](https://github.com/regentmarkets/flutter-deriv-packages/commit/4b403dff5ffd92b28b8e69cf77e24a598ea0a6e5)) + - **FIX**(deriv_numpad): add title for exchanger. ([4347d4fb](https://github.com/regentmarkets/flutter-deriv-packages/commit/4347d4fb9be768b459873c1fa8be3b5e053f332b)) + - **FIX**(deriv_numpad): add dynamic validation from client code. ([f07b5d8a](https://github.com/regentmarkets/flutter-deriv-packages/commit/f07b5d8a54bb3e3996cbe9b272ed4a1d778aaf9f)) + - **FIX**(deriv_numpad): fix empty textfield while swapping. ([3fa9786c](https://github.com/regentmarkets/flutter-deriv-packages/commit/3fa9786c0df1b00bd68188d208e506d92b2606fb)) + - **FIX**(deriv_numpad): fallback sdk version to minimum `2.16.2`. ([e2162e16](https://github.com/regentmarkets/flutter-deriv-packages/commit/e2162e1658280003379e58e6b7edf31bd1d7619e)) + - **FEAT**(deriv_numpad): add factory constructor for currency exchange. ([5744974b](https://github.com/regentmarkets/flutter-deriv-packages/commit/5744974b35acf52ab99c078c45c1cbb69061b44d)) + - **FEAT**(deriv_numpad): add rate changing stream for currency exchange. ([ae9ddbea](https://github.com/regentmarkets/flutter-deriv-packages/commit/ae9ddbea0a1d74c8d31fe92ee5db8e2b3b499a47)) + - **FEAT**(deriv_numpad): update exchange logic for crypto exchange. ([ac808e69](https://github.com/regentmarkets/flutter-deriv-packages/commit/ac808e69ba4e109d21094a2a00493f531c7c28df)) + - **FEAT**(deriv_numpad): polish exchange logic when swapping currency. ([12bbb017](https://github.com/regentmarkets/flutter-deriv-packages/commit/12bbb017b68adf6eaec634e7ecf7f3d6e84299d1)) + - **FEAT**(deriv_numpad): add ability to switch between currency. ([894449f1](https://github.com/regentmarkets/flutter-deriv-packages/commit/894449f1996f455803bc020301df6eafea1d7ba1)) + - **FEAT**(deriv_numpad): add currency exchange UI. ([c47c2a85](https://github.com/regentmarkets/flutter-deriv-packages/commit/c47c2a85e5566cc2b867b847c2136189e5c4d856)) + - **FEAT**(deriv_numpad): add currency switcher ui. ([5ffd83c7](https://github.com/regentmarkets/flutter-deriv-packages/commit/5ffd83c721f19373eeb428bcf36597d575620442)) + - **DOCS**: minor adjustments in documentation. ([7ac660f6](https://github.com/regentmarkets/flutter-deriv-packages/commit/7ac660f6134b9eb89bdf0b5efb40a3dce58ae2ae)) + - **DOCS**(deriv_numpad): add documentation for public member. ([28266e8e](https://github.com/regentmarkets/flutter-deriv-packages/commit/28266e8edf65dfe34d4d9f3470508ab83d8855a0)) + - **DOCS**(deriv_numpad): add documentation for public variables. ([8cbb263e](https://github.com/regentmarkets/flutter-deriv-packages/commit/8cbb263eff0c39b1ba2ab7bad645546954a41146)) + +## 1.0.2 + + - **FIX**: change color to general. ([cac78e49](https://github.com/regentmarkets/flutter-deriv-packages/commit/cac78e49f1650fe1ba5f7698b97ce7a5adaa1308)) + +## 1.0.1 + + - Update a dependency to the latest release. + diff --git a/packages/deriv_numpad/assets/ic_currency_swap.svg b/packages/deriv_numpad/assets/ic_currency_swap.svg new file mode 100644 index 000000000..2433cd25e --- /dev/null +++ b/packages/deriv_numpad/assets/ic_currency_swap.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/deriv_numpad/example/lib/main.dart b/packages/deriv_numpad/example/lib/main.dart index 51cf08151..4939c6095 100644 --- a/packages/deriv_numpad/example/lib/main.dart +++ b/packages/deriv_numpad/example/lib/main.dart @@ -1,4 +1,8 @@ +import 'dart:async'; + import 'package:deriv_numpad/deriv_numberpad.dart'; +import 'package:deriv_numpad/number_pad/model/numpad_validation_text.dart'; +import 'package:deriv_theme/deriv_theme.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -24,7 +28,7 @@ class MyApp extends StatelessWidget { } class Homepage extends StatelessWidget { - const Homepage({super.key}); + const Homepage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -32,48 +36,148 @@ class Homepage extends StatelessWidget { appBar: AppBar( title: const Text('DerivNumberPad Example'), ), - body: Center( - child: ElevatedButton( - onPressed: () { - showModalBottomSheet( - isScrollControlled: true, - context: context, - backgroundColor: Colors.transparent, - builder: (BuildContext context) => NumberPad( - formatter: NumberFormat.decimalPattern(), - numberPadType: NumberPadWidgetType.singleInput, - firstInputTitle: 'Hello', - firstInputInitialValue: 25, - firstInputMinimumValue: 10, - firstInputMaximumValue: 100, - currency: 'USD', - onClose: ( - NumberPadWidgetType type, - NumberPadCloseType closeType, - NumberPadData result, - ) async { - print('Calling onClose() method.'); - }, - label: NumberPadLabel( - semanticNumberPadBottomSheetHandle: - 'semanticNumberPadBottomSheetHandle', - warnValueCantBeLessThan: (_, __, ___) => - 'warnValueCantBeLessThan', - warnValueCantBeGreaterThan: (_, __, ___) => - 'warnValueCantBeGreaterThan', - warnDoubleInputValueCantBeLessThan: (_, __, ___) => - 'warnDoubleInputValueCantBeLessThan', - warnDoubleInputValueCantBeGreaterThan: (_, __, ___) => - 'warnDoubleInputValueCantBeGreaterThan', - warnValueShouldBeInRange: (_, __, ___, ____) => - 'warnValueShouldBeInRange', - actionOK: 'OK', + body: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () { + final controller = + StreamController.broadcast(); + + final Timer timer = + Timer.periodic(const Duration(seconds: 5), (Timer timer) { + controller.add( + ExchangeRateModel( + baseCurrency: 'BTC', + targetCurrency: 'USD', + exchangeRate: (23 * timer.tick).toDouble(), + ), + ); + }); + + showModalBottomSheet( + isScrollControlled: true, + context: context, + backgroundColor: Colors.transparent, + builder: (BuildContext context) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + NumberPad.withCurrencyExchanger( + onClose: (type, closeType, result) {}, + title: 'Amount', + exchangeRatesStream: controller.stream, + initialExchangeRate: ExchangeRateModel( + baseCurrency: 'BTC', + targetCurrency: 'USD', + exchangeRate: 42800, + ), + primaryCurrency: CurrencyDetail(0.123, 'BTC'), + label: NumberPadLabel( + onValidate: (value) { + if (value.isEmpty) { + return NumpadValidationText( + enableActionButton: false, + text: RichText( + text: TextSpan( + text: 'Please enter an amount', + style: context.theme.textStyle( + textStyle: TextStyles.captionBold, + color: context.theme.colors.blue, + ), + ), + ), + ); + } + if (double.parse(value) > 50) { + return NumpadValidationText( + enableActionButton: false, + text: RichText( + textAlign: TextAlign.center, + text: TextSpan( + text: + 'You have reached the daily transfer limit of [50,000.00] USD between your USD Wallet and Deriv X.', + style: context.theme.textStyle( + textStyle: TextStyles.captionBold, + color: context.theme.colors.blue, + ), + ), + ), + ); + } else { + return NumpadValidationText( + text: RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'This looks good! Hehe', + style: context.theme.textStyle( + textStyle: TextStyles.captionBold, + color: context.theme.colors.disabled, + ), + ), + ], + ), + ), + ); + } + }, + actionOK: 'OK', + ), + ), + ], + ), + ); + }, + child: const Text('Numpad with currency exchange'), + ), + ElevatedButton( + onPressed: () { + showModalBottomSheet( + isScrollControlled: true, + context: context, + backgroundColor: Colors.transparent, + builder: (BuildContext context) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + NumberPad( + numberPadType: NumberPadWidgetType.singleInput, + currency: 'USD', + firstInputTitle: 'Amount', + formatter: NumberFormat.decimalPattern(), + firstInputInitialValue: 20, + firstInputMaximumValue: 100, + firstInputMinimumValue: 10, + label: NumberPadLabel( + semanticNumberPadBottomSheetHandle: + 'semanticNumberPadBottomSheetHandle', + warnValueCantBeLessThan: (Object input, Object minValue, + Object currency) => + '$input can\'t be less than $minValue $currency', + warnValueCantBeGreaterThan: (Object input, + Object maxValue, Object currency) => + '$input can\'t be greater than $maxValue $currency', + warnDoubleInputValueCantBeLessThan: (Object input, + Object minValue, Object currency) => + 'Invalid $input. $input can\'t be less than $minValue $currency', + warnDoubleInputValueCantBeGreaterThan: (Object input, + Object maxValue, Object currency) => + 'Invalid $input. $input can\'t be greater than $maxValue $currency', + warnValueShouldBeInRange: (Object input, + Object minValue, + Object currency, + Object maxValue) => + '$input between $minValue $currency and $maxValue $currency', + actionOK: 'OK', + ), + ), + ], ), - ), - ); - }, - child: const Text('Button 1'), - ), + ); + }, + child: const Text('Numpad'), + ), + ], ), ); } diff --git a/packages/deriv_numpad/example/pubspec.yaml b/packages/deriv_numpad/example/pubspec.yaml index 2dc549031..a18895600 100644 --- a/packages/deriv_numpad/example/pubspec.yaml +++ b/packages/deriv_numpad/example/pubspec.yaml @@ -6,25 +6,28 @@ publish_to: "none" version: 1.0.0+1 environment: - sdk: ">=2.16.2 <3.0.0" + sdk: ">=3.0.0 <4.0.0" dependencies: + cupertino_icons: ^1.0.2 + deriv_numpad: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_numpad + ref: deriv_numpad-v1.1.1 flutter: sdk: flutter - deriv_numpad: - path: ../ - - cupertino_icons: ^1.0.2 - intl: ^0.17.0 + intl: ^0.18.0 + deriv_theme: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_theme + ref: deriv_theme-v2.2.0 dev_dependencies: + flutter_lints: ^1.0.4 flutter_test: sdk: flutter - flutter_lints: ^1.0.4 - flutter: uses-material-design: true - - assets: - - assets/ic_handle.svg diff --git a/packages/deriv_numpad/example/test/widget_test.dart b/packages/deriv_numpad/example/test/widget_test.dart index 092d222f7..c4de88218 100644 --- a/packages/deriv_numpad/example/test/widget_test.dart +++ b/packages/deriv_numpad/example/test/widget_test.dart @@ -5,26 +5,14 @@ // 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:example/main.dart'; +import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget(const MyApp()); - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); + expect(find.text('Numpad'), findsOneWidget); }); } diff --git a/packages/deriv_numpad/lib/core/assets.dart b/packages/deriv_numpad/lib/core/assets.dart index 20c5d40ff..348e0fd45 100644 --- a/packages/deriv_numpad/lib/core/assets.dart +++ b/packages/deriv_numpad/lib/core/assets.dart @@ -4,3 +4,4 @@ // Icons const String handleIcon = 'assets/ic_handle.svg'; +const String swapIcon = 'assets/ic_currency_swap.svg'; diff --git a/packages/deriv_numpad/lib/core/widgets/currency_switcher.dart b/packages/deriv_numpad/lib/core/widgets/currency_switcher.dart new file mode 100644 index 000000000..30b088877 --- /dev/null +++ b/packages/deriv_numpad/lib/core/widgets/currency_switcher.dart @@ -0,0 +1,75 @@ +import 'package:deriv_numpad/core/assets.dart'; +import 'package:deriv_numpad/number_pad/model/currency_detail.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +/// A widget that reflects the exchanged currency of what every typed in currency textfield. +class CurrencySwitcher extends StatelessWidget { + /// Creates an instance of [CurrencySwitcher]. + const CurrencySwitcher({ + required this.currencyDetail, + this.onTap, + }); + + /// Data class for currency information to show in this widget. + final CurrencyDetail currencyDetail; + + /// call back when this widget is pressed. + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) => Container( + decoration: BoxDecoration( + border: Border.all( + color: context.theme.colors.general, + ), + borderRadius: BorderRadius.circular(ThemeProvider.borderRadius08), + ), + child: InkWell( + borderRadius: BorderRadius.circular(ThemeProvider.borderRadius08), + onTap: onTap, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: ThemeProvider.margin08, + vertical: ThemeProvider.margin04, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + currencyDetail.displayAmount.isEmpty + ? const SizedBox.shrink() + : _CurrencyText('${currencyDetail.displayAmount} '), + _CurrencyText(currencyDetail.displayCurrency), + const SizedBox(width: ThemeProvider.margin04), + SvgPicture.asset( + swapIcon, + package: 'deriv_numpad', + height: ThemeProvider.iconSize16, + width: ThemeProvider.iconSize16, + colorFilter: ColorFilter.mode( + context.theme.colors.prominent, + BlendMode.srcIn, + ), + ) + ], + ), + ), + ), + ); +} + +class _CurrencyText extends StatelessWidget { + const _CurrencyText(this.message); + + final String message; + + @override + Widget build(BuildContext context) => Text( + message, + style: context.theme.textStyle( + textStyle: TextStyles.body1, + color: context.theme.colors.general, + ), + ); +} diff --git a/packages/deriv_numpad/lib/core/widgets/custom_checkbox.dart b/packages/deriv_numpad/lib/core/widgets/custom_checkbox.dart index 4f0b4938a..5a7476e56 100644 --- a/packages/deriv_numpad/lib/core/widgets/custom_checkbox.dart +++ b/packages/deriv_numpad/lib/core/widgets/custom_checkbox.dart @@ -50,7 +50,7 @@ class CustomCheckbox extends StatelessWidget { textAlign: TextAlign.left, style: context.theme.textStyle( textStyle: TextStyles.body1, - color: context.theme.colors.lessProminent, + color: context.theme.colors.general, ), ), ), diff --git a/packages/deriv_numpad/lib/deriv_numberpad.dart b/packages/deriv_numpad/lib/deriv_numberpad.dart index bc2de47bc..0a3c2063f 100644 --- a/packages/deriv_numpad/lib/deriv_numberpad.dart +++ b/packages/deriv_numpad/lib/deriv_numberpad.dart @@ -2,3 +2,5 @@ export 'package:deriv_numpad/number_pad/number_pad.dart'; export 'package:deriv_numpad/number_pad/model/number_pad_label.dart'; export 'package:deriv_numpad/number_pad/model/number_pad_data.dart'; export 'package:deriv_numpad/core/enums.dart'; +export 'package:deriv_numpad/number_pad/model/currency_detail.dart'; +export 'package:deriv_numpad/number_pad/model/exchange_rate_model.dart'; diff --git a/packages/deriv_numpad/lib/number_pad/model/currency_detail.dart b/packages/deriv_numpad/lib/number_pad/model/currency_detail.dart new file mode 100644 index 000000000..cf8d367ce --- /dev/null +++ b/packages/deriv_numpad/lib/number_pad/model/currency_detail.dart @@ -0,0 +1,35 @@ +import 'package:deriv_numpad/core/helpers/custom_website_status_helper.dart'; + +/// This is a data class for storing the currency data like amount, currencyType. +class CurrencyDetail { + /// Data class for currency.Here, + /// + /// [amount] - amount in this currency. + /// + /// [currencyType] - type of currency like: 'USD','AUD',BTC,'ETH' and so on. + CurrencyDetail(this.amount, this.currencyType); + + /// Available fiat currency that Deriv supports. + static List fiatCurrencies = ['USD', 'AUD', 'EUR', 'GBP']; + + /// Amount passed for exchance. + final double amount; + + /// Type of the currency whether be it crypto or fiat. Eg: USD,BTC,ETH,AUD and so on. + final String currencyType; + + /// This will check if this instance is a fiat type currency or not. + bool get isFiat => fiatCurrencies.contains(currencyType); + + /// Amount that is used to display for user. + String get displayAmount { + if (amount == 0.0) { + return ''; + } + + return isFiat ? amount.toStringAsFixed(2) : amount.toStringAsFixed(8); + } + + /// This method is used to display currency for user. + String get displayCurrency => getStringWithMappedCurrencyName(currencyType); +} diff --git a/packages/deriv_numpad/lib/number_pad/model/exchange_rate_model.dart b/packages/deriv_numpad/lib/number_pad/model/exchange_rate_model.dart new file mode 100644 index 000000000..aa5785d14 --- /dev/null +++ b/packages/deriv_numpad/lib/number_pad/model/exchange_rate_model.dart @@ -0,0 +1,18 @@ +/// A data class for exchange rate. +class ExchangeRateModel { + /// A data class for exchange rate. + ExchangeRateModel({ + required this.baseCurrency, + required this.targetCurrency, + required this.exchangeRate, + }); + + /// This currency denotes the main currency we are exchanging from. + final String baseCurrency; + + /// This currency denotes the targetted currency we are exchanging to. + final String targetCurrency; + + /// This is the current exchange rate. The unit of this amount is [targetCurrency]. + final double exchangeRate; +} diff --git a/packages/deriv_numpad/lib/number_pad/model/number_pad_label.dart b/packages/deriv_numpad/lib/number_pad/model/number_pad_label.dart index 241353258..1b68d7227 100644 --- a/packages/deriv_numpad/lib/number_pad/model/number_pad_label.dart +++ b/packages/deriv_numpad/lib/number_pad/model/number_pad_label.dart @@ -1,18 +1,21 @@ +import 'package:deriv_numpad/number_pad/model/numpad_validation_text.dart'; + /// A class that will hold all the strings required by the NumberPad widget. class NumberPadLabel { /// Initialize [NumberPadLabel]. NumberPadLabel({ - required this.semanticNumberPadBottomSheetHandle, - required this.warnValueCantBeLessThan, - required this.warnValueCantBeGreaterThan, - required this.warnDoubleInputValueCantBeLessThan, - required this.warnDoubleInputValueCantBeGreaterThan, - required this.warnValueShouldBeInRange, required this.actionOK, + this.semanticNumberPadBottomSheetHandle, + this.warnValueCantBeLessThan, + this.warnValueCantBeGreaterThan, + this.warnDoubleInputValueCantBeLessThan, + this.warnDoubleInputValueCantBeGreaterThan, + this.warnValueShouldBeInRange, + this.onValidate, }); /// Semantic label for the handle svg at the top of the NumberPad bottom sheet. - final String semanticNumberPadBottomSheetHandle; + final String? semanticNumberPadBottomSheetHandle; /// The text on the 'OK' button that's in the AlertDialgue. final String actionOK; @@ -22,28 +25,28 @@ class NumberPadLabel { Object input, Object minAmount, Object currencySymbol, - ) warnValueCantBeLessThan; + )? warnValueCantBeLessThan; /// `{Input} can't be greater than {maxAmount} {currencySymbol}` final String Function( Object input, Object maxAmount, Object currencySymbol, - ) warnValueCantBeGreaterThan; + )? warnValueCantBeGreaterThan; /// `Invalid {Input}. {Input} can't be less than {minAmount} {currencySymbol}` final String Function( Object input, Object minAmount, Object currencySymbol, - ) warnDoubleInputValueCantBeLessThan; + )? warnDoubleInputValueCantBeLessThan; /// `Invalid {Input}. {Input} can't be greater than {maxAmount} {currencySymbol}` final String Function( Object input, Object maxAmount, Object currencySymbol, - ) warnDoubleInputValueCantBeGreaterThan; + )? warnDoubleInputValueCantBeGreaterThan; /// `{Input} between {minAmountClear} {currencySymbol} and {maxAmount} {currencySymbol}` final String Function( @@ -51,5 +54,10 @@ class NumberPadLabel { Object minAmountClear, Object currencySymbol, Object maxAmount, - ) warnValueShouldBeInRange; + )? warnValueShouldBeInRange; + + /// With this, client code can have their own validation logic. + /// Returning [NumpadValidationText] means this will show the message in the UI + /// Returning [null] means it won't show the message in the UI. + final NumpadValidationText? Function(String)? onValidate; } diff --git a/packages/deriv_numpad/lib/number_pad/model/numpad_validation_text.dart b/packages/deriv_numpad/lib/number_pad/model/numpad_validation_text.dart new file mode 100644 index 000000000..72d78a42b --- /dev/null +++ b/packages/deriv_numpad/lib/number_pad/model/numpad_validation_text.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +/// This class will hold UI information that numpad needs to know from client code. +class NumpadValidationText { + /// It creates a [NumpadValidationText] instance. + NumpadValidationText({ + this.text, + this.enableActionButton = true, + }); + + /// Text to display in Numpad label. + final RichText? text; + + /// This flag will check to enable action button or not. + final bool enableActionButton; +} diff --git a/packages/deriv_numpad/lib/number_pad/notifier/exchange_notifier.dart b/packages/deriv_numpad/lib/number_pad/notifier/exchange_notifier.dart new file mode 100644 index 000000000..8fc16d0f0 --- /dev/null +++ b/packages/deriv_numpad/lib/number_pad/notifier/exchange_notifier.dart @@ -0,0 +1,129 @@ +import 'package:deriv_numpad/number_pad/model/currency_detail.dart'; +import 'package:deriv_numpad/number_pad/model/exchange_rate_model.dart'; +import 'package:flutter/material.dart'; + +/// This class is used to provide [ExchangeController] to the widget tree. +class ExchangeNotifier extends InheritedNotifier { + /// This class is used to provide [ExchangeController] to the widget tree. + const ExchangeNotifier({ + required Widget child, + ExchangeController? notifier, + }) : super(child: child, notifier: notifier); + + /// Retrieve [ExchangeController] from the widget tree. + static ExchangeController? of(BuildContext context) => + context.dependOnInheritedWidgetOfExactType()?.notifier; +} + +/// This class contains the all the logic of exchange. +class ExchangeController extends ChangeNotifier { + /// It controls exchange rate conversion, switching between currencies and all the logic related to currency. + ExchangeController({ + required Stream rateSource, + required CurrencyDetail primaryCurrency, + required this.currencyFieldController, + required ExchangeRateModel initialExchangeRate, + }) : _rateSource = rateSource { + _exchangeRate = initialExchangeRate; + _primaryCurrency = primaryCurrency; + _secondaryCurrency = CurrencyDetail( + _getExchangedOutput(_primaryCurrency.amount), + _exchangeRate.targetCurrency, + ); + _listenForExchangeRateChange(); + } + + /// controller for active textfield. + TextEditingController currencyFieldController; + late bool _isSwapped = false; + late ExchangeRateModel _exchangeRate; + late CurrencyDetail _primaryCurrency; + late CurrencyDetail _secondaryCurrency; + final Stream _rateSource; + + /// Currently active currency in textField + CurrencyDetail get primaryCurrency => _primaryCurrency; + + /// Exchanged currency + CurrencyDetail get secondaryCurrency => _secondaryCurrency; + + Future _listenForExchangeRateChange() async { + _rateSource.listen((ExchangeRateModel rate) { + _exchangeRate = rate; + if (currencyFieldController.text.isEmpty) { + _secondaryCurrency = CurrencyDetail(0, secondaryCurrency.currencyType); + } else { + _secondaryCurrency = CurrencyDetail( + _getExchangedOutput(_primaryCurrency.amount), + secondaryCurrency.currencyType); + } + notifyListeners(); + }); + } + + /// This is called when an amount is changed in textField and immediately converts amount in secondary currency. + void onChangeCurrency(String newValue) { + if (currencyFieldController.text.isNotEmpty) { + _secondaryCurrency = _getUpdatedSecondaryCurrency(); + _primaryCurrency = _getUpdatedPrimaryCurrency(); + notifyListeners(); + } else { + _secondaryCurrency = CurrencyDetail(0, _secondaryCurrency.currencyType); + } + } + + /// This will interchange the currency, amount from textField to switcher and vice-versa. + void swap() { + _isSwapped = !_isSwapped; + + final double localPrimary = + double.tryParse(currencyFieldController.text) ?? 0.0; + + final String localPrimaryCurrency = _primaryCurrency.currencyType; + + _primaryCurrency = CurrencyDetail( + _secondaryCurrency.amount, + _secondaryCurrency.currencyType, + ); + _secondaryCurrency = CurrencyDetail(localPrimary, localPrimaryCurrency); + currencyFieldController + ..text = _primaryCurrency.displayAmount + ..selection = TextSelection.fromPosition( + TextPosition(offset: currencyFieldController.text.length), + ); + notifyListeners(); + } + + CurrencyDetail _getUpdatedSecondaryCurrency() { + final double value = double.parse( + _getExchangedOutput(double.parse(currencyFieldController.text)) + .toStringAsFixed(8)); + return CurrencyDetail(value, _secondaryCurrency.currencyType); + } + + CurrencyDetail _getUpdatedPrimaryCurrency() => CurrencyDetail( + double.parse(currencyFieldController.text), + _primaryCurrency.currencyType); + + double _getExchangedOutput(double amount) => _isSwapped + ? _exchangeRate.getInverseOfExchangeRate() * amount + : _exchangeRate.exchangeRate * amount; + + /// This will return the final output currency. + /// + /// The output amount is the same as the base of exchange rate(as this is + /// what user send by default in primaryCurrency when coming to numpad.) + double finalAmount() { + if (_exchangeRate.baseCurrency == primaryCurrency.currencyType) { + return primaryCurrency.amount; + } else { + return secondaryCurrency.amount; + } + } +} + +/// Inversion extension. +extension ExchangeRateInverse on ExchangeRateModel { + /// This will get the inverse of the actual exchange rate. + double getInverseOfExchangeRate() => 1 / exchangeRate; +} diff --git a/packages/deriv_numpad/lib/number_pad/number_pad.dart b/packages/deriv_numpad/lib/number_pad/number_pad.dart index b0ee8715e..d134aacc2 100644 --- a/packages/deriv_numpad/lib/number_pad/number_pad.dart +++ b/packages/deriv_numpad/lib/number_pad/number_pad.dart @@ -1,8 +1,13 @@ import 'dart:async'; import 'dart:ui' as ui; +import 'package:deriv_numpad/core/widgets/currency_switcher.dart'; import 'package:deriv_numpad/core/widgets/info_icon_button.dart'; +import 'package:deriv_numpad/number_pad/model/currency_detail.dart'; +import 'package:deriv_numpad/number_pad/model/exchange_rate_model.dart'; import 'package:deriv_numpad/number_pad/model/number_pad_label.dart'; +import 'package:deriv_numpad/number_pad/model/numpad_validation_text.dart'; +import 'package:deriv_numpad/number_pad/notifier/exchange_notifier.dart'; import 'package:deriv_theme/deriv_theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -110,6 +115,38 @@ class NumberPad extends StatefulWidget { Key? key, }) : super(key: key); + /// This is the instance of Numberpad which has currency exchanger within it. + /// + /// It will return [NumberPadData] which contains the latest currency amount. + factory NumberPad.withCurrencyExchanger({ + /// This information will be prefilled in the textField + required CurrencyDetail primaryCurrency, + + /// stream of exchange rate of the currencies. Here, + /// [base_currency] should be same as [currencyType] in `primaryCurrency`. + /// [target_currency] will be the currency shown in currency switcher. + /// When there is a new exchange rate in this stream, the value in currency switcher changes. + required Stream exchangeRatesStream, + + /// The initial exchange rate for the currency provided. + required ExchangeRateModel initialExchangeRate, + + /// Any validation for currencies. + required NumberPadLabel label, + + /// Calls when this widget is closed. + NumberPadCloseCallback? onClose, + String title = '', + }) => + _NumpadWithExchange( + label: label, + primaryCurrency: primaryCurrency, + exchangeRatesStream: exchangeRatesStream, + initialExchangeRate: initialExchangeRate, + title: title, + onClose: onClose, + ); + /// Sets the currency of the number pad /// /// The currency code that will be shown on the right side of the number pad text input area. @@ -212,6 +249,7 @@ class _NumberPadState extends State { late NumberFormat _formatter; TextEditingController? _firstInputController; TextEditingController? _secondInputController; + late ExchangeController _exchangeController; late FocusNode _firstInputFocusNode; FocusNode? _secondInputFocusNode; @@ -223,9 +261,7 @@ class _NumberPadState extends State { _formatter = widget.formatter; _firstInputController = TextEditingController(); _firstInputFocusNode = FocusNode(); - _firstInputController?.text = widget.firstInputInitialValue == null - ? noInput - : _formatter.format(widget.firstInputInitialValue); + _firstInputController?.text = _getFirstInputControllerText(); if (widget.numberPadType == NumberPadWidgetType.doubleInput) { _secondInputController = TextEditingController(); @@ -244,8 +280,18 @@ class _NumberPadState extends State { widget.onOpen?.call(); } + String _getFirstInputControllerText() { + if (widget.firstInputInitialValue != null) { + return _formatter.format(widget.firstInputInitialValue); + } else { + return noInput; + } + } + @override - Widget build(BuildContext context) => _NumberPadProvider( + Widget build(BuildContext ctx) => _buildWholeNumpad(context); + + Widget _buildWholeNumpad(BuildContext context) => _NumberPadProvider( type: widget.numberPadType, label: widget.label, formatter: _formatter, @@ -262,17 +308,18 @@ class _NumberPadState extends State { isSecondInputInRange: _isSecondInputInRange, isFirstInputInRange: _isFirstInputInRange, isAllInputsValid: _isAllInputsValid, - child: WillPopScope( - onWillPop: () async { - _applyInputs(NumberPadCloseType.clickOutsideView); - - return Future.value(true); - }, - child: ListView( - shrinkWrap: true, - physics: const ClampingScrollPhysics(), - children: [ - Container( + child: Builder( + builder: (BuildContext context) => WillPopScope( + onWillPop: () async { + _applyInputs(NumberPadCloseType.clickOutsideView); + + return Future.value(true); + }, + child: ListView( + shrinkWrap: true, + physics: const ClampingScrollPhysics(), + children: [ + Container( decoration: BoxDecoration( color: context.theme.colors.primary, borderRadius: const BorderRadius.only( @@ -283,11 +330,8 @@ class _NumberPadState extends State { child: Column( children: [ Container( - padding: const EdgeInsets.fromLTRB( - ThemeProvider.margin16, - 0, - ThemeProvider.margin16, - 0, + padding: const EdgeInsets.symmetric( + horizontal: ThemeProvider.margin16, ), decoration: BoxDecoration( color: context.theme.colors.secondary, @@ -305,8 +349,9 @@ class _NumberPadState extends State { const EdgeInsets.all(ThemeProvider.margin08), child: SvgPicture.asset( handleIcon, - width: 40, - height: 4, + package: 'deriv_numpad', + width: ThemeProvider.margin40, + height: ThemeProvider.margin04, semanticsLabel: widget .label.semanticNumberPadBottomSheetHandle, ), @@ -325,13 +370,18 @@ class _NumberPadState extends State { firstTitleValue: widget.firstInputTitle, secondTitleValue: widget.secondInputTitle, ), - _NumberPadMessage(message: _validateMessage()), + getCustomValidationText(context) != null + ? _NumberPadMessage( + messageText: getCustomValidationText(context)) + : _NumberPadMessage(message: _validateMessage()), _NumberPadKeypadWidget( onKeyPressed: _onKeyboardButtonPressed, ) ], - )), - ], + ), + ), + ], + ), ), ), ); @@ -343,6 +393,16 @@ class _NumberPadState extends State { ) { switch (text) { case applyValuesInput: + if (ExchangeNotifier.of(context) != null) { + final double returningValue = + ExchangeNotifier.of(context)!.finalAmount(); + final NumberPadData data = NumberPadData( + firstInputValue: returningValue, + ); + widget.onClose?.call(NumberPadWidgetType.doubleInput, + NumberPadCloseType.pressOK, data); + return Navigator.of(context).pop(data); + } _applyInputs(NumberPadCloseType.pressOK); break; case backspaceInput: @@ -354,6 +414,7 @@ class _NumberPadState extends State { _setNewAmount(controller, text); } } + ExchangeNotifier.of(context)?.onChangeCurrency(controller.text); } void _applyInputs(NumberPadCloseType closeStyle) { @@ -398,6 +459,18 @@ class _NumberPadState extends State { Navigator.pop(context, data); } + RichText? getCustomValidationText(BuildContext context) { + final NumpadValidationText? Function(String value)? onValidate = + _NumberPadProvider.of(context)?.label.onValidate; + if (onValidate == null) { + return null; + } else { + final RichText? richText = + onValidate(_firstInputController?.text ?? '')?.text; + return richText; + } + } + String _validateMessage() { String message = ''; @@ -411,17 +484,25 @@ class _NumberPadState extends State { lowerLimit: widget.firstInputMinimumValue ?? 0); if (!isFirstMoreThanMin) { - return message = widget.label.warnValueCantBeLessThan( - widget.firstInputTitle, - widget.firstInputMinimumValue ?? 0, - getStringWithMappedCurrencyName(_currency), - ); + final String Function( + Object input, Object minAmount, Object currencySymbol)? + callback = widget.label.warnValueCantBeLessThan; + return message = callback?.call( + widget.firstInputTitle, + widget.firstInputMinimumValue ?? 0, + getStringWithMappedCurrencyName(_currency), + ) ?? + ''; } else if (!isFirstLessThanMax) { - return message = widget.label.warnValueCantBeGreaterThan( - widget.firstInputTitle, - widget.firstInputMaximumValue, - getStringWithMappedCurrencyName(_currency), - ); + final String Function( + Object input, Object maxAmount, Object currencySymbol)? + callback = widget.label.warnValueCantBeGreaterThan; + return message = callback?.call( + widget.firstInputTitle, + widget.firstInputMaximumValue, + getStringWithMappedCurrencyName(_currency), + ) ?? + ''; } } if (widget.numberPadType == NumberPadWidgetType.doubleInput) { @@ -435,27 +516,41 @@ class _NumberPadState extends State { lowerLimit: widget.secondInputMinimumValue); if (!isSecondMoreThanMin) { - return message = widget.label.warnDoubleInputValueCantBeLessThan( - widget.secondInputTitle, - widget.secondInputMinimumValue, - getStringWithMappedCurrencyName(_currency), - ); + final String Function( + Object input, Object minAmount, Object currencySymbol)? + callback = widget.label.warnDoubleInputValueCantBeLessThan; + return message = callback?.call( + widget.secondInputTitle, + widget.secondInputMinimumValue, + getStringWithMappedCurrencyName(_currency), + ) ?? + ''; } else if (!isSecondLessThanMax) { - return message = widget.label.warnDoubleInputValueCantBeGreaterThan( - widget.secondInputTitle, - widget.secondInputMaximumValue!, - getStringWithMappedCurrencyName(_currency), - ); + final String Function( + Object input, Object maxAmount, Object currencySymbol)? + callback = widget.label.warnDoubleInputValueCantBeGreaterThan; + return message = callback?.call( + widget.secondInputTitle, + widget.secondInputMaximumValue!, + getStringWithMappedCurrencyName(_currency), + ) ?? + ''; } } } else if (widget.firstInputMinimumValue != null && widget.firstInputMaximumValue != double.maxFinite) { - return message = widget.label.warnValueShouldBeInRange( - widget.firstInputTitle, - widget.firstInputMinimumValue ?? 0, - getStringWithMappedCurrencyName(_currency), - widget.firstInputMaximumValue, - ); + final String Function( + Object input, + Object minAmountClear, + Object currencySymbol, + Object maxAmount)? callback = widget.label.warnValueShouldBeInRange; + return callback?.call( + widget.firstInputTitle, + widget.firstInputMinimumValue ?? 0, + getStringWithMappedCurrencyName(_currency), + widget.firstInputMaximumValue, + ) ?? + ''; } return message; } @@ -533,7 +628,7 @@ class _NumberPadState extends State { } bool _isFirstInputInRange() => - hasNoValue(_firstInputController?.text) || + !hasNoValue(_firstInputController?.text) && isBetweenLimits( value: _firstInputController?.text ?? '', upperLimit: widget.firstInputMaximumValue, @@ -548,3 +643,62 @@ class _NumberPadState extends State { lowerLimit: widget.secondInputMinimumValue, ); } + +class _NumpadWithExchange extends NumberPad { + _NumpadWithExchange({ + required NumberPadLabel label, + required this.primaryCurrency, + required this.exchangeRatesStream, + required this.initialExchangeRate, + required String title, + NumberPadCloseCallback? onClose, + }) : super( + numberPadType: NumberPadWidgetType.singleInput, + formatter: NumberFormat.decimalPattern()..maximumFractionDigits = 8, + label: label, + firstInputTitle: title, + onClose: onClose, + ); + + final CurrencyDetail primaryCurrency; + + final Stream exchangeRatesStream; + + final ExchangeRateModel initialExchangeRate; + + @override + State createState() => _NumberPadWithExchangeState( + exchangeRatesStream: exchangeRatesStream, + initialExchangeRate: initialExchangeRate, + primaryCurrency: primaryCurrency, + ); +} + +class _NumberPadWithExchangeState extends _NumberPadState { + _NumberPadWithExchangeState({ + required this.primaryCurrency, + required this.exchangeRatesStream, + required this.initialExchangeRate, + }); + final CurrencyDetail primaryCurrency; + final Stream exchangeRatesStream; + final ExchangeRateModel initialExchangeRate; + + @override + void initState() { + super.initState(); + super._firstInputController!.text = primaryCurrency.displayAmount; + _exchangeController = ExchangeController( + primaryCurrency: primaryCurrency, + currencyFieldController: super._firstInputController!, + rateSource: exchangeRatesStream, + initialExchangeRate: initialExchangeRate, + ); + } + + @override + Widget build(BuildContext context) => ExchangeNotifier( + child: super.build(context), + notifier: _exchangeController, + ); +} diff --git a/packages/deriv_numpad/lib/number_pad/number_pad_keypad.dart b/packages/deriv_numpad/lib/number_pad/number_pad_keypad.dart index e6eb29b8c..f4f8d520b 100644 --- a/packages/deriv_numpad/lib/number_pad/number_pad_keypad.dart +++ b/packages/deriv_numpad/lib/number_pad/number_pad_keypad.dart @@ -47,11 +47,27 @@ class _NumberPadKeypadWidgetState extends State<_NumberPadKeypadWidget> { final int index = columnCounter + counter; return _NumberPadKey( index: index, - ignoring: _NumberPadProvider.of(context)!.isAllInputsValid(), + ignoring: _NumberPadProvider.of(context)!.isAllInputsValid() && + _noErrorExist(), onPressed: widget.onKeyPressed, actionOK: _NumberPadProvider.of(context)!.label.actionOK, ); }), ), ); + + bool _noErrorExist() { + final _NumberPadProvider provider = _NumberPadProvider.of(context)!; + final NumpadValidationText? Function(String value)? validate = + provider.label.onValidate; + if (validate == null) { + return true; + } else { + final NumpadValidationText? result = + validate(provider.firstInputController!.text); + final bool _noError = result == null || result.enableActionButton; + + return _noError; + } + } } diff --git a/packages/deriv_numpad/lib/number_pad/number_pad_message.dart b/packages/deriv_numpad/lib/number_pad/number_pad_message.dart index bebcd5e8e..8fe2f5dca 100644 --- a/packages/deriv_numpad/lib/number_pad/number_pad_message.dart +++ b/packages/deriv_numpad/lib/number_pad/number_pad_message.dart @@ -1,9 +1,13 @@ part of 'number_pad.dart'; class _NumberPadMessage extends StatelessWidget { - const _NumberPadMessage({required this.message}); + const _NumberPadMessage({ + this.message, + this.messageText, + }) : assert(message != null || messageText != null); - final String message; + final RichText? messageText; + final String? message; @override Widget build(BuildContext context) { @@ -20,18 +24,20 @@ class _NumberPadMessage extends StatelessWidget { left: ThemeProvider.margin16, right: ThemeProvider.margin16, ), - child: Text( - message, - style: numberPadProvider!.isAllInputsValid() - ? context.theme.textStyle( - textStyle: TextStyles.caption, - color: context.theme.colors.disabled, - ) - : context.theme.textStyle( - textStyle: TextStyles.captionBold, - color: context.theme.colors.coral, - ), - ), + child: messageText != null + ? messageText! + : Text( + message!, + style: numberPadProvider!.isAllInputsValid() + ? context.theme.textStyle( + textStyle: TextStyles.caption, + color: context.theme.colors.disabled, + ) + : context.theme.textStyle( + textStyle: TextStyles.captionBold, + color: context.theme.colors.coral, + ), + ), ), ); } diff --git a/packages/deriv_numpad/lib/number_pad/number_pad_single_textfield.dart b/packages/deriv_numpad/lib/number_pad/number_pad_single_textfield.dart index e49996cab..2110507b2 100644 --- a/packages/deriv_numpad/lib/number_pad/number_pad_single_textfield.dart +++ b/packages/deriv_numpad/lib/number_pad/number_pad_single_textfield.dart @@ -30,13 +30,15 @@ class _NumberPadSingleTextField extends StatelessWidget { const double margin = ThemeProvider.margin24; final _NumberPadProvider? numPadProvider = _NumberPadProvider.of(context); + final ExchangeController? exchangeProvider = ExchangeNotifier.of(context); final Size labelSize = getTextSize( - numPadProvider?.currency ?? '', + exchangeProvider?.primaryCurrency.currencyType ?? + numPadProvider?.currency ?? + '', TextStyles.headlineNormal, context, ); - return Column( children: [ title.isEmpty @@ -46,6 +48,16 @@ class _NumberPadSingleTextField extends StatelessWidget { dialogDescription: dialogDescription, leading: leading, ), + exchangeProvider != null + ? Padding( + padding: const EdgeInsets.only(top: ThemeProvider.margin16), + child: CurrencySwitcher( + currencyDetail: + ExchangeNotifier.of(context)!.secondaryCurrency, + onTap: () => ExchangeNotifier.of(context)!.swap(), + ), + ) + : const SizedBox.shrink(), Row( children: [ Expanded( @@ -60,37 +72,30 @@ class _NumberPadSingleTextField extends StatelessWidget { if (textSize.width + padding > constraints.maxWidth) { padding = margin; } + return numPadProvider != null ? Padding( - padding: EdgeInsets.only( - left: padding, - ), + padding: EdgeInsets.only(left: padding), child: _NumberPadTextField( controller: numPadProvider.firstInputController, textStyle: TextStyles.display1, focusNode: numPadProvider.firstInputFocusNode, inputValidator: numPadProvider.isFirstInputInRange, textAlign: TextAlign.center, + suffixIcon: Text( + exchangeProvider?.primaryCurrency.currencyType ?? + numPadProvider.currency, + style: context.theme.textStyle( + textStyle: TextStyles.headlineNormal, + color: context.theme.colors.disabled, + ), + ), ), ) : const SizedBox.shrink(); }, ), ), - numPadProvider != null - ? Padding( - padding: const EdgeInsets.only( - right: margin, - ), - child: Text( - getStringWithMappedCurrencyName(numPadProvider.currency), - style: context.theme.textStyle( - textStyle: TextStyles.headlineNormal, - color: context.theme.colors.disabled, - ), - ), - ) - : const SizedBox.shrink(), ], ), ], diff --git a/packages/deriv_numpad/lib/number_pad/number_pad_text_field.dart b/packages/deriv_numpad/lib/number_pad/number_pad_text_field.dart index fe54a1fce..2bf0e2233 100644 --- a/packages/deriv_numpad/lib/number_pad/number_pad_text_field.dart +++ b/packages/deriv_numpad/lib/number_pad/number_pad_text_field.dart @@ -8,8 +8,10 @@ class _NumberPadTextField extends StatefulWidget { this.focusNode, this.textAlign, this.label = '', + this.suffixIcon, }); + final Widget? suffixIcon; final TextEditingController? controller; final TextStyle textStyle; final FocusNode? focusNode; @@ -44,8 +46,24 @@ class _NumberPadTextFieldState extends State<_NumberPadTextField> { color: context.theme.colors.coral, ), decoration: widget.label.isEmpty - ? const InputDecoration(border: InputBorder.none) + ? InputDecoration( + border: InputBorder.none, + suffixIcon: widget.suffixIcon != null + ? Padding( + padding: const EdgeInsets.only( + right: ThemeProvider.margin16), + child: widget.suffixIcon, + ) + : null, + ) : InputDecoration( + suffixIcon: widget.suffixIcon != null + ? Padding( + padding: const EdgeInsets.only( + right: ThemeProvider.margin16), + child: widget.suffixIcon, + ) + : null, border: const OutlineInputBorder(), focusedBorder: OutlineInputBorder( borderSide: BorderSide( @@ -61,6 +79,7 @@ class _NumberPadTextFieldState extends State<_NumberPadTextField> { textAlign: widget.textAlign ?? TextAlign.start, readOnly: true, showCursor: true, + cursorColor: context.theme.colors.prominent, enableInteractiveSelection: false, autofocus: true, ); diff --git a/packages/deriv_numpad/pubspec.yaml b/packages/deriv_numpad/pubspec.yaml index b16596119..dd31bfeb4 100644 --- a/packages/deriv_numpad/pubspec.yaml +++ b/packages/deriv_numpad/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_numpad description: A new Flutter project. -version: 1.0.0+1 +version: 1.1.11 publish_to: "none" environment: @@ -13,10 +13,10 @@ dependencies: git: url: git@github.com:regentmarkets/flutter-deriv-packages.git path: packages/deriv_theme - ref: dev + ref: deriv_theme-v2.8.0 - flutter_svg: ^1.0.3 - intl: ^0.17.0 + flutter_svg: ^2.0.7 + intl: ^0.19.0 decimal: ^2.3.2 cupertino_icons: ^1.0.2 @@ -30,6 +30,6 @@ dev_dependencies: flutter: uses-material-design: true - assets: - assets/ic_handle.svg + - assets/ic_currency_swap.svg diff --git a/packages/deriv_numpad/test/number_pad_test.dart b/packages/deriv_numpad/test/number_pad_test.dart index 07ca3327b..f719c0cb8 100644 --- a/packages/deriv_numpad/test/number_pad_test.dart +++ b/packages/deriv_numpad/test/number_pad_test.dart @@ -1,3 +1,6 @@ +import 'dart:async'; + +import 'package:deriv_numpad/core/widgets/currency_switcher.dart'; import 'package:deriv_numpad/deriv_numberpad.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -464,6 +467,182 @@ void main() { expect(data.secondInputValue, isNull); }); }); + + group('NumberPad with exchange currency', () { + late ExchangeRateModel mockExchangeRate; + late Stream mockExchangeStream; + setUpAll(() { + mockExchangeRate = ExchangeRateModel( + baseCurrency: 'BTC', + targetCurrency: 'USD', + exchangeRate: 42800, + ); + mockExchangeStream = + StreamController.broadcast().stream; + }); + + testWidgets('should render exchange switcher, numpad, and title', + (WidgetTester widgetTester) async { + await widgetTester.pumpWidget( + TestWidget( + NumberPad.withCurrencyExchanger( + primaryCurrency: CurrencyDetail(0.123, 'BTC'), + exchangeRatesStream: mockExchangeStream, + initialExchangeRate: mockExchangeRate, + title: 'Amount', + label: NumberPadLabel( + actionOK: 'OK', + ), + ), + ), + ); + + expect(find.text('Amount'), findsOneWidget); + expect(find.byType(CurrencySwitcher), findsOneWidget); + expect(find.byType(TextButton), findsNWidgets(13)); + expect(find.byType(TextField), findsOneWidget); + }); + + testWidgets( + 'should populate the textfield when there is an amount in primaryCurrency. ', + (WidgetTester widgetTester) async { + final CurrencyDetail mockPrimaryCurrency = CurrencyDetail(0.123, 'BTC'); + await widgetTester.pumpWidget( + TestWidget( + NumberPad.withCurrencyExchanger( + primaryCurrency: mockPrimaryCurrency, + exchangeRatesStream: mockExchangeStream, + initialExchangeRate: mockExchangeRate, + title: 'Amount', + label: NumberPadLabel( + actionOK: 'OK', + ), + ), + ), + ); + + final TextField textField = + find.byType(TextField).evaluate().first.widget as TextField; + + expect(textField.controller!.text, mockPrimaryCurrency.displayAmount); + + expect(find.text('5264.40 '), findsOneWidget); + }); + + testWidgets( + 'should render the exchanged amount in USD in UI when user inputs amount in BTC from the keypad.', + (WidgetTester widgetTester) async { + final CurrencyDetail mockPrimaryCurrency = CurrencyDetail(0, 'BTC'); + await widgetTester.pumpWidget( + TestWidget( + NumberPad.withCurrencyExchanger( + primaryCurrency: mockPrimaryCurrency, + exchangeRatesStream: mockExchangeStream, + initialExchangeRate: mockExchangeRate, + title: 'Amount', + label: NumberPadLabel( + actionOK: 'OK', + ), + ), + ), + ); + + // Inputted 0 from the keypad + await widgetTester.tap(find.text('0')); + await widgetTester.pumpAndSettle(); + + // Inputted . from the keypad + await widgetTester.tap(find.text('.')); + await widgetTester.pumpAndSettle(); + + // Inputted 3 from the keypad + await widgetTester.tap(find.text('3')); + await widgetTester.pumpAndSettle(); + + // Inputted 5 from the keypad + await widgetTester.tap(find.text('5')); + await widgetTester.pumpAndSettle(); + + // Inputted 5 from the keypad + await widgetTester.tap(find.text('5')); + await widgetTester.pumpAndSettle(); + + expect(find.text('15194.00 '), findsOneWidget); + }); + + testWidgets( + 'should switch amount from currency switcher to textfield and vice versa when currency Switcher is pressed.', + (WidgetTester widgetTester) async { + final CurrencyDetail mockPrimaryCurrency = CurrencyDetail(0.123, 'BTC'); + + await widgetTester.pumpWidget( + TestWidget( + NumberPad.withCurrencyExchanger( + primaryCurrency: mockPrimaryCurrency, + exchangeRatesStream: mockExchangeStream, + initialExchangeRate: mockExchangeRate, + title: 'Amount', + label: NumberPadLabel( + actionOK: 'OK', + ), + ), + ), + ); + + await widgetTester.tap(find.text('5264.40 ')); + await widgetTester.pumpAndSettle(); + + final TextField textField = + find.byType(TextField).evaluate().first.widget as TextField; + + expect(textField.controller!.text, '5264.40'); + expect(find.text('0.12300000 '), findsOneWidget); + }, + ); + + testWidgets( + 'should render the exchanged amount in BTC in UI when user inputs amount in USD from the keypad after it is switched.', + (WidgetTester widgetTester) async { + final CurrencyDetail mockPrimaryCurrency = CurrencyDetail(0, 'BTC'); + + await widgetTester.pumpWidget( + TestWidget( + NumberPad.withCurrencyExchanger( + primaryCurrency: mockPrimaryCurrency, + exchangeRatesStream: mockExchangeStream, + initialExchangeRate: mockExchangeRate, + title: 'Amount', + label: NumberPadLabel( + actionOK: 'OK', + ), + ), + ), + ); + + // switch to USD amount. + await widgetTester.tap(find.text('USD')); + await widgetTester.pumpAndSettle(); + + // Inputted 1 from the keypad + await widgetTester.tap(find.text('1')); + await widgetTester.pumpAndSettle(); + + // Inputted 8 from the keypad + await widgetTester.tap(find.text('8')); + await widgetTester.pumpAndSettle(); + + // Inputted 0 from the keypad + await widgetTester.tap(find.text('0')); + await widgetTester.pumpAndSettle(); + + // Inputted 0 from the keypad + await widgetTester.tap(find.text('0')); + await widgetTester.pumpAndSettle(); + + expect(find.text('0.04205607 '), findsOneWidget); + }, + ); + }); } class TestWidget extends StatelessWidget { diff --git a/packages/deriv_passkeys/.gitignore b/packages/deriv_passkeys/.gitignore new file mode 100644 index 000000000..96486fd93 --- /dev/null +++ b/packages/deriv_passkeys/.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/packages/deriv_passkeys/.metadata b/packages/deriv_passkeys/.metadata new file mode 100644 index 000000000..7e5bb4c81 --- /dev/null +++ b/packages/deriv_passkeys/.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: "ead455963c12b453cdb2358cad34969c76daf180" + channel: "stable" + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: ead455963c12b453cdb2358cad34969c76daf180 + base_revision: ead455963c12b453cdb2358cad34969c76daf180 + - platform: android + create_revision: ead455963c12b453cdb2358cad34969c76daf180 + base_revision: ead455963c12b453cdb2358cad34969c76daf180 + - platform: ios + create_revision: ead455963c12b453cdb2358cad34969c76daf180 + base_revision: ead455963c12b453cdb2358cad34969c76daf180 + + # 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/packages/deriv_passkeys/CHANGELOG.md b/packages/deriv_passkeys/CHANGELOG.md new file mode 100644 index 000000000..c37695681 --- /dev/null +++ b/packages/deriv_passkeys/CHANGELOG.md @@ -0,0 +1,273 @@ +## 0.0.5+11 + + - Update a dependency to the latest release. + +## 0.0.5+10 + + - Update a dependency to the latest release. + +## 0.0.5+9 + + - Update a dependency to the latest release. + +## 0.0.5+8 + + - Update a dependency to the latest release. + +## 0.0.5+7 + + - Update a dependency to the latest release. + +## 0.0.5+6 + + - Update a dependency to the latest release. + +## 0.0.5+5 + + - **REFACTOR**(deriv_passkeys): update deriv_api dep ([#856](https://github.com/regentmarkets/flutter-deriv-packages/issues/856)). ([ede54c8c](https://github.com/regentmarkets/flutter-deriv-packages/commit/ede54c8ce7f371189b460fae5f0e3e95f4fb7817)) + +## 0.0.5+4 + + - Update a dependency to the latest release. + +## 0.0.5+3 + + - Update a dependency to the latest release. + +## 0.0.5+2 + + - Update a dependency to the latest release. + +## 0.0.5+1 + + - **FIX**: Updating Flutter Deriv API version to latest ([#843](https://github.com/regentmarkets/flutter-deriv-packages/issues/843)). ([db461495](https://github.com/regentmarkets/flutter-deriv-packages/commit/db46149557a9a93eb86a6691a8c7831bf709e8f1)) + +## 0.0.5 + + - **FEAT**(deriv_auth): [DERG-1396] akhil/1396/multi_user_level_authentication_poc_master ([#574](https://github.com/regentmarkets/flutter-deriv-packages/issues/574)). ([97ac8004](https://github.com/regentmarkets/flutter-deriv-packages/commit/97ac8004370762ed38ed0608e64699a020406b8e)) + +## 0.0.4+6 + + - **REFACTOR**(deriv_passkeys): Add call back to call after pass key flow finished ([#831](https://github.com/regentmarkets/flutter-deriv-packages/issues/831)). ([444e963e](https://github.com/regentmarkets/flutter-deriv-packages/commit/444e963e949334ae81b170c73c1a35afad7a1e0e)) + +## 0.0.4+5 + + - Update a dependency to the latest release. + +## 0.0.4+4 + + - Update a dependency to the latest release. + +## 0.0.4+3 + + - Update a dependency to the latest release. + +## 0.0.4+2 + + - Update a dependency to the latest release. + +## 0.0.4+1 + + - Update a dependency to the latest release. + +## 0.0.4 + + - **FEAT**(deriv_mobile_chart_wrapper): wire up indicators ([#760](https://github.com/regentmarkets/flutter-deriv-packages/issues/760)). ([4ff1747b](https://github.com/regentmarkets/flutter-deriv-packages/commit/4ff1747b76e168710768be84d851276db5884c29)) + +## 0.0.3+28 + + - Update a dependency to the latest release. + +## 0.0.3+27 + + - **REFACTOR**(deriv_passkeys): replace token with account entity ([#785](https://github.com/regentmarkets/flutter-deriv-packages/issues/785)). ([600d7e44](https://github.com/regentmarkets/flutter-deriv-packages/commit/600d7e44c4318501d180640e5a9c21f5357f6022)) + +## 0.0.3+26 + + - Update a dependency to the latest release. + +## 0.0.3+25 + + - Update a dependency to the latest release. + +## 0.0.3+24 + + - Update a dependency to the latest release. + +## 0.0.3+23 + + - Update a dependency to the latest release. + +## 0.0.3+22 + + - Update a dependency to the latest release. + +## 0.0.3+21 + + - Update a dependency to the latest release. + +## 0.0.3+20 + + - Update a dependency to the latest release. + +## 0.0.3+19 + + - Update a dependency to the latest release. + +## 0.0.3+18 + + - Update a dependency to the latest release. + +## 0.0.3+17 + + - Update a dependency to the latest release. + +## 0.0.3+16 + + - Update a dependency to the latest release. + +## 0.0.3+15 + + - Update a dependency to the latest release. + +## 0.0.3+14 + + - Update a dependency to the latest release. + +## 0.0.3+13 + + - Update a dependency to the latest release. + +## 0.0.3+12 + + - Update a dependency to the latest release. + +## 0.0.3+11 + + - Update a dependency to the latest release. + +## 0.0.3+10 + + - Update a dependency to the latest release. + +## 0.0.3+9 + + - Update a dependency to the latest release. + +## 0.0.3+8 + + - Update a dependency to the latest release. + +## 0.0.3+7 + + - Update a dependency to the latest release. + +## 0.0.3+6 + + - Update a dependency to the latest release. + +## 0.0.3+5 + + - **REFACTOR**(version): updated the version of flutter deriv api ([#694](https://github.com/regentmarkets/flutter-deriv-packages/issues/694)). ([eac7e8cb](https://github.com/regentmarkets/flutter-deriv-packages/commit/eac7e8cba4e9310d30296e07a47731f08d4d7342)) + +## 0.0.3+4 + + - **FIX**(deriv_passkeys): fix some missing keys in passkey login page ([#692](https://github.com/regentmarkets/flutter-deriv-packages/issues/692)). ([d944a1c3](https://github.com/regentmarkets/flutter-deriv-packages/commit/d944a1c37f127f35143d9920532f76bc3487ebd4)) + +## 0.0.3+3 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +## 0.0.3+2 + + - Update a dependency to the latest release. + +## 0.0.3+1 + + - **FIX**(deriv_passkeys): add keys to passkey login page ([#676](https://github.com/regentmarkets/flutter-deriv-packages/issues/676)). ([aa84a46d](https://github.com/regentmarkets/flutter-deriv-packages/commit/aa84a46dfb9cd22a335276c1ae0063ffee7852ef)) + +## 0.0.3 + + - **FEAT**(deriv_passkeys): [P2PS-3072] add keys to deriv passkey package effortless passkeys login page ([#677](https://github.com/regentmarkets/flutter-deriv-packages/issues/677)). ([39472704](https://github.com/regentmarkets/flutter-deriv-packages/commit/39472704a3d264bc5f64ba2ae75e29134f890590)) + +## 0.0.2+9 + + - **REFACTOR**(deriv_passkeys): update deriv api dependency ([#656](https://github.com/regentmarkets/flutter-deriv-packages/issues/656)). ([3425078b](https://github.com/regentmarkets/flutter-deriv-packages/commit/3425078b52baac4f387504c9d41063bda1dba249)) + +## 0.0.2+8 + + - Update a dependency to the latest release. + +## 0.0.2+7 + + - **FIX**(deriv_passkeys): Fix_logout_issue_by_fetching_refresh_token ([#630](https://github.com/regentmarkets/flutter-deriv-packages/issues/630)). ([282278ae](https://github.com/regentmarkets/flutter-deriv-packages/commit/282278aeb27256eaa37660f58eff704d27e92c93)) + +## 0.0.2+6 + + - Update a dependency to the latest release. + +## 0.0.2+5 + + - Update a dependency to the latest release. + +## 0.0.2+4 + + - **FIX**(deriv_passkeys): fix passkeys success button styles ([#629](https://github.com/regentmarkets/flutter-deriv-packages/issues/629)). ([d1fb8590](https://github.com/regentmarkets/flutter-deriv-packages/commit/d1fb8590eb5eb55f9bd9db3a255f33cf152f364d)) + +## 0.0.2+3 + + - Update a dependency to the latest release. + +## 0.0.2+2 + + - Update a dependency to the latest release. + +## 0.0.2+1 + + - Update a dependency to the latest release. + +## 0.0.2 + + - **REFACTOR**(deriv_passkeys): Removed deprecated linter rules ([#625](https://github.com/regentmarkets/flutter-deriv-packages/issues/625)). ([2fdc28f2](https://github.com/regentmarkets/flutter-deriv-packages/commit/2fdc28f20b8efe2ddc3a9a261c40b533307f25e3)) + - **FEAT**(deriv_passkeys): add user tracking events. ([#607](https://github.com/regentmarkets/flutter-deriv-packages/issues/607)). ([d86b51e2](https://github.com/regentmarkets/flutter-deriv-packages/commit/d86b51e2fe4ca4d18768d0ba17567a388a8d360d)) + +## 0.0.1+8 + + - Update a dependency to the latest release. + +## 0.0.1+7 + + - Update a dependency to the latest release. + +## 0.0.1+6 + + - Update a dependency to the latest release. + +## 0.0.1+5 + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + - **FIX**(deriv_passkeys): Increased iOS support to 16 ([#601](https://github.com/regentmarkets/flutter-deriv-packages/issues/601)). ([b136424f](https://github.com/regentmarkets/flutter-deriv-packages/commit/b136424f144454727d670c3076074ed0e7197ae0)) + +## 0.0.1+4 + + - **REFACTOR**: update dependencies. ([af89e148](https://github.com/regentmarkets/flutter-deriv-packages/commit/af89e148dfecb6dbc8552b2b883cbad4102a56f8)) + +## 0.0.1+3 + + - **REFACTOR**(deriv_passkeys): Update exception handling in DerivPasskeysPlugin. ([5d218724](https://github.com/regentmarkets/flutter-deriv-packages/commit/5d218724ffd2cf71dc58238fda97e8413abfee8a)) + - **REFACTOR**(deriv_passkeys): Remove unused code. ([8a12ae4a](https://github.com/regentmarkets/flutter-deriv-packages/commit/8a12ae4aebffb48fbfe4b205019a83a4a86fde7e)) + - **REFACTOR**(deriv_passkeys): Update exception handling in DerivPasskeysPlugin. ([5f31ece9](https://github.com/regentmarkets/flutter-deriv-packages/commit/5f31ece9eb5331e1e252c9db0c63423dd5b46d8f)) + - **REFACTOR**(deriv_passkeys): Update exception handling in DerivPasskeysPlugin. ([ca366418](https://github.com/regentmarkets/flutter-deriv-packages/commit/ca36641847dc206354ece977e44c8bf4531a3def)) + - **REFACTOR**(deriv_passkeys): Fix exception handling in DerivPasskeysPlugin. ([eafa7cd1](https://github.com/regentmarkets/flutter-deriv-packages/commit/eafa7cd1f5eacc6d7c58f345936292dc87af624e)) + +## 0.0.1+2 + + - Update a dependency to the latest release. + +## 0.0.1+1 + + - **REFACTOR**(deriv_passkeys): updated deriv_localizations ([#590](https://github.com/regentmarkets/flutter-deriv-packages/issues/590)). ([3f299fae](https://github.com/regentmarkets/flutter-deriv-packages/commit/3f299faea33731a814d18f18ba99180eb6483ca6)) + +## 0.0.1 + +* TODO: Describe initial release. diff --git a/packages/deriv_passkeys/LICENSE b/packages/deriv_passkeys/LICENSE new file mode 100644 index 000000000..ba75c69f7 --- /dev/null +++ b/packages/deriv_passkeys/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/packages/deriv_passkeys/README.md b/packages/deriv_passkeys/README.md new file mode 100644 index 000000000..ef9c026b7 --- /dev/null +++ b/packages/deriv_passkeys/README.md @@ -0,0 +1,96 @@ +# Deriv Passkeys + +Deriv Passkeys is a Flutter plugin that allows you to generate passkeys for Deriv applications (based on WebAuthn). + +## Features + +This package contains everything you need to set up passkeys in your Deriv Flutter App: + +* Android and iOS support +* Login with passkeys +* Create passkeys +* List passkeys + +## Dependencies to Other Deriv Packages + +* [flutter_deriv_api](https://github.com/deriv-com/flutter-deriv-api) +* [deriv_theme](https://github.com/regentmarkets/flutter-deriv-packages/tree/master/packages/deriv_theme) +* [deriv_ui](https://github.com/regentmarkets/flutter-deriv-packages/tree/master/packages/deriv_ui) + +## Getting started + +To use this package, add `deriv_passkeys` as a dependency in your pubspec.yaml file: + +```yaml +dependencies: + deriv_passkeys: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_passkeys + ref: deriv_passkeys-v0.0.1 +``` + +### Android + +No additional setup required. + +### iOS + +- Add the associated domains to your signing capabilities in Xcode: +- Add the following values to your associated domains: + - webcredentials:deriv.com + - applinks:deriv.com + + +## Usage + +### Import the package + +```dart +import 'package:deriv_passkeys/deriv_passkeys.dart'; +``` + +### DerivPasskeysBloc: + +```dart + final DerivPasskeysBloc derivPasskeysBloc = DerivPasskeysBloc( + getJwtToken: derivJwtService.getJwtToken, + derivPasskeysService: DerivPasskeysService( + DerivPasskeysRepository( + DerivPasskeysDataSource( + DerivPasskeysMapper(), + ), + ), + ), + connectionInfo: PasskeysConnectionInfoEntity( + appId: FlavorConfig.instance.flavorValues.appId!, + endpoint: FlavorConfig.instance.flavorValues.endPoint!, + ), + ); +``` + +### ContinueWithPasskeyButton + +```dart + ContinueWithPasskeyButton( + derivPasskeysBloc: context.read(), + ), +``` + +- This widget is used to continue with passkey. +- It requires `derivPasskeysBloc` as a parameter. +- It will return not show anything if the user's device does not support passkeys. +- When it's clicked it will either be successful or show an error message. +- in case of success the DerivPasskeysBloc will emit DerivPasskeysCredentialVerifiedState. +- The DerivPasskeysCredentialVerifiedState will contain the token which can be used to authenticate the user. + ```dart + DerivPasskeysCredentialVerifiedState( + token: token, + ), + ``` + + + + + + diff --git a/packages/deriv_passkeys/analysis_options.yaml b/packages/deriv_passkeys/analysis_options.yaml new file mode 100644 index 000000000..aea7c9654 --- /dev/null +++ b/packages/deriv_passkeys/analysis_options.yaml @@ -0,0 +1,117 @@ +analyzer: + language: + strict-raw-types: true + +linter: + rules: + - always_declare_return_types + - always_put_control_body_on_new_line + - always_put_required_named_parameters_first + - always_specify_types + - annotate_overrides + - avoid_bool_literals_in_conditional_expressions + - avoid_catches_without_on_clauses + - avoid_empty_else + - avoid_equals_and_hash_code_on_mutable_classes + - avoid_field_initializers_in_const_classes + - avoid_function_literals_in_foreach_calls + - avoid_init_to_null + - avoid_null_checks_in_equality_operators + - avoid_positional_boolean_parameters + - avoid_print + - avoid_redundant_argument_values + - avoid_relative_lib_imports + - avoid_renaming_method_parameters + - avoid_return_types_on_setters + - avoid_returning_null_for_void + - avoid_setters_without_getters + - avoid_shadowing_type_parameters + - avoid_single_cascade_in_expression_statements + - avoid_slow_async_io + - avoid_types_as_parameter_names + - avoid_unnecessary_containers + - avoid_unused_constructor_parameters + - avoid_void_async + - await_only_futures + - camel_case_types + - cancel_subscriptions + - cascade_invocations + - close_sinks + - constant_identifier_names + - control_flow_in_finally + - curly_braces_in_flow_control_structures + - empty_catches + - empty_constructor_bodies + - empty_statements + - file_names + - flutter_style_todos + - hash_and_equals + - implementation_imports + - join_return_with_assignment + - library_names + - library_prefixes + - no_adjacent_strings_in_list + - no_duplicate_case_values + - non_constant_identifier_names + - null_closures + - only_throw_errors + - overridden_fields + - package_api_docs + - package_names + - package_prefixed_library_names + - parameter_assignments + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_constructors_over_static_methods + - prefer_contains + - prefer_expression_function_bodies + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_foreach + - prefer_function_declarations_over_variables + - prefer_generic_function_type_aliases + - prefer_initializing_formals + - prefer_int_literals + - prefer_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_iterable_whereType + - prefer_mixin + - prefer_single_quotes + - prefer_typing_uninitialized_variables + - prefer_void_to_null + - public_member_api_docs + - recursive_getters + - slash_for_doc_comments + - sort_constructors_first + - sort_unnamed_constructors_first + - test_types_in_equals + - throw_in_finally + - type_init_formals + - unawaited_futures + - unnecessary_await_in_return + - unnecessary_brace_in_string_interps + - unnecessary_const + - unnecessary_getters_setters + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_overrides + - unnecessary_parenthesis + - unnecessary_statements + - unnecessary_this + - unrelated_type_equality_checks + - use_function_type_syntax_for_parameters + - use_rethrow_when_possible + - use_setters_to_change_properties + - use_string_buffers + - use_to_and_as_if_applicable + - valid_regexps + - void_checks diff --git a/packages/deriv_passkeys/android/.gitignore b/packages/deriv_passkeys/android/.gitignore new file mode 100644 index 000000000..161bdcdaf --- /dev/null +++ b/packages/deriv_passkeys/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/packages/deriv_passkeys/android/build.gradle b/packages/deriv_passkeys/android/build.gradle new file mode 100644 index 000000000..935fccbf9 --- /dev/null +++ b/packages/deriv_passkeys/android/build.gradle @@ -0,0 +1,76 @@ +group 'com.deriv.passkeys.deriv_passkeys' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.8.22' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:8.0.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + if (project.android.hasProperty("namespace")) { + namespace 'com.deriv.passkeys.deriv_passkeys' + } + + compileSdk 34 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + test.java.srcDirs += 'src/test/kotlin' + } + + defaultConfig { + minSdkVersion 21 + } + + dependencies { + testImplementation 'org.jetbrains.kotlin:kotlin-test' + testImplementation 'org.mockito:mockito-core:5.0.0' + + implementation "androidx.credentials:credentials:1.2.2" + implementation "androidx.credentials:credentials-play-services-auth:1.2.2" + + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1" + + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1" + + } + + testOptions { + unitTests.all { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen {false} + showStandardStreams = true + } + } + } +} diff --git a/packages/deriv_passkeys/android/gradle.properties b/packages/deriv_passkeys/android/gradle.properties new file mode 100644 index 000000000..fc53bee90 --- /dev/null +++ b/packages/deriv_passkeys/android/gradle.properties @@ -0,0 +1,15 @@ +## 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. +# Default value: -Xmx1024m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -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 +#Tue Dec 26 15:23:43 GST 2023 +android.enableJetifier=true +android.useAndroidX=true diff --git a/packages/deriv_passkeys/android/settings.gradle b/packages/deriv_passkeys/android/settings.gradle new file mode 100644 index 000000000..dcd8a9248 --- /dev/null +++ b/packages/deriv_passkeys/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'deriv_passkeys' diff --git a/packages/deriv_passkeys/android/src/main/AndroidManifest.xml b/packages/deriv_passkeys/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000..de887b8df --- /dev/null +++ b/packages/deriv_passkeys/android/src/main/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/packages/deriv_passkeys/android/src/main/kotlin/com/deriv/passkeys/deriv_passkeys/DerivPasskeysPlugin.kt b/packages/deriv_passkeys/android/src/main/kotlin/com/deriv/passkeys/deriv_passkeys/DerivPasskeysPlugin.kt new file mode 100644 index 000000000..f153e13d2 --- /dev/null +++ b/packages/deriv_passkeys/android/src/main/kotlin/com/deriv/passkeys/deriv_passkeys/DerivPasskeysPlugin.kt @@ -0,0 +1,155 @@ +package com.deriv.passkeys.deriv_passkeys + +import android.app.Activity +import androidx.credentials.CreatePublicKeyCredentialRequest +import androidx.credentials.CreatePublicKeyCredentialResponse +import androidx.credentials.CredentialManager +import androidx.credentials.GetCredentialRequest +import androidx.credentials.GetPublicKeyCredentialOption +import androidx.credentials.PublicKeyCredential +import androidx.credentials.exceptions.publickeycredential.CreatePublicKeyCredentialDomException +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import kotlinx.coroutines.launch +import org.json.JSONObject +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import androidx.credentials.exceptions.NoCredentialException +import androidx.credentials.exceptions.GetCredentialCancellationException + +/// DerivPasskeysPlugin is a Flutter plugin that provides a way to create and get credentials using the WebAuthn API. +class DerivPasskeysPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, ViewModel() { + /// The MethodChannel that will the communication between Flutter and native Android + /// + /// This local reference serves to register the plugin with the Flutter Engine and unregister it + /// when the Flutter Engine is detached from the Activity + private lateinit var channel: MethodChannel + private lateinit var activity: Activity + + + private fun createCredential(options: String, callback: (credential: String?, e: Exception?) -> Unit) { + JSONObject(options) + val createPublicKeyCredentialRequest = CreatePublicKeyCredentialRequest( + requestJson = options, + preferImmediatelyAvailableCredentials = false + ) + viewModelScope.launch { + try { + val credentialManager = CredentialManager.create(activity) + val result = credentialManager.createCredential( + context = activity, + request = createPublicKeyCredentialRequest, + ) + val credential = result as CreatePublicKeyCredentialResponse + callback(credential.registrationResponseJson, null) + } catch (e: Exception) { + callback(null, e) + } + } + } + + private fun getCredential(options: String, callback: (credential: String?, e: Exception?) -> Unit) { + JSONObject(options) + val getPublicKeyCredentialOption = GetPublicKeyCredentialOption( + requestJson = options + ) + viewModelScope.launch { + try { + val credentialManager = CredentialManager.create(activity) + val result = credentialManager.getCredential( + context = activity, + request = GetCredentialRequest(listOf(getPublicKeyCredentialOption)), + ) + val credential = result.credential as PublicKeyCredential + callback(credential.authenticationResponseJson, null) + } catch (e: Exception) { + callback(null, e) + } + } + } + + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "deriv_passkeys") + channel.setMethodCallHandler(this) + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + this.activity = binding.activity; + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } + + override fun onDetachedFromActivity() { + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + this.activity = binding.activity; + } + + override fun onDetachedFromActivityForConfigChanges() { + } + + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "isPlatformSupported" -> { + val isSupported = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P + result.success(isSupported) + } + "createCredential" -> { + val options = call.argument("options") as String? + options?.let { + try { + createCredential(options) { credential, e -> + if (credential != null) { + result.success(credential) + } + else if (e != null) { + var exceptionType = e.javaClass.simpleName + if (e is CreatePublicKeyCredentialDomException) { + exceptionType = "$exceptionType(${e.domError.javaClass.simpleName ?: "DomError"})" + } + result.error(exceptionType ?: "Exception", e.message ?: "Exception occurred", null) + } + else { + result.error("Error", "Unknown error", null) + } + } + } catch (e: Exception) { + result.error(e.javaClass.simpleName ?: "Exception", e.message ?: "Exception occurred", null) + } + } ?: run { + result.error("InvalidParameterException", "Options not found", null) + } + } + "getCredential" -> { + val options = call.argument("options") as String? + options?.let { + try { + getCredential(options) { credential, e -> + if (credential != null) { + result.success(credential) + } + else if (e != null) { + result.error(e.javaClass.simpleName ?: "Exception", e.message ?: "Exception occurred", null) + } + else { + result.error("Error", "Unknown error", null) + } + } + } catch (e: Exception) { + result.error(e.javaClass.simpleName ?: "Exception", e.message ?: "Exception occurred", null) + } + } ?: run { + result.error("InvalidParameterException", "Options not found", null) + } + } + else -> result.notImplemented() + } + } +} diff --git a/packages/deriv_passkeys/android/src/main/proguard-rules.pro b/packages/deriv_passkeys/android/src/main/proguard-rules.pro new file mode 100644 index 000000000..5bf7d0357 --- /dev/null +++ b/packages/deriv_passkeys/android/src/main/proguard-rules.pro @@ -0,0 +1,4 @@ +-if class androidx.credentials.CredentialManager +-keep class androidx.credentials.playservices.** { + *; +} diff --git a/packages/deriv_passkeys/assets/svg/add_passkey_icon.svg b/packages/deriv_passkeys/assets/svg/add_passkey_icon.svg new file mode 100644 index 000000000..8e9207994 --- /dev/null +++ b/packages/deriv_passkeys/assets/svg/add_passkey_icon.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/deriv_passkeys/assets/svg/effortless_login_passkey_icon.svg b/packages/deriv_passkeys/assets/svg/effortless_login_passkey_icon.svg new file mode 100644 index 000000000..d41e62fac --- /dev/null +++ b/packages/deriv_passkeys/assets/svg/effortless_login_passkey_icon.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/deriv_passkeys/assets/svg/face_id.svg b/packages/deriv_passkeys/assets/svg/face_id.svg new file mode 100644 index 000000000..eefdd4626 --- /dev/null +++ b/packages/deriv_passkeys/assets/svg/face_id.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/deriv_passkeys/assets/svg/fingerprint_icon.svg b/packages/deriv_passkeys/assets/svg/fingerprint_icon.svg new file mode 100644 index 000000000..610bf2721 --- /dev/null +++ b/packages/deriv_passkeys/assets/svg/fingerprint_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/deriv_passkeys/assets/svg/learn_more_passkeys_icon.svg b/packages/deriv_passkeys/assets/svg/learn_more_passkeys_icon.svg new file mode 100644 index 000000000..fd8279ecf --- /dev/null +++ b/packages/deriv_passkeys/assets/svg/learn_more_passkeys_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/deriv_passkeys/assets/svg/light_bulb_icon.svg b/packages/deriv_passkeys/assets/svg/light_bulb_icon.svg new file mode 100644 index 000000000..c3c24b18a --- /dev/null +++ b/packages/deriv_passkeys/assets/svg/light_bulb_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/deriv_passkeys/assets/svg/lock_icon.svg b/packages/deriv_passkeys/assets/svg/lock_icon.svg new file mode 100644 index 000000000..c9ce9da67 --- /dev/null +++ b/packages/deriv_passkeys/assets/svg/lock_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/deriv_passkeys/assets/svg/passkey_created_success_icon.svg b/packages/deriv_passkeys/assets/svg/passkey_created_success_icon.svg new file mode 100644 index 000000000..af74a8fbe --- /dev/null +++ b/packages/deriv_passkeys/assets/svg/passkey_created_success_icon.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/deriv_passkeys/assets/svg/passkey_icon.svg b/packages/deriv_passkeys/assets/svg/passkey_icon.svg new file mode 100644 index 000000000..16812e500 --- /dev/null +++ b/packages/deriv_passkeys/assets/svg/passkey_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/deriv_passkeys/assets/svg/sync_icon.svg b/packages/deriv_passkeys/assets/svg/sync_icon.svg new file mode 100644 index 000000000..4ba1b1be0 --- /dev/null +++ b/packages/deriv_passkeys/assets/svg/sync_icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/deriv_passkeys/example/.gitignore b/packages/deriv_passkeys/example/.gitignore new file mode 100644 index 000000000..24476c5d1 --- /dev/null +++ b/packages/deriv_passkeys/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/packages/deriv_passkeys/example/.metadata b/packages/deriv_passkeys/example/.metadata new file mode 100644 index 000000000..ab3e1c09c --- /dev/null +++ b/packages/deriv_passkeys/example/.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: "ead455963c12b453cdb2358cad34969c76daf180" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: ead455963c12b453cdb2358cad34969c76daf180 + base_revision: ead455963c12b453cdb2358cad34969c76daf180 + - platform: android + create_revision: ead455963c12b453cdb2358cad34969c76daf180 + base_revision: ead455963c12b453cdb2358cad34969c76daf180 + - platform: ios + create_revision: ead455963c12b453cdb2358cad34969c76daf180 + base_revision: ead455963c12b453cdb2358cad34969c76daf180 + - platform: linux + create_revision: ead455963c12b453cdb2358cad34969c76daf180 + base_revision: ead455963c12b453cdb2358cad34969c76daf180 + - platform: macos + create_revision: ead455963c12b453cdb2358cad34969c76daf180 + base_revision: ead455963c12b453cdb2358cad34969c76daf180 + - platform: web + create_revision: ead455963c12b453cdb2358cad34969c76daf180 + base_revision: ead455963c12b453cdb2358cad34969c76daf180 + - platform: windows + create_revision: ead455963c12b453cdb2358cad34969c76daf180 + base_revision: ead455963c12b453cdb2358cad34969c76daf180 + + # 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/packages/deriv_passkeys/example/README.md b/packages/deriv_passkeys/example/README.md new file mode 100644 index 000000000..f121535bb --- /dev/null +++ b/packages/deriv_passkeys/example/README.md @@ -0,0 +1,8 @@ +# passkeys_poc + +Passkeys POC is a simple Flutter app that demonstrates how to use the `deriv_passkeys` package to authenticate a user in a Deriv app using the passkeys authentication method. + + +## Getting Started + +To make the example app work, you need to fill in the `appId` and `endpoint` in the `connectionInfo` object. Open the file `lib/main.dart` and locate the following code: diff --git a/packages/deriv_passkeys/example/analysis_options.yaml b/packages/deriv_passkeys/example/analysis_options.yaml new file mode 100644 index 000000000..76b08637f --- /dev/null +++ b/packages/deriv_passkeys/example/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + depend_on_referenced_packages: false + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/deriv_passkeys/example/android/.gitignore b/packages/deriv_passkeys/example/android/.gitignore new file mode 100644 index 000000000..6f568019d --- /dev/null +++ b/packages/deriv_passkeys/example/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/packages/deriv_passkeys/example/android/app/build.gradle b/packages/deriv_passkeys/example/android/app/build.gradle new file mode 100644 index 000000000..63de18e36 --- /dev/null +++ b/packages/deriv_passkeys/example/android/app/build.gradle @@ -0,0 +1,72 @@ +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 { + namespace "com.example.passkeys_poc" + compileSdk 34 + 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.passkeys_poc" + // 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/packages/deriv_passkeys/example/android/app/src/debug/AndroidManifest.xml b/packages/deriv_passkeys/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..399f6981d --- /dev/null +++ b/packages/deriv_passkeys/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/deriv_passkeys/example/android/app/src/main/AndroidManifest.xml b/packages/deriv_passkeys/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..b6e879786 --- /dev/null +++ b/packages/deriv_passkeys/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/packages/deriv_passkeys/example/android/app/src/main/kotlin/com/example/passkeys_poc/MainActivity.kt b/packages/deriv_passkeys/example/android/app/src/main/kotlin/com/example/passkeys_poc/MainActivity.kt new file mode 100644 index 000000000..ede17fb8d --- /dev/null +++ b/packages/deriv_passkeys/example/android/app/src/main/kotlin/com/example/passkeys_poc/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.passkeys_poc + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/packages/deriv_passkeys/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/deriv_passkeys/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/packages/deriv_passkeys/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/deriv_passkeys/example/android/app/src/main/res/drawable/launch_background.xml b/packages/deriv_passkeys/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/packages/deriv_passkeys/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/deriv_passkeys/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/deriv_passkeys/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..db77bb4b7 Binary files /dev/null and b/packages/deriv_passkeys/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/deriv_passkeys/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/deriv_passkeys/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..17987b79b Binary files /dev/null and b/packages/deriv_passkeys/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/deriv_passkeys/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/deriv_passkeys/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..09d439148 Binary files /dev/null and b/packages/deriv_passkeys/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/deriv_passkeys/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/deriv_passkeys/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..d5f1c8d34 Binary files /dev/null and b/packages/deriv_passkeys/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/deriv_passkeys/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/deriv_passkeys/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..4d6372eeb Binary files /dev/null and b/packages/deriv_passkeys/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/deriv_passkeys/example/android/app/src/main/res/values-night/styles.xml b/packages/deriv_passkeys/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/packages/deriv_passkeys/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/deriv_passkeys/example/android/app/src/main/res/values/styles.xml b/packages/deriv_passkeys/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..cb1ef8805 --- /dev/null +++ b/packages/deriv_passkeys/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/deriv_passkeys/example/android/app/src/profile/AndroidManifest.xml b/packages/deriv_passkeys/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..399f6981d --- /dev/null +++ b/packages/deriv_passkeys/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/deriv_passkeys/example/android/build.gradle b/packages/deriv_passkeys/example/android/build.gradle new file mode 100644 index 000000000..f7eb7f63c --- /dev/null +++ b/packages/deriv_passkeys/example/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/packages/deriv_passkeys/example/android/gradle.properties b/packages/deriv_passkeys/example/android/gradle.properties new file mode 100644 index 000000000..94adc3a3f --- /dev/null +++ b/packages/deriv_passkeys/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/deriv_passkeys/example/android/settings.gradle b/packages/deriv_passkeys/example/android/settings.gradle new file mode 100644 index 000000000..44e62bcf0 --- /dev/null +++ b/packages/deriv_passkeys/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/packages/deriv_passkeys/example/devtools_options.yaml b/packages/deriv_passkeys/example/devtools_options.yaml new file mode 100644 index 000000000..7e7e7f67d --- /dev/null +++ b/packages/deriv_passkeys/example/devtools_options.yaml @@ -0,0 +1 @@ +extensions: diff --git a/packages/deriv_passkeys/example/ios/.gitignore b/packages/deriv_passkeys/example/ios/.gitignore new file mode 100644 index 000000000..7a7f9873a --- /dev/null +++ b/packages/deriv_passkeys/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/packages/deriv_passkeys/example/ios/Flutter/AppFrameworkInfo.plist b/packages/deriv_passkeys/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..7c5696400 --- /dev/null +++ b/packages/deriv_passkeys/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 + 12.0 + + diff --git a/packages/deriv_passkeys/example/ios/Flutter/Debug.xcconfig b/packages/deriv_passkeys/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..ec97fc6f3 --- /dev/null +++ b/packages/deriv_passkeys/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/packages/deriv_passkeys/example/ios/Flutter/Release.xcconfig b/packages/deriv_passkeys/example/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..c4855bfe2 --- /dev/null +++ b/packages/deriv_passkeys/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/packages/deriv_passkeys/example/ios/Podfile b/packages/deriv_passkeys/example/ios/Podfile new file mode 100644 index 000000000..9b392a5ea --- /dev/null +++ b/packages/deriv_passkeys/example/ios/Podfile @@ -0,0 +1,43 @@ +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/packages/deriv_auth_ui/example/ios/Podfile.lock b/packages/deriv_passkeys/example/ios/Podfile.lock similarity index 58% rename from packages/deriv_auth_ui/example/ios/Podfile.lock rename to packages/deriv_passkeys/example/ios/Podfile.lock index 3d9f26088..1de363a36 100644 --- a/packages/deriv_auth_ui/example/ios/Podfile.lock +++ b/packages/deriv_passkeys/example/ios/Podfile.lock @@ -2,18 +2,22 @@ PODS: - connectivity_plus (0.0.1): - Flutter - ReachabilitySwift + - deriv_passkeys (1.0.0): + - Flutter - device_info_plus (0.0.1): - Flutter - Flutter (1.0.0) - flutter_deriv_api (0.0.1): - Flutter - - flutter_inappwebview (0.0.1): + - flutter_inappwebview_ios (0.0.1): - Flutter - - flutter_inappwebview/Core (= 0.0.1) + - flutter_inappwebview_ios/Core (= 0.0.1) - OrderedSet (~> 5.0) - - flutter_inappwebview/Core (0.0.1): + - flutter_inappwebview_ios/Core (0.0.1): - Flutter - OrderedSet (~> 5.0) + - flutter_system_proxy (0.0.1): + - Flutter - OrderedSet (5.0.0) - package_info_plus (0.4.5): - Flutter @@ -25,10 +29,12 @@ PODS: DEPENDENCIES: - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) + - deriv_passkeys (from `.symlinks/plugins/deriv_passkeys/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - Flutter (from `Flutter`) - flutter_deriv_api (from `.symlinks/plugins/flutter_deriv_api/ios`) - - flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`) + - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) + - flutter_system_proxy (from `.symlinks/plugins/flutter_system_proxy/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) @@ -41,14 +47,18 @@ SPEC REPOS: EXTERNAL SOURCES: connectivity_plus: :path: ".symlinks/plugins/connectivity_plus/ios" + deriv_passkeys: + :path: ".symlinks/plugins/deriv_passkeys/ios" device_info_plus: :path: ".symlinks/plugins/device_info_plus/ios" Flutter: :path: Flutter flutter_deriv_api: :path: ".symlinks/plugins/flutter_deriv_api/ios" - flutter_inappwebview: - :path: ".symlinks/plugins/flutter_inappwebview/ios" + flutter_inappwebview_ios: + :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" + flutter_system_proxy: + :path: ".symlinks/plugins/flutter_system_proxy/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" url_launcher_ios: @@ -57,17 +67,19 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" SPEC CHECKSUMS: - connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e + connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d + deriv_passkeys: beeafb308b654fcbec61b567697c2b21c797037d device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed - Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_deriv_api: 9e29abd7cc5091b72303f9c8be549618415f1437 - flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721 + flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0 + flutter_system_proxy: 96eb97e3857a1d1bc533a6f7387a1f0dcb63d782 OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c - package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e + package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 - url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 - webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a + url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 + webview_flutter_wkwebview: 4f3e50f7273d31e5500066ed267e3ae4309c5ae4 -PODFILE CHECKSUM: 70d9d25280d0dd177a5f637cdb0f0b0b12c6a189 +PODFILE CHECKSUM: 23c5db403a9f2d103e08afd68b26a1601b49de37 -COCOAPODS: 1.12.1 +COCOAPODS: 1.13.0 diff --git a/packages/deriv_passkeys/example/ios/Runner.xcodeproj/project.pbxproj b/packages/deriv_passkeys/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..2aab18b65 --- /dev/null +++ b/packages/deriv_passkeys/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,747 @@ +// !$*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 */; }; + 747C8817C0330ABBFB890B14 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C7398745361BCCB0E96EA4D /* Pods_Runner.framework */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 912B875A1A7FB5C581324BD6 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D7BD61FE28A65EBE1195EF83 /* Pods_RunnerTests.framework */; }; + 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 = ""; }; + 1C7398745361BCCB0E96EA4D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 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 = ""; }; + 49A0B9C2519474E7D84EAC8C /* 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 = ""; }; + 532A03FC307182502C1BD3CD /* 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 = ""; }; + 5C02BF7919AD58348F432BF5 /* 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 = ""; }; + 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 = ""; }; + 7D84192F82F1C69314508FE0 /* 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 = ""; }; + 7F9ECC56F29EED37FD5095E7 /* 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; 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 = ""; }; + 9AFCD78B2B50133C003DE0AD /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + CBC9E18AB54CF7F16148E91E /* 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 = ""; }; + D7BD61FE28A65EBE1195EF83 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 29E5AF7624FE1A1191CC1B37 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 912B875A1A7FB5C581324BD6 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 747C8817C0330ABBFB890B14 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 845CB8412A1191B744F55060 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1C7398745361BCCB0E96EA4D /* Pods_Runner.framework */, + D7BD61FE28A65EBE1195EF83 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 8669CBC0392B21497E31E534 /* Pods */ = { + isa = PBXGroup; + children = ( + 7F9ECC56F29EED37FD5095E7 /* Pods-Runner.debug.xcconfig */, + 49A0B9C2519474E7D84EAC8C /* Pods-Runner.release.xcconfig */, + 532A03FC307182502C1BD3CD /* Pods-Runner.profile.xcconfig */, + CBC9E18AB54CF7F16148E91E /* Pods-RunnerTests.debug.xcconfig */, + 5C02BF7919AD58348F432BF5 /* Pods-RunnerTests.release.xcconfig */, + 7D84192F82F1C69314508FE0 /* Pods-RunnerTests.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 */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 8669CBC0392B21497E31E534 /* Pods */, + 845CB8412A1191B744F55060 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 9AFCD78B2B50133C003DE0AD /* Runner.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 = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 78EBCC64ED315B282CABDF60 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 29E5AF7624FE1A1191CC1B37 /* 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 = ( + BBFD2B46E15A0299A268D1AE /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + F0ABF9E044B3B98C9A697E0E /* [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 = { + 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"; + }; + 78EBCC64ED315B282CABDF60 /* [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"; + }; + BBFD2B46E15A0299A268D1AE /* [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; + }; + F0ABF9E044B3B98C9A697E0E /* [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 */ + 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 = 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; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 36S5Q8S4V5; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.deriv.sample; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Deriv sample app development profile"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CBC9E18AB54CF7F16148E91E /* 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.deriv.sample.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 = 5C02BF7919AD58348F432BF5 /* 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.deriv.sample.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 = 7D84192F82F1C69314508FE0 /* 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.deriv.sample.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 = 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_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; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 36S5Q8S4V5; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.deriv.sample; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Deriv sample app development profile"; + 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; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 36S5Q8S4V5; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.deriv.sample; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Deriv sample app development profile"; + 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/packages/deriv_passkeys/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/deriv_passkeys/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/packages/deriv_passkeys/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/deriv_passkeys/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/deriv_passkeys/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/deriv_passkeys/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/deriv_passkeys/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/deriv_passkeys/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/deriv_passkeys/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/deriv_passkeys/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/deriv_passkeys/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..87131a09b --- /dev/null +++ b/packages/deriv_passkeys/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/deriv_passkeys/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/deriv_passkeys/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..21a3cc14c --- /dev/null +++ b/packages/deriv_passkeys/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/deriv_passkeys/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/deriv_passkeys/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/deriv_passkeys/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/deriv_passkeys/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/deriv_passkeys/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/deriv_passkeys/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/deriv_passkeys/example/ios/Runner/AppDelegate.swift b/packages/deriv_passkeys/example/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..70693e4a8 --- /dev/null +++ b/packages/deriv_passkeys/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/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/packages/deriv_passkeys/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/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 000000000..dc9ada472 Binary files /dev/null and b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000..7353c41ec Binary files /dev/null and b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000..797d452e4 Binary files /dev/null and b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000..6ed2d933e Binary files /dev/null and b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000..4cd7b0099 Binary files /dev/null and b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000..fe730945a Binary files /dev/null and b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000..321773cd8 Binary files /dev/null and b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000..797d452e4 Binary files /dev/null and b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000..502f463a9 Binary files /dev/null and b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000..0ec303439 Binary files /dev/null and b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000..0ec303439 Binary files /dev/null and b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000..e9f5fea27 Binary files /dev/null and b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000..84ac32ae7 Binary files /dev/null and b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000..8953cba09 Binary files /dev/null and b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000..0467bf12a Binary files /dev/null and b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..0bedcf2fd --- /dev/null +++ b/packages/deriv_passkeys/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/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/deriv_passkeys/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/deriv_passkeys/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/packages/deriv_passkeys/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/deriv_passkeys/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/packages/deriv_passkeys/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/deriv_passkeys/example/ios/Runner/Base.lproj/Main.storyboard b/packages/deriv_passkeys/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/packages/deriv_passkeys/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/deriv_passkeys/example/ios/Runner/Info.plist b/packages/deriv_passkeys/example/ios/Runner/Info.plist new file mode 100644 index 000000000..5458fc418 --- /dev/null +++ b/packages/deriv_passkeys/example/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example + 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/packages/deriv_passkeys/example/ios/Runner/Runner-Bridging-Header.h b/packages/deriv_passkeys/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/packages/deriv_passkeys/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/deriv_passkeys/example/ios/Runner/Runner.entitlements b/packages/deriv_passkeys/example/ios/Runner/Runner.entitlements new file mode 100644 index 000000000..d96c26284 --- /dev/null +++ b/packages/deriv_passkeys/example/ios/Runner/Runner.entitlements @@ -0,0 +1,15 @@ + + + + + com.apple.developer.associated-domains + + webcredentials:qa163.deriv.dev + applinks:qa163.deriv.dev + applinks:pro-7837426045311437779.frontendapi.corbado.io + webcredentials:pro-7837426045311437779.frontendapi.corbado.io + webcredentials:24be-94-207-97-237.ngrok-free.app + applinks:24be-94-207-97-237.ngrok-free.app + + + diff --git a/packages/deriv_passkeys/example/ios/RunnerTests/RunnerTests.swift b/packages/deriv_passkeys/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..86a7c3b1b --- /dev/null +++ b/packages/deriv_passkeys/example/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/packages/deriv_passkeys/example/lib/main.dart b/packages/deriv_passkeys/example/lib/main.dart new file mode 100644 index 000000000..8047aeed3 --- /dev/null +++ b/packages/deriv_passkeys/example/lib/main.dart @@ -0,0 +1,113 @@ +import 'package:deriv_http_client/deriv_http_client.dart'; +import 'package:deriv_localizations/l10n/generated/deriv_passkeys/deriv_passkeys_localizations.dart'; +import 'package:deriv_passkeys/deriv_passkeys.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'dart:async'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => DerivPasskeysBloc( + derivPasskeysService: DerivPasskeysService( + DerivPasskeysRepository( + DerivPasskeysDataSource( + mapper: DerivPasskeysMapper(), client: HttpClient()), + ), + ), + connectionInfo: PasskeysConnectionInfoEntity( + appId: '', + endpoint: '', + ), + getJwtToken: () { + return Future.value(''); + }, + ), + child: MaterialApp( + localizationsDelegates: const >[ + DerivPasskeysLocalizations.delegate, + ], + theme: ThemeData( + primaryColor: context.theme.colors.secondary, + fontFamily: context.theme.fontFamily, + brightness: Brightness.dark, + bottomSheetTheme: BottomSheetThemeData( + backgroundColor: Colors.black.withOpacity(0), + modalBarrierColor: Colors.black.withOpacity(0.72), + ), + unselectedWidgetColor: context.theme.colors.lessProminent, + toggleButtonsTheme: ToggleButtonsThemeData( + textStyle: context.theme.textStyle( + textStyle: TextStyles.body2, + ), + ), + colorScheme: const ColorScheme.dark().copyWith( + primary: context.theme.colors.prominent, + secondary: context.theme.colors.coral, + ), + textSelectionTheme: TextSelectionThemeData( + cursorColor: context.theme.colors.lessProminent, + selectionHandleColor: context.theme.colors.lessProminent, + selectionColor: context.theme.colors.lessProminent, + ), + appBarTheme: AppBarTheme.of(context).copyWith( + backgroundColor: context.theme.colors.secondary, + centerTitle: false, + ), + ), + home: const MyPage(), + ), + ); + } +} + +class MyPage extends StatelessWidget { + const MyPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: context.theme.colors.primary, + appBar: AppBar( + title: const Text('Deriv Passkeys example app'), + ), + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ContinueWithPasskeyButton( + onTap: () {}, + ), + SizedBox( + width: double.infinity, + child: Padding( + padding: const EdgeInsets.all(16), + child: TextButton( + onPressed: () { + context + .read() + .add(DerivPasskeysCreateCredentialEvent()); + }, + child: const Text('Create Passkey'), + ), + ), + ) + ], + ), + ); + } +} diff --git a/packages/deriv_passkeys/example/pubspec.yaml b/packages/deriv_passkeys/example/pubspec.yaml new file mode 100644 index 000000000..b890b2bbd --- /dev/null +++ b/packages/deriv_passkeys/example/pubspec.yaml @@ -0,0 +1,54 @@ +name: passkeys_poc +description: A new Flutter project. +# 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. +version: 1.0.0+1 + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + deriv_passkeys: + path: ../ + + cupertino_icons: ^1.0.2 + dartz: ^0.10.1 + json_annotation: ^4.8.1 + http: ^1.1.0 + flutter_platform_widgets: ^3.3.5 + flutter_bloc: ^8.1.3 + equatable: ^2.0.5 + +dependency_overrides: + flutter_svg: ^2.0.9 + http: ^1.1.0 + +dev_dependencies: + 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: ^2.0.0 + +flutter: + uses-material-design: true diff --git a/packages/deriv_passkeys/example/test/widget_test.dart b/packages/deriv_passkeys/example/test/widget_test.dart new file mode 100644 index 000000000..9468a3906 --- /dev/null +++ b/packages/deriv_passkeys/example/test/widget_test.dart @@ -0,0 +1,30 @@ +// 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. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:passkeys_poc/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/packages/deriv_passkeys/ios/.gitignore b/packages/deriv_passkeys/ios/.gitignore new file mode 100644 index 000000000..0c885071e --- /dev/null +++ b/packages/deriv_passkeys/ios/.gitignore @@ -0,0 +1,38 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig +/Flutter/ephemeral/ +/Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/packages/deriv_passkeys/ios/Classes/DerivPasskeysManager.swift b/packages/deriv_passkeys/ios/Classes/DerivPasskeysManager.swift new file mode 100644 index 000000000..7b42151d1 --- /dev/null +++ b/packages/deriv_passkeys/ios/Classes/DerivPasskeysManager.swift @@ -0,0 +1,363 @@ +import Flutter +import UIKit +import AuthenticationServices + +@available(iOS 16.0, *) +public class DerivPasskeysManager{ + enum PluginError: Error { + case unknownError + case notFound(String) + case unknownCredentialType(AnyObject) + case excludedCredentialExists + } + + class AuthCtrlDelegate: NSObject, ASAuthorizationControllerDelegate { + private let semaphore = DispatchSemaphore(value: 0) + private var result: (controller: ASAuthorizationController, authorization: ASAuthorization?, error: Error?)? + + func getResult() throws -> (controller: ASAuthorizationController, authorization: ASAuthorization?, error: Error?) { + semaphore.wait() + guard let ret = result else { + throw PluginError.unknownError + } + return ret + } + + func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { + result = (controller, authorization, nil) + semaphore.signal() + } + + func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { + result = (controller, nil, error) + semaphore.signal() + } + } + + @available(iOS 16.0, *) + private func getPlatformPublicKeyCredentials() -> [String] { + return (UserDefaults.standard.object(forKey: "platformPublicKeyCredentials") as? [String]) ?? [] + } + + @available(iOS 16.0, *) + private func savePlatformPublicKeyCredential(_ credential: ASAuthorizationCredential) { + var credentialID = "" + if let credential = credential as? ASAuthorizationPlatformPublicKeyCredentialRegistration { + credentialID = credential.credentialID.base64url + } + else if let credential = credential as? ASAuthorizationPlatformPublicKeyCredentialAssertion { + credentialID = credential.credentialID.base64url + } + if credentialID.isEmpty { return } + + var credentials = getPlatformPublicKeyCredentials() + if !credentials.contains(credentialID) { + credentials.append(credentialID) + UserDefaults.standard.set(credentials, forKey: "platformPublicKeyCredentials") + } + } + + + @available(iOS 16.0, *) + private func handlePlatformExcludedCredentials(_ options: PublicKeyCredentialRequestOptions) throws { + let excludeCredentials = options.excludeCredentials + guard excludeCredentials.count > 0 else { return } + let credentials = getPlatformPublicKeyCredentials() + for credential in credentials { + for excludeCredential in excludeCredentials { + if excludeCredential.base64url == credential { + throw PluginError.excludedCredentialExists + } + } + } + } + + @available(iOS 16.0, *) + private func findPlatformCredentials(_ credentials: [Data]) -> [Data] { + guard credentials.count > 0 else { return [] } + var foundCredentials: [Data] = [] + let platformCredentials = getPlatformPublicKeyCredentials() + for credential in credentials { + for platformCredential in platformCredentials { + if credential.base64url == platformCredential { + foundCredentials.append(credential) + break + } + } + } + return foundCredentials + } + + @available(iOS 16.0, *) + private func createPlatformPublicKeyCredentialRegistrationRequest(_ options: PublicKeyCredentialRequestOptions) throws -> ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest { + let rpId = try options.rpId + let challenge = try options.challenge + let userName = try options.userName + let userId = try options.userId + let platformProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: rpId) + let registrationRequest = platformProvider.createCredentialRegistrationRequest(challenge: challenge, name: userName, userID: userId) + /*// Passkeys do not support attestation. + if let attestationPreference = options.attestation { + registrationRequest.attestationPreference = attestationPreference + } + */ + if let userVerificationPreference = options.userVerification { + registrationRequest.userVerificationPreference = userVerificationPreference + } + // ISSUE: Currently ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest doesn't support excludedCredentials. + // WORKAROUND: Handling excludeCredentials by plugin self. + try handlePlatformExcludedCredentials(options) + return registrationRequest + } + + @available(iOS 16.0, *) + private func createSecurityKeyPublicKeyCredentialRegistrationRequest(_ options: PublicKeyCredentialRequestOptions) throws -> ASAuthorizationSecurityKeyPublicKeyCredentialRegistrationRequest { + let rpId = try options.rpId + let challenge = try options.challenge + let userName = try options.userName + let userId = try options.userId + let securityKeyProvider = ASAuthorizationSecurityKeyPublicKeyCredentialProvider(relyingPartyIdentifier: rpId) + let registrationRequest = securityKeyProvider.createCredentialRegistrationRequest(challenge: challenge, displayName: userName, name: userName, userID: userId) + registrationRequest.credentialParameters = options.pubKeyCredParams + if let attestationPreference = options.attestation { + registrationRequest.attestationPreference = attestationPreference + } + if let userVerificationPreference = options.userVerification { + registrationRequest.userVerificationPreference = userVerificationPreference + } + if let residentKeyPreference = options.residentKey { + registrationRequest.residentKeyPreference = residentKeyPreference + } + let excludedCredentials = options.excludeCredentials + if excludedCredentials.count > 0 { + registrationRequest.excludedCredentials = excludedCredentials.map { ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor(credentialID: $0, transports: ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor.Transport.allSupported) } + } + return registrationRequest + } + + @available(iOS 16.0, *) + private func createPlatformPublicKeyCredentialAssertionRequest(_ options: PublicKeyCredentialRequestOptions) throws -> ASAuthorizationPlatformPublicKeyCredentialAssertionRequest { + let rpId = try options.rpId + let challenge = try options.challenge + let platformProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: rpId) + let assertionRequest = platformProvider.createCredentialAssertionRequest(challenge: challenge) + if let userVerificationPreference = options.userVerification { + assertionRequest.userVerificationPreference = userVerificationPreference + } + let allowedCredentials = options.allowCredentials + // ISSUE: if allowedCredentials are different from SecurityKeyPublicKeyCredentialAssertionRequest, iOS Passkeys will hang on processing in background. + if allowedCredentials.count > 0 { + assertionRequest.allowedCredentials = allowedCredentials.map { ASAuthorizationPlatformPublicKeyCredentialDescriptor(credentialID: $0) } + } + return assertionRequest + } + + @available(iOS 16.0, *) + private func createSecurityKeyPublicKeyCredentialAssertionRequest(_ options: PublicKeyCredentialRequestOptions) throws -> ASAuthorizationSecurityKeyPublicKeyCredentialAssertionRequest { + let rpId = try options.rpId + let challenge = try options.challenge + let securityKeyProvider = ASAuthorizationSecurityKeyPublicKeyCredentialProvider(relyingPartyIdentifier: rpId) + let assertionRequest = securityKeyProvider.createCredentialAssertionRequest(challenge: challenge) + if let userVerificationPreference = options.userVerification { + assertionRequest.userVerificationPreference = userVerificationPreference + } + let allowedCredentials = options.allowCredentials + if allowedCredentials.count > 0 { + assertionRequest.allowedCredentials = allowedCredentials.map { ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor(credentialID: $0, transports: ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor.Transport.allSupported) } + } + return assertionRequest + } + + @available(iOS 16.0, *) + private func requestCredential(_ authorizationRequests: [ASAuthorizationRequest]) throws -> ASAuthorizationCredential { + let authController = ASAuthorizationController(authorizationRequests: authorizationRequests) + let authCtrlDelete = AuthCtrlDelegate() + authController.delegate = authCtrlDelete + authController.performRequests() + + let result = try authCtrlDelete.getResult() + guard result.error == nil else { + throw result.error! + } + guard let authorization = result.authorization else { + throw PluginError.notFound("Credential") + } + // ISSUE: Currently ASAuthorizationPlatformPublicKeyCredentialRegistrationRequest doesn't support excludedCredentials. + // WORKAROUND: Handling excludeCredentials by plugin self. + // SIDE-EFFECT: Assertions may come from another device by scanning QR code, in that case, the credential SHALL NOT be saved, but there is no way to know the assertion source. This leads that credential registration fails if excludedCredentials includes the credential. + savePlatformPublicKeyCredential(authorization.credential) + return authorization.credential + } + + @available(iOS 16.0, *) + private func generateCredentialRegistrationResponse(_ credentialID: Data, _ rawClientDataJSON: Data, _ rawAttestationObject: Data? = nil) throws -> [String: Any] { + let response = [ + "id": credentialID.base64url, + "rawId": credentialID.base64url, + "type": "public-key", + "response": [ + "clientDataJSON": rawClientDataJSON.base64url, + "attestationObject": rawAttestationObject?.base64url ?? "null", + "transports": ASAuthorizationSecurityKeyPublicKeyCredentialDescriptor.Transport.allSupported.map { $0.rawValue }, + ] as [String : Any], + "clientExtensionResults": [:] as [String : Any] + ] as [String : Any] + return response + } + + @available(iOS 16.0, *) + private func generateCredentialAssertionResponse(_ credentialID: Data, _ userID: Data, _ signature: Data, _ rawAuthenticatorData: Data, _ rawClientDataJSON: Data) throws -> [String: Any] { + let response = [ + "id": credentialID.base64url, + "rawId": credentialID.base64url, + "type": "public-key", + "response": [ + "authenticatorData": rawAuthenticatorData.base64url, + "signature": signature.base64url, + "userHandle": userID.base64url, + "clientDataJSON": rawClientDataJSON.base64url, + ] as [String : Any], + "clientExtensionResults": [:] as [String : Any] + ] as [String : Any] + return response + } + + @available(iOS 16.0, *) + private func generateCredentialResponse(_ credential: ASAuthorizationCredential) throws -> [String: Any] { + var response: [String: Any] = [:] + switch (credential) { + case is ASAuthorizationPlatformPublicKeyCredentialRegistration: + let credential = credential as! ASAuthorizationPlatformPublicKeyCredentialRegistration + response = try generateCredentialRegistrationResponse(credential.credentialID, credential.rawClientDataJSON, credential.rawAttestationObject) + case is ASAuthorizationSecurityKeyPublicKeyCredentialRegistration: + let credential = credential as! ASAuthorizationSecurityKeyPublicKeyCredentialRegistration + response = try generateCredentialRegistrationResponse(credential.credentialID, credential.rawClientDataJSON, credential.rawAttestationObject) + case is ASAuthorizationPlatformPublicKeyCredentialAssertion: + let credential = credential as! ASAuthorizationPlatformPublicKeyCredentialAssertion + response = try generateCredentialAssertionResponse(credential.credentialID, credential.userID, credential.signature, credential.rawAuthenticatorData, credential.rawClientDataJSON) + case is ASAuthorizationSecurityKeyPublicKeyCredentialAssertion: + let credential = credential as! ASAuthorizationSecurityKeyPublicKeyCredentialAssertion + response = try generateCredentialAssertionResponse(credential.credentialID, credential.userID, credential.signature, credential.rawAuthenticatorData, credential.rawClientDataJSON) + default: + throw PluginError.unknownCredentialType(credential) + } + return response + } + + @available(iOS 16.0, *) + private func requestCredentialRegistration(_ options: PublicKeyCredentialRequestOptions) throws -> String { + var authorizationRequests: [ASAuthorizationRequest] = [] + switch options.authenticatorAttachment { + case .platform: + authorizationRequests.append(try createPlatformPublicKeyCredentialRegistrationRequest(options)) + case .crossPlatform: + authorizationRequests.append(try createSecurityKeyPublicKeyCredentialRegistrationRequest(options)) + default: + authorizationRequests.append(try createPlatformPublicKeyCredentialRegistrationRequest(options)) + authorizationRequests.append(try createSecurityKeyPublicKeyCredentialRegistrationRequest(options)) + } + let credential = try requestCredential(authorizationRequests) + let response = try generateCredentialResponse(credential) + return try response.jsonString + } + + @available(iOS 16.0, *) + private func requestCredentialAssertion(_ options: PublicKeyCredentialRequestOptions) throws -> String { + var authorizationRequests: [ASAuthorizationRequest] = [] + switch options.authenticatorAttachment { + case .platform: + authorizationRequests.append(try createPlatformPublicKeyCredentialAssertionRequest(options)) + case .crossPlatform: + authorizationRequests.append(try createSecurityKeyPublicKeyCredentialAssertionRequest(options)) + default: + authorizationRequests.append(try createPlatformPublicKeyCredentialAssertionRequest(options)) + /* ISSUE: + * iOS(16.4.1) will show QR code for Passkeys if adding both PlatformPublicKeyCredentialAssertionRequest and + * SecurityKeyPublicKeyCredentialAssertionRequest into authorizationRequests and allowCredentials is not empty. + * It seems that allowCredentials cannot be found in the platform credeintials, but it will work if removing + * SecurityKeyPublicKeyCredentialAssertionRequest from authorizationRequests, that means allowCredentials can be found. + */ + // WORKAROUND: Adding SecurityKeyPublicKeyCredentialAssertionRequest if there is no any platform credeintial can be found in allowCredentials. But the saved platform credentials are not all on the device, due to there is no way can get all platform credentials from iOS. + if findPlatformCredentials(options.allowCredentials).count == 0 { + authorizationRequests.append(try createSecurityKeyPublicKeyCredentialAssertionRequest(options)) + } + } + let credential = try requestCredential(authorizationRequests) + let response = try generateCredentialResponse(credential) + return try response.jsonString + } + + @available(iOS 16.0, *) + public func createCredential(_ options: String, _ callback: @escaping (_ credential: String?, _ error: Error?) -> Void) { + Task { + do { + let credential = try requestCredentialRegistration(PublicKeyCredentialRequestOptions(options)) + callback(credential, nil) + } catch { + callback(nil, error) + } + } + } + + @available(iOS 16.0, *) + public func getCredential(_ options: String, _ callback: @escaping (_ credential: String?, _ error: Error?) -> Void) { + Task { + do { + let credential = try requestCredentialAssertion(PublicKeyCredentialRequestOptions(options)) + callback(credential, nil) + } catch { + callback(nil, error) + } + } + } +} + +@available(iOS 16.0, *) +extension DerivPasskeysManager.PluginError: CustomStringConvertible { + var description: String { + switch self { + case .unknownError: + return "Unknown error." + case .notFound(let name): + return "\(name) not found." + case .unknownCredentialType(let object): + return "Unknown credential type \(String(describing: type(of: object)))." + case .excludedCredentialExists: + return "One of the excluded credentials exists on the local device." + } + } +} + +@available(iOS 16.0, *) +extension DerivPasskeysManager.PluginError: LocalizedError { + private var errorDescription: String { + return self.description + } +} + +@available(iOS 16.0, *) +extension FlutterViewController: ASAuthorizationControllerPresentationContextProviding { + public func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { + return self.view.window! + } +} + +@available(iOS 16.0, *) +extension Data { + var base64url: String { + self.base64EncodedString().replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "=", with: "") + } +} + +@available(iOS 16.0, *) +extension Dictionary where Key == String { + var jsonString: String { + get throws { + let data = try JSONSerialization.data(withJSONObject: self) + guard let jsonString = String(data: data, encoding: .utf8) else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Invalid JSON data.", underlyingError: nil)) + } + return jsonString + } + } +} diff --git a/packages/deriv_passkeys/ios/Classes/DerivPasskeysPlugin.swift b/packages/deriv_passkeys/ios/Classes/DerivPasskeysPlugin.swift new file mode 100644 index 000000000..b2cdeb88a --- /dev/null +++ b/packages/deriv_passkeys/ios/Classes/DerivPasskeysPlugin.swift @@ -0,0 +1,72 @@ +import Flutter +import UIKit + +public class DerivPasskeysPlugin: NSObject, FlutterPlugin { + + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel( + name: "deriv_passkeys", binaryMessenger: registrar.messenger()) + let instance = DerivPasskeysPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + + switch call.method { + case "isPlatformSupported": + if #available(iOS 16.0, *) { + result(true) + } else { + result(false) + } + case "createCredential": + if #available(iOS 16.0, *) { + let derivPasskeysManager = DerivPasskeysManager() + if let args = call.arguments as? [String: Any], let options = args["options"] as? String { + derivPasskeysManager.createCredential(options) { credential, error in + if let err = error { + let errorCode = (err as NSError).code + result(FlutterError(code: String(errorCode), message: "\(err)", details: nil)) + return + } + result(credential!) + } + } else { + result( + FlutterError(code: "CreateCredentialError", message: "Options not found", details: nil)) + } + } else { + result( + FlutterError( + code: "UNAVAILABLE", + message: "Passkey creation not available on this iOS version", + details: nil)) + } + case "getCredential": + if #available(iOS 16.0, *) { + let derivPasskeysManager = DerivPasskeysManager() + if let args = call.arguments as? [String: Any], let options = args["options"] as? String { + derivPasskeysManager.getCredential(options) { credential, error in + if let err = error { + let errorCode = (err as NSError).code + result(FlutterError(code: String(errorCode), message: "\(err)", details: nil)) + return + } + result(credential!) + } + } else { + result( + FlutterError(code: "GetCredentialError", message: "Options not found", details: nil)) + } + } else { + result( + FlutterError( + code: "UNAVAILABLE", + message: "Passkey retrieval not available on this iOS version", + details: nil)) + } + default: + result(FlutterMethodNotImplemented) + } + } +} diff --git a/packages/deriv_passkeys/ios/Classes/PublicKeyCredentialRequestOptions.swift b/packages/deriv_passkeys/ios/Classes/PublicKeyCredentialRequestOptions.swift new file mode 100644 index 000000000..455ca75ca --- /dev/null +++ b/packages/deriv_passkeys/ios/Classes/PublicKeyCredentialRequestOptions.swift @@ -0,0 +1,215 @@ +import AuthenticationServices + +@available(iOS 16.0, *) +public class PublicKeyCredentialRequestOptions: NSObject { + + enum OptionError: Error { + case notFound(String) + case invalidValue(String) + } + + enum AuthenticatorAttachment: String { + case platform = "platform" + case crossPlatform = "cross-platform" + } + + let options: [String: Any] + + init(_ jsonObject: [String: Any]) { + self.options = jsonObject + } + + convenience init (_ jsonString: String) throws { + self.init(try jsonString.jsonObject) + } + + var challenge: Data { + get throws { + guard let challengeStr = self.options["challenge"] as? String, let challenge = challengeStr.base64urlDecoded else { + throw OptionError.notFound("challenge") + } + return challenge + } + } + + var rpId: String { + get throws { + var rpId = "" + if let rpObj = self.options["rp"] as? [String: Any], let id = rpObj["id"] as? String { rpId = id } + if let id = self.options["rpId"] as? String { rpId = id } + if rpId.isEmpty { + throw OptionError.notFound("rpId") + } + return rpId + } + } + + var userName: String { + get throws { + guard let userObj = self.options["user"] as? [String: Any], let userName = userObj["name"] as? String else { + throw OptionError.notFound("userName") + } + return userName + } + } + + var userId: Data { + get throws { + guard let userObj = self.options["user"] as? [String: Any], let id = userObj["id"] as? String, let userId = id.data(using: .utf8) else { + throw OptionError.notFound("userId") + } + return userId + } + } + + var pubKeyCredParams: [ASAuthorizationPublicKeyCredentialParameters] { + var _pubKeyCredParams: [ASAuthorizationPublicKeyCredentialParameters] = [] + if let pubKeyCredParamsObj = self.options["pubKeyCredParams"] as? [[String: Any]] { + for paramObj in pubKeyCredParamsObj { + if let alg = paramObj["alg"] as? Int { + _pubKeyCredParams.append(ASAuthorizationPublicKeyCredentialParameters(algorithm: ASCOSEAlgorithmIdentifier(alg))) + } + } + } + return _pubKeyCredParams + } + + var authenticatorAttachment: AuthenticatorAttachment? { + guard let authenticatorSelectionObj = self.options["authenticatorSelection"] as? [String: Any] else { return nil } + guard let _authenticatorAttachment = authenticatorSelectionObj["authenticatorAttachment"] as? String else { return nil } + switch _authenticatorAttachment { + case AuthenticatorAttachment.platform.rawValue: + return AuthenticatorAttachment.platform + case AuthenticatorAttachment.crossPlatform.rawValue: + return AuthenticatorAttachment.crossPlatform + default: + return nil + } + } + + var attestation: ASAuthorizationPublicKeyCredentialAttestationKind? { + switch self.options["attestation"] as? String { + case "none": + return ASAuthorizationPublicKeyCredentialAttestationKind.none + case "indirect": + return ASAuthorizationPublicKeyCredentialAttestationKind.indirect + case "direct": + return ASAuthorizationPublicKeyCredentialAttestationKind.direct + case "enterprise": + return ASAuthorizationPublicKeyCredentialAttestationKind.enterprise + default: + return nil + } + } + + var userVerification: ASAuthorizationPublicKeyCredentialUserVerificationPreference? { + var _userVerification = "" + if let authenticatorSelectionObj = self.options["authenticatorSelection"] as? [String: Any], let uv = authenticatorSelectionObj["userVerification"] as? String { + _userVerification = uv + } + else if let uv = self.options["authenticatorSelection"] as? String { + _userVerification = uv + } + switch _userVerification { + case "discouraged": + return ASAuthorizationPublicKeyCredentialUserVerificationPreference.discouraged + case "preferred": + return ASAuthorizationPublicKeyCredentialUserVerificationPreference.preferred + case "required": + return ASAuthorizationPublicKeyCredentialUserVerificationPreference.required + default: + return nil + } + } + + var residentKey: ASAuthorizationPublicKeyCredentialResidentKeyPreference? { + var _residentKey = "" + if let authenticatorSelectionObj = self.options["authenticatorSelection"] as? [String: Any] { + if let rk = authenticatorSelectionObj["residentKey"] as? String { + _residentKey = rk + } + else if (authenticatorSelectionObj["requireResidentKey"] as? Bool) == true { + _residentKey = "required" + } + } + switch _residentKey { + case "discouraged": + return ASAuthorizationPublicKeyCredentialResidentKeyPreference.discouraged + case "preferred": + return ASAuthorizationPublicKeyCredentialResidentKeyPreference.preferred + case "required": + return ASAuthorizationPublicKeyCredentialResidentKeyPreference.required + default: + return nil + } + } + + var excludeCredentials: [Data] { + get { + var _excludeCredentials: [Data] = [] + if let excludeCredentialsObj = self.options["excludeCredentials"] as? [[String: Any]] { + for credentialObj in excludeCredentialsObj { + if let id = credentialObj["id"] as? String, let data = id.base64urlDecoded { + _excludeCredentials.append(data) + } + } + } + return _excludeCredentials + } + } + + var allowCredentials: [Data] { + get { + var _allowCredentials: [Data] = [] + if let allowCredentialsObj = self.options["allowCredentials"] as? [[String: Any]] { + for credentialObj in allowCredentialsObj { + if let id = credentialObj["id"] as? String, let data = id.base64urlDecoded { + _allowCredentials.append(data) + } + } + } + return _allowCredentials + } + } +} + +@available(iOS 16.0, *) +extension PublicKeyCredentialRequestOptions.OptionError: CustomStringConvertible { + var description: String { + switch self { + case .notFound(let name): + return "\(name) not found." + case .invalidValue(let value): + return "Invalid value '\(value)'." + } + } +} + +@available(iOS 16.0, *) +extension PublicKeyCredentialRequestOptions.OptionError: LocalizedError { + private var errorDescription: String { + return self.description + } +} + +@available(iOS 16.0, *) +extension String { + var base64urlDecoded: Data? { + var base64 = self.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/") + if base64.count % 4 != 0 { base64.append(String(repeating: "=", count: 4 - base64.count % 4)) } + return Data(base64Encoded: base64) + } + + var jsonObject: [String: Any] { + get throws { + guard let data = self.data(using: .utf8) else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Invalid JSON string.", underlyingError: nil)) + } + let object = try JSONSerialization.jsonObject(with: data) + guard let jsonObject = object as? [String: Any] else { + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Invalid JSON string.", underlyingError: nil)) + } + return jsonObject + } + } +} diff --git a/packages/deriv_passkeys/ios/deriv_passkeys.podspec b/packages/deriv_passkeys/ios/deriv_passkeys.podspec new file mode 100644 index 000000000..4b8eaa030 --- /dev/null +++ b/packages/deriv_passkeys/ios/deriv_passkeys.podspec @@ -0,0 +1,23 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint deriv_passkeys.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'deriv_passkeys' + s.version = '1.0.0' + s.summary = 'Deriv Passkeys for iOS.' + s.description = <<-DESC +A new Flutter project. + DESC + s.homepage = 'https://deriv.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Deriv' => 'email@example.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'Flutter' + s.platform = :ios, '13.0' + + # 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/packages/deriv_passkeys/lib/deriv_passkeys.dart b/packages/deriv_passkeys/lib/deriv_passkeys.dart new file mode 100644 index 000000000..2476c43e8 --- /dev/null +++ b/packages/deriv_passkeys/lib/deriv_passkeys.dart @@ -0,0 +1,7 @@ +library deriv_passkeys; + +export './src/presentation/presentation.dart'; +export './src/domain/domain.dart'; +export './src/interactor/interactor.dart'; +export './src/data/data.dart'; +export './src/domain/entities/account_entity.dart'; diff --git a/packages/deriv_passkeys/lib/src/core/constants/analytics_actions_enums.dart b/packages/deriv_passkeys/lib/src/core/constants/analytics_actions_enums.dart new file mode 100644 index 000000000..4fa6a010b --- /dev/null +++ b/packages/deriv_passkeys/lib/src/core/constants/analytics_actions_enums.dart @@ -0,0 +1,59 @@ +/// The list of actions that can be performed on the Effortless page. +enum EffortlessPageActions { + /// The user opened the effortless login page. + openEffortlessLoginPage, + + /// The user closed the effortless login page. + closeEffortlessLoginPage, + + /// The user pressed the maybe later button. + maybeLater, +} + +/// The list of actions that can be performed on the Learn more page. +enum LearnMorePageActions { + /// The user opened the learn more page. + openLearnMorePage, + + /// The user closed the learn more page. + closeLearnMorePage, +} + +/// The list of actions that can be performed on the Create passkey page. +enum CreatePasskeyFlowActions { + /// The user pressed the create passkey button. + createPasskey, + + /// Create passkey success. + createPasskeySuccess, + + /// The user faces an error. + error, + + /// The user presses the continue trading button. + continueTrading, + + /// The user presses the add more passkeys button. + addMorePasskeys, +} + +/// The list of actions that can be performed on the Rename passkey page. +enum RenamePasskeyFlowActions { + /// The user presses the rename passkey button. + renamePasskey, + + /// The user cancels rename passkey. + cancelRenamePasskey, + + /// Rename passkey success. + renamePasskeySuccess, +} + +/// The list of actions that can be performed on the Manage Passkeys page. +enum ManagePasskeysPageActions { + /// The user opened the manage passkeys page. + openManagePasskeysPage, + + /// The user closed the manage passkeys page. + closeManagePasskeysPage, +} diff --git a/packages/deriv_passkeys/lib/src/core/constants/tracking_event_params_helper.dart b/packages/deriv_passkeys/lib/src/core/constants/tracking_event_params_helper.dart new file mode 100644 index 000000000..5693a7a83 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/core/constants/tracking_event_params_helper.dart @@ -0,0 +1,158 @@ +import 'package:deriv_passkeys/src/core/constants/analytics_actions_enums.dart'; +import 'package:flutter/foundation.dart'; + +/// Platform type string. +String _platform = defaultTargetPlatform == TargetPlatform.android + ? 'Android' + : defaultTargetPlatform == TargetPlatform.iOS + ? 'IOS' + : 'other'; + +/// Function to get all the Effortless page params. +Map getEffortlessLoginTrackingParams(T eventName) => + switch (eventName) { + EffortlessPageActions.openEffortlessLoginPage => { + 'event_name': 'open', + 'params': { + 'form_name': 'ce_passkey_effortless_form_', + 'operating_system': _platform, + }, + }, + EffortlessPageActions.closeEffortlessLoginPage => { + 'event_name': 'close', + 'params': { + 'form_name': 'ce_passkey_effortless_form_', + 'operating_system': _platform, + }, + }, + EffortlessPageActions.maybeLater => { + 'event_name': 'maybe_later', + 'params': { + 'form_name': 'ce_passkey_effortless_form_', + 'operating_system': _platform, + }, + }, + _ => throw Exception('Invalid event name'), + }; + +/// Function to get all the Learn More page params. +Map getLearnMoreTrackingParams( + T eventName, + String mainFormName, +) => + switch (eventName) { + LearnMorePageActions.openLearnMorePage => { + 'event_name': 'info_open', + 'params': { + 'form_name': mainFormName, + 'operating_system': _platform, + }, + }, + LearnMorePageActions.closeLearnMorePage => { + 'event_name': 'info_back', + 'params': { + 'form_name': mainFormName, + 'operating_system': _platform, + }, + }, + _ => throw Exception('Invalid event name'), + }; + +/// Function to get all the Manage passkeys page params. +Map getManagePasskeysTrackingParams(T eventName) => + switch (eventName) { + ManagePasskeysPageActions.openManagePasskeysPage => { + 'event_name': 'open', + 'params': { + 'form_name': 'ce_passkey_account_settings_form_', + 'operating_system': _platform, + }, + }, + ManagePasskeysPageActions.closeManagePasskeysPage => { + 'event_name': 'close', + 'params': { + 'form_name': 'ce_passkey_account_settings_form_', + 'operating_system': _platform, + }, + }, + _ => throw Exception('Invalid event name'), + }; + +/// Function to get all the Create passkey page params. +Map getCreatePasskeyTrackingParams( + T eventName, { + String? mainFormName, + String? subFormName, + String? errorMessage, +}) => + switch (eventName) { + CreatePasskeyFlowActions.createPasskey => { + 'event_name': 'create_passkey_started', + 'params': { + 'form_name': mainFormName, + 'subform_name': subFormName, + 'operating_system': _platform, + }, + }, + CreatePasskeyFlowActions.createPasskeySuccess => { + 'event_name': 'create_passkey_finished', + 'params': { + 'form_name': mainFormName, + 'subform_name': subFormName, + 'operating_system': _platform, + }, + }, + CreatePasskeyFlowActions.error => { + 'event_name': 'error', + 'params': { + 'form_name': mainFormName, + 'subform_name': subFormName, + 'operating_system': _platform, + 'error_message': errorMessage, + }, + }, + CreatePasskeyFlowActions.continueTrading => { + 'event_name': 'create_passkey_continue_trading', + 'params': { + 'form_name': mainFormName, + 'subform_name': subFormName, + 'operating_system': _platform, + }, + }, + CreatePasskeyFlowActions.addMorePasskeys => { + 'event_name': 'create_passkey_add_more_passkeys', + 'params': { + 'form_name': mainFormName, + 'subform_name': subFormName, + 'operating_system': _platform, + }, + }, + _ => throw Exception('Invalid event name'), + }; + +/// Function to get all the Rename passkey page params. +Map getRenamePasskeyTrackingParams(T eventName) => + switch (eventName) { + RenamePasskeyFlowActions.renamePasskey => { + 'event_name': 'passkey_rename_open', + 'params': { + 'form_name': 'ce_passkey_account_settings_form_', + 'operating_system': _platform, + }, + }, + RenamePasskeyFlowActions.renamePasskeySuccess => { + 'event_name': 'passkey_rename_success', + 'params': { + 'form_name': 'ce_passkey_account_settings_form_', + 'operating_system': _platform, + }, + }, + RenamePasskeyFlowActions.cancelRenamePasskey => { + 'event_name': 'passkey_rename_back', + 'params': { + 'form_name': 'ce_passkey_account_settings_form_', + 'operating_system': _platform, + }, + }, + _ => throw Exception('Invalid event name'), + }; diff --git a/packages/deriv_passkeys/lib/src/core/extensions/context_extensions.dart b/packages/deriv_passkeys/lib/src/core/extensions/context_extensions.dart new file mode 100644 index 000000000..4b3dcfe65 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/core/extensions/context_extensions.dart @@ -0,0 +1,9 @@ +import 'package:deriv_localizations/l10n/generated/deriv_passkeys/deriv_passkeys_localizations.dart'; +import 'package:flutter/widgets.dart'; + +/// Extension for [BuildContext] +extension ContextExtension on BuildContext { + /// Get derivPasskeysLocalizations. + DerivPasskeysLocalizations get derivPasskeysLocalizations => + DerivPasskeysLocalizations.of(this); +} diff --git a/packages/deriv_passkeys/lib/src/data/data.dart b/packages/deriv_passkeys/lib/src/data/data.dart new file mode 100644 index 000000000..450421bc6 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/data/data.dart @@ -0,0 +1,5 @@ +library deriv_passkeys; + +export './repositories/deriv_passkeys_repository.dart'; +export './data_sources/deriv_passkeys_data_source.dart'; +export './mappers/deriv_passkeys_mapper.dart'; diff --git a/packages/deriv_passkeys/lib/src/data/data_sources/base_deriv_passkeys_data_source.dart b/packages/deriv_passkeys/lib/src/data/data_sources/base_deriv_passkeys_data_source.dart new file mode 100644 index 000000000..80ba174cd --- /dev/null +++ b/packages/deriv_passkeys/lib/src/data/data_sources/base_deriv_passkeys_data_source.dart @@ -0,0 +1,48 @@ +import 'package:deriv_http_client/deriv_http_client.dart'; +import 'package:deriv_passkeys/src/data/mappers/deriv_passkeys_mapper.dart'; +import 'package:deriv_passkeys/src/data/models/passkeys_connection_info_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkey_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkeys_options_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkeys_register_options_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkeys_verify_credentials_request_body_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkeys_verify_credentials_response_model.dart'; +import 'package:flutter_deriv_api/basic_api/generated/passkeys_register_send.dart'; + +/// [BaseDerivPasskeysDataSource] defines a contract for all DerivPasskeys data sources. +/// +/// All DerivPasskeys data sources should extend this class, and implement its abstract methods. +abstract class BaseDerivPasskeysDataSource { + /// Creates a [BaseDerivPasskeysDataSource]. + BaseDerivPasskeysDataSource({required this.mapper, required this.client}); + + /// The mapper used to map the data source to the domain. + final DerivPasskeysMapper mapper; + + /// The http client used to make http requests. + BaseHttpClient client; + + /// Get options for authentication with DerivPasskeys. + Future getOptions({ + required PasskeysConnectionInfoModel passkeysConnectionInfoModel, + }); + + /// Verify the user's DerivPasskeys credentials. + Future verifyCredentials({ + required DerivPasskeysVerifyCredentialsRequest requestBodyModel, + required String jwtToken, + required PasskeysConnectionInfoModel passkeysConnectionInfoModel, + String? userAgent, + }); + + /// Get options for registration with DerivPasskeys. + Future getRegisterOptions({ + String? loginId, + }); + + /// Register credentials with DerivPasskeys. + Future registerCredentials( + PasskeysRegisterRequest request); + + /// Get passkeys list. + Future> getPasskeysList({String? loginId}); +} diff --git a/packages/deriv_passkeys/lib/src/data/data_sources/deriv_passkeys_data_source.dart b/packages/deriv_passkeys/lib/src/data/data_sources/deriv_passkeys_data_source.dart new file mode 100644 index 000000000..4711f7e01 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/data/data_sources/deriv_passkeys_data_source.dart @@ -0,0 +1,168 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:deriv_http_client/deriv_http_client.dart'; +import 'package:flutter_deriv_api/api/exceptions/base_api_exception.dart'; +import 'package:flutter_deriv_api/api/response/passkeys_list_response_extended.dart'; +import 'package:flutter_deriv_api/api/response/passkeys_register_options_response_extended.dart'; +import 'package:flutter_deriv_api/api/response/passkeys_register_response_extended.dart'; +import 'package:flutter_deriv_api/basic_api/generated/passkeys_list_receive.dart'; +import 'package:flutter_deriv_api/basic_api/generated/passkeys_list_send.dart'; +import 'package:flutter_deriv_api/basic_api/generated/passkeys_register_options_receive.dart'; +import 'package:flutter_deriv_api/basic_api/generated/passkeys_register_options_send.dart'; +import 'package:flutter_deriv_api/basic_api/generated/passkeys_register_receive.dart'; +import 'package:flutter_deriv_api/basic_api/generated/passkeys_register_send.dart'; + +import 'package:http/http.dart' as http; + +import '../data_sources/base_deriv_passkeys_data_source.dart'; +import '../models/passkeys_connection_info_model.dart'; +import '../models/deriv_passkey_model.dart'; +import '../models/deriv_passkeys_options_model.dart'; +import '../models/deriv_passkeys_register_options_model.dart'; +import '../models/deriv_passkeys_verify_credentials_request_body_model.dart'; +import '../models/deriv_passkeys_verify_credentials_response_model.dart'; +import '../../exceptions/server_exceptions.dart'; + +/// [DerivPasskeysDataSource] extends and implements [BaseDerivPasskeysDataSource]. +class DerivPasskeysDataSource extends BaseDerivPasskeysDataSource { + /// Creates a [DerivPasskeysDataSource]. + DerivPasskeysDataSource({required super.mapper, required super.client}); + + @override + Future getOptions({ + required PasskeysConnectionInfoModel passkeysConnectionInfoModel, + }) async { + final String url = + 'https://${passkeysConnectionInfoModel.endpoint}/oauth2/api/v1/passkeys/login/options'; + final http.Response response = await client.get( + url, + ); + + if (response.statusCode == 200) { + return DerivPasskeysOptionsModel.fromJson(jsonDecode(response.body)); + } else { + final Map jsonDecodedResponse = + jsonDecode(response.body); + if (jsonDecodedResponse.containsKey('error_code')) { + throw ServerException( + errorCode: jsonDecodedResponse['error_code'], + message: jsonDecodedResponse['message'], + ); + } + throw Exception('Failed to load options!'); + } + } + + @override + Future verifyCredentials({ + required DerivPasskeysVerifyCredentialsRequest requestBodyModel, + required String jwtToken, + required PasskeysConnectionInfoModel passkeysConnectionInfoModel, + String? userAgent, + }) async { + try { + final String url = + 'https://${passkeysConnectionInfoModel.endpoint}/oauth2/api/v1/passkeys/login/verify'; + + final Map headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $jwtToken', + 'User-Agent': userAgent ?? 'Dart/3.0 (dart:io)', + 'accept': 'application/json' + }; + final Map jsonDecodedResponse = await client.post( + url: url, + headers: headers, + jsonBody: requestBodyModel.toJson(), + ); + + if (jsonDecodedResponse.containsKey('error_code')) { + throw ServerException( + errorCode: jsonDecodedResponse['error_code'], + message: jsonDecodedResponse['message'], + ); + } + + return DerivPasskeysVerifyCredentialsResponseModel( + response: jsonDecodedResponse, + ); + } on HTTPClientException catch (e) { + throw ServerException( + errorCode: e.errorCode ?? '', + message: e.message, + ); + } + } + + @override + Future getRegisterOptions({ + String? loginId, + }) async { + try { + final PasskeysRegisterOptionsReceive response = + await PasskeysRegisterOptionsResponseExtended.fetchRaw( + PasskeysRegisterOptionsRequest(loginid: loginId), + ); + + if (response.passkeysRegisterOptions == null) { + throw Exception('Failed to load register options!'); + } + + return DerivPasskeysRegisterOptionsModel( + options: response.passkeysRegisterOptions!['publicKey']); + } on BaseAPIException catch (e) { + throw ServerException( + errorCode: e.code ?? '', + message: e.message ?? '', + ); + } + } + + @override + Future registerCredentials( + PasskeysRegisterRequest request) async { + try { + final PasskeysRegisterReceive response = + await PasskeysRegisterResponseExtended.fetchRaw(request); + + if (response.passkeysRegister == null) { + throw Exception('Failed to register credentials!'); + } + + return DerivPasskeyModel.fromJson( + response.passkeysRegister!['properties']); + } on BaseAPIException catch (e) { + throw ServerException( + errorCode: e.code ?? '', + message: e.message ?? '', + ); + } + } + + @override + Future> getPasskeysList({String? loginId}) async { + try { + final PasskeysListReceive response = + await PasskeysListResponseExtended.fetchRaw( + PasskeysListRequest(loginid: loginId), + ); + + if (response.passkeysList == null) { + throw Exception('Failed to load passkeys list!'); + } + + final List passkeys = []; + for (final Map passkey in response.passkeysList!) { + passkeys.add(DerivPasskeyModel.fromJson(passkey)); + } + + return passkeys; + } on BaseAPIException catch (e) { + throw ServerException( + errorCode: e.code ?? '', + message: e.message ?? '', + ); + } + } +} diff --git a/packages/deriv_passkeys/lib/src/data/mappers/deriv_passkeys_mapper.dart b/packages/deriv_passkeys/lib/src/data/mappers/deriv_passkeys_mapper.dart new file mode 100644 index 000000000..923c90c5a --- /dev/null +++ b/packages/deriv_passkeys/lib/src/data/mappers/deriv_passkeys_mapper.dart @@ -0,0 +1,72 @@ +import 'package:deriv_passkeys/src/data/models/passkeys_connection_info_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkey_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkeys_options_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkeys_register_options_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkeys_verify_credentials_request_body_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkeys_verify_credentials_response_model.dart'; +import 'package:deriv_passkeys/src/domain/entities/account_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/passkeys_connection_info_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkey_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_options_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_register_credentials_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_register_options_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_verify_credentials_request_body_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_verify_credentials_response_entity.dart'; +import 'package:flutter_deriv_api/basic_api/generated/passkeys_register_send.dart'; + +/// [DerivPasskeysMapper] maps [DerivPasskeysOptionsModel] to [DerivPasskeysOptionsEntity]. +class DerivPasskeysMapper { + /// Maps [DerivPasskeysOptionsModel] to [DerivPasskeysOptionsEntity]. + DerivPasskeysOptionsEntity mapDerivPasskeysOptionsModel( + DerivPasskeysOptionsModel model) => + DerivPasskeysOptionsEntity.fromJson(model.toJson()); + + /// Maps [DerivPasskeysVerifyCredentialsRequest] to [DerivPasskeysVerifyCredentialsRequestBodyEntity]. + DerivPasskeysVerifyCredentialsRequest + mapDerivPasskeysVerifyCredentialsRequestBodyEntity( + DerivPasskeysVerifyCredentialsRequestBodyEntity entity) => + DerivPasskeysVerifyCredentialsRequest.fromJson(entity.toJson()); + + /// Maps [DerivPasskeysRegisterOptionsModel] to [DerivPasskeysRegisterOptionsEntity]. + DerivPasskeysRegisterOptionsEntity mapDerivPasskeysRegisterOptionsModel( + DerivPasskeysRegisterOptionsModel model) => + DerivPasskeysRegisterOptionsEntity.fromJson(model.toJson()); + + /// Maps [DerivPasskeyModel] to [DerivPasskeyEntity]. + DerivPasskeyEntity mapDerivPasskeyModel(DerivPasskeyModel model) => + DerivPasskeyEntity.fromJson( + model.toJson(), + ); + + /// Maps [DerivPasskeysRegisterCredentialsEntity] to [PasskeysRegisterRequest]. + PasskeysRegisterRequest mapDerivPasskeysRegisterCredentialsEntity( + DerivPasskeysRegisterCredentialsEntity entity) => + PasskeysRegisterRequest( + name: entity.name, + publicKeyCredential: entity.publicKeyCredential, + ); + + /// Maps [DerivPasskeysVerifyCredentialsResponseModel] to [DerivPasskeysVerifyCredentialsResponseEntity]. + DerivPasskeysVerifyCredentialsResponseEntity + mapDerivPasskeysVerifyCredentialsResponseModel( + DerivPasskeysVerifyCredentialsResponseModel model) => + DerivPasskeysVerifyCredentialsResponseEntity( + accounts: + (model.response['tokens'] as List).map((dynamic e) { + final Map map = e as Map; + return AccountEntity( + loginId: map['loginid'] as String, + token: map['token'] as String, + ); + }).toList(), + refreshToken: model.response['refresh_token'] as String, + ); + + /// Maps [ConnectionInfoEntity] to [PasskeysConnectionInfoModel]. + PasskeysConnectionInfoModel mapConnectionInfoEntity( + PasskeysConnectionInfoEntity entity) => + PasskeysConnectionInfoModel( + endpoint: entity.endpoint, + appId: entity.appId, + ); +} diff --git a/packages/deriv_passkeys/lib/src/data/models/deriv_passkey_model.dart b/packages/deriv_passkeys/lib/src/data/models/deriv_passkey_model.dart new file mode 100644 index 000000000..c0fc61836 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/data/models/deriv_passkey_model.dart @@ -0,0 +1,58 @@ +import 'package:equatable/equatable.dart'; + +/// [DerivPasskeyModel] DTO for DerivPasskey data coming from the WebsocketAPI. +class DerivPasskeyModel extends Equatable { + /// Creates a [DerivPasskeyModel]. + const DerivPasskeyModel({ + required this.createdAt, + required this.id, + required this.lastUsed, + required this.name, + required this.passkeyId, + required this.storedOn, + }); + + /// Creates a [DerivPasskeyModel] from a JSON object. + factory DerivPasskeyModel.fromJson(Map json) => + DerivPasskeyModel( + createdAt: json['created_at'] as int, + id: json['id'].toString(), + lastUsed: + (json['last_used'] as int?) == 0 ? null : json['last_used'] as int?, + name: json['name'] as String, + passkeyId: json['passkey_id'] as String, + storedOn: json['stored_on'] as String, + ); + + /// Creates a JSON object from a [DerivPasskeyModel]. + Map toJson() => { + 'created_at': createdAt, + 'id': id, + 'last_used': lastUsed, + 'name': name, + 'passkey_id': passkeyId, + 'stored_on': storedOn, + }; + + /// [createdAt] is the creation date. + final int createdAt; + + /// [id] is the passkey ID. + final String id; + + /// [lastUsed] is the last used date. + final int? lastUsed; + + /// [name] is the passkey name. + final String name; + + /// [passkeyId] is the passkey ID. + final String passkeyId; + + /// [storedOn] is the device the passkey is stored on. + final String storedOn; + + @override + List get props => + [createdAt, id, lastUsed, name, passkeyId, storedOn]; +} diff --git a/packages/deriv_passkeys/lib/src/data/models/deriv_passkeys_options_model.dart b/packages/deriv_passkeys/lib/src/data/models/deriv_passkeys_options_model.dart new file mode 100644 index 000000000..55994a2ca --- /dev/null +++ b/packages/deriv_passkeys/lib/src/data/models/deriv_passkeys_options_model.dart @@ -0,0 +1,51 @@ +/// [DerivPasskeysOptionsModel] DTO for DerivPasskeysOptions data coming from the WebsocketAPI. +final class DerivPasskeysOptionsModel { + /// Creates a [DerivPasskeysOptionsModel]. + /// [challenge] is the challenge string. + /// [rpId] is the relying party ID. + /// [timeout] is the timeout in seconds. + /// [userVerification] is the user verification method. + DerivPasskeysOptionsModel({ + required this.challenge, + required this.rpId, + required this.timeout, + required this.userVerification, + required this.allowCredentials, + }); + + /// Creates a [DerivPasskeysOptionsModel] from a JSON object. + factory DerivPasskeysOptionsModel.fromJson(Map json) => + DerivPasskeysOptionsModel( + challenge: json['publicKey']['challenge'] as String, + rpId: json['publicKey']['rpId'] as String, + timeout: json['publicKey']['timeout'] as int, + userVerification: json['publicKey']['userVerification'] as String, + allowCredentials: + json['publicKey']['allowCredentials'] as List? ?? + [], + ); + + /// [challenge] is the challenge string. + final String challenge; + + /// [rpId] is the relying party ID. + final String rpId; + + /// [timeout] is the timeout in seconds. + final int timeout; + + /// [userVerification] is the user verification method. + final String userVerification; + + /// [allowCredentials] is the allowed credentials. + final List allowCredentials; + + /// Creates a JSON object from a [DerivPasskeysOptionsModel]. + Map toJson() => { + 'challenge': challenge, + 'rpId': rpId, + 'timeout': timeout, + 'userVerification': userVerification, + 'allowCredentials': allowCredentials, + }; +} diff --git a/packages/deriv_passkeys/lib/src/data/models/deriv_passkeys_register_options_model.dart b/packages/deriv_passkeys/lib/src/data/models/deriv_passkeys_register_options_model.dart new file mode 100644 index 000000000..3e2cf2028 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/data/models/deriv_passkeys_register_options_model.dart @@ -0,0 +1,18 @@ +/// [DerivPasskeysRegisterOptionsModel] DTO for DerivPasskeysOptions data coming from the WebsocketAPI. +final class DerivPasskeysRegisterOptionsModel { + /// Creates a [DerivPasskeysRegisterOptionsModel]. + DerivPasskeysRegisterOptionsModel({ + required this.options, + }); + + /// Creates a [DerivPasskeysRegisterOptionsModel] from a JSON object. + factory DerivPasskeysRegisterOptionsModel.fromJson( + Map json) => + DerivPasskeysRegisterOptionsModel(options: json); + + /// [options] is the challenge string. + Map options; + + /// Creates a JSON object from a [DerivPasskeysRegisterOptionsModel]. + Map toJson() => options; +} diff --git a/packages/deriv_passkeys/lib/src/data/models/deriv_passkeys_verify_credentials_request_body_model.dart b/packages/deriv_passkeys/lib/src/data/models/deriv_passkeys_verify_credentials_request_body_model.dart new file mode 100644 index 000000000..f5cdaa133 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/data/models/deriv_passkeys_verify_credentials_request_body_model.dart @@ -0,0 +1,48 @@ +/// A dart class that serves as a request body for the `DerivPasskeysVerifyCredentials` request. +final class DerivPasskeysVerifyCredentialsRequest { + /// Creates a [DerivPasskeysVerifyCredentialsRequest]. + DerivPasskeysVerifyCredentialsRequest({ + required this.appId, + required this.publicKeyCredential, + required this.type, + }); + + /// Creates a [DerivPasskeysVerifyCredentialsRequest] from a JSON object. + factory DerivPasskeysVerifyCredentialsRequest.fromJson( + Map json, + ) => + DerivPasskeysVerifyCredentialsRequest( + appId: json['app_id'] as String, + publicKeyCredential: + json['publicKeyCredential'] as Map, + type: json['type'] as String, + ); + + /// The app ID. + final String appId; + + /// The public key credential returned by the authenticator. + final Map publicKeyCredential; + + /// The type. + final String type; + + /// Converts the [DerivPasskeysVerifyCredentialsRequest] to a JSON object. + Map toJson() => { + 'app_id': appId, + 'publicKeyCredential': publicKeyCredential, + 'type': type, + }; + + /// Copies the [DerivPasskeysVerifyCredentialsRequest] with some new values. + DerivPasskeysVerifyCredentialsRequest copyWith({ + String? appId, + Map? publicKeyCredential, + String? type, + }) => + DerivPasskeysVerifyCredentialsRequest( + appId: appId ?? this.appId, + publicKeyCredential: publicKeyCredential ?? this.publicKeyCredential, + type: type ?? this.type, + ); +} diff --git a/packages/deriv_passkeys/lib/src/data/models/deriv_passkeys_verify_credentials_response_model.dart b/packages/deriv_passkeys/lib/src/data/models/deriv_passkeys_verify_credentials_response_model.dart new file mode 100644 index 000000000..3b6c29e2e --- /dev/null +++ b/packages/deriv_passkeys/lib/src/data/models/deriv_passkeys_verify_credentials_response_model.dart @@ -0,0 +1,15 @@ +import 'package:equatable/equatable.dart'; + +/// A Dart class that contains the response to verify credentials. +final class DerivPasskeysVerifyCredentialsResponseModel extends Equatable { + /// Creates a [DerivPasskeysVerifyCredentialsResponseModel]. + const DerivPasskeysVerifyCredentialsResponseModel({ + required this.response, + }); + + /// contains the response from the REST API to verify credentials. + final Map response; + + @override + List get props => [response]; +} diff --git a/packages/deriv_passkeys/lib/src/data/models/passkeys_connection_info_model.dart b/packages/deriv_passkeys/lib/src/data/models/passkeys_connection_info_model.dart new file mode 100644 index 000000000..238548ff0 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/data/models/passkeys_connection_info_model.dart @@ -0,0 +1,24 @@ +/// Model to store connection info. +class PasskeysConnectionInfoModel { + /// Creates a new [PasskeysConnectionInfoModel] instance. + PasskeysConnectionInfoModel({ + required this.endpoint, + required this.appId, + }); + + /// Connection endpoint. + String endpoint; + + /// Deriv client app ID. + String appId; + + /// Copies the current instance with some new values. + PasskeysConnectionInfoModel copyWith({ + String? endpoint, + String? appId, + }) => + PasskeysConnectionInfoModel( + endpoint: endpoint ?? this.endpoint, + appId: appId ?? this.appId, + ); +} diff --git a/packages/deriv_passkeys/lib/src/data/platform/deriv_passkeys_method_channel.dart b/packages/deriv_passkeys/lib/src/data/platform/deriv_passkeys_method_channel.dart new file mode 100644 index 000000000..3742d9b9d --- /dev/null +++ b/packages/deriv_passkeys/lib/src/data/platform/deriv_passkeys_method_channel.dart @@ -0,0 +1,71 @@ +import 'package:deriv_passkeys/src/exceptions/platform_exceptions.dart'; +import 'package:deriv_passkeys/src/domain/platform/base_deriv_passkeys_method_channel.dart'; +import 'package:flutter/services.dart'; + +/// An implementation of [BaseDerivPasskeysMethodChannel] that uses method channels. +class MethodChannelDerivPasskeys extends BaseDerivPasskeysMethodChannel { + /// Constructs a [MethodChannelDerivPasskeys] with an optional [MethodChannel]; + MethodChannelDerivPasskeys({ + MethodChannel? channel, + }) : methodChannel = channel ?? const MethodChannel('deriv_passkeys'); + + /// The method channel used to interact with the native platform. + MethodChannel methodChannel; + + @override + Future isPlatformSupported() async => + methodChannel.invokeMethod('isPlatformSupported'); + + @override + Future createCredential(String options) => + methodChannel.invokeMethod( + 'createCredential', {'options': options}).catchError( + (Object error) { + if (error is PlatformException) { + if (error.code == '1001' || + error.code == 'CreateCredentialCancellationException') { + throw CanceledPlatformException( + code: error.code, + message: error.message ?? 'Error creating passkey', + details: error.details ?? error.toString(), + ); + } + } + throw PlatformException( + code: 'unhandled_error', + message: 'Unhandled error creating passkey', + details: error.toString(), + ); + }, + ); + + @override + Future getCredential(String options) => + methodChannel.invokeMethod( + 'getCredential', {'options': options}).catchError( + (Object error) { + if (error is PlatformException) { + if (error.code == '1001' || + error.code == 'GetCredentialCancellationException') { + throw CanceledPlatformException( + code: error.code, + message: error.message ?? 'Error creating passkey', + details: error.details ?? error.toString(), + ); + } + if (error.code == 'NoCredentialException') { + throw NoCredentialPlatformException( + code: error.code, + message: error.message ?? 'Error creating passkey', + details: error.details ?? error.toString(), + ); + } + } + throw PlatformException( + code: 'unhandled_error', + message: 'Unhandled error creating passkey', + details: error.toString(), + ); + }, + ); +} diff --git a/packages/deriv_passkeys/lib/src/data/repositories/deriv_passkeys_repository.dart b/packages/deriv_passkeys/lib/src/data/repositories/deriv_passkeys_repository.dart new file mode 100644 index 000000000..b563776db --- /dev/null +++ b/packages/deriv_passkeys/lib/src/data/repositories/deriv_passkeys_repository.dart @@ -0,0 +1,82 @@ +import 'package:deriv_passkeys/src/data/data_sources/base_deriv_passkeys_data_source.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkey_model.dart'; +import 'package:deriv_passkeys/src/domain/base_repositories/base_deriv_passkeys_repository.dart'; +import 'package:deriv_passkeys/src/domain/entities/passkeys_connection_info_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkey_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_options_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_register_credentials_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_register_options_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_verify_credentials_request_body_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_verify_credentials_response_entity.dart'; + +/// [DerivPasskeysRepository] extends and implements [BaseDerivPasskeysRepository]. +/// +/// It is responsible for handling all DerivPasskeys data sources, and forwarding relavant data +/// to the services using it. +final class DerivPasskeysRepository extends BaseDerivPasskeysRepository { + /// Creates a [DerivPasskeysRepository]. + DerivPasskeysRepository(this.dataSource); + + /// The data source used to get data for Passkeys functionalities. + final BaseDerivPasskeysDataSource dataSource; + + @override + Future getOptions({ + required PasskeysConnectionInfoEntity passkeysConnectionInfoEntity, + }) => + dataSource + .getOptions( + passkeysConnectionInfoModel: dataSource.mapper + .mapConnectionInfoEntity(passkeysConnectionInfoEntity), + ) + .then(dataSource.mapper.mapDerivPasskeysOptionsModel); + + @override + Future verifyCredentials({ + required DerivPasskeysVerifyCredentialsRequestBodyEntity requestBodyEntity, + required String jwtToken, + required PasskeysConnectionInfoEntity passkeysConnectionInfoEntity, + String? userAgent, + }) => + dataSource + .verifyCredentials( + requestBodyModel: dataSource.mapper + .mapDerivPasskeysVerifyCredentialsRequestBodyEntity( + requestBodyEntity), + jwtToken: jwtToken, + passkeysConnectionInfoModel: dataSource.mapper + .mapConnectionInfoEntity(passkeysConnectionInfoEntity), + userAgent: userAgent, + ) + .then( + dataSource.mapper.mapDerivPasskeysVerifyCredentialsResponseModel); + + @override + Future getRegisterOptions({ + String? loginId, + }) => + dataSource + .getRegisterOptions(loginId: loginId) + .then(dataSource.mapper.mapDerivPasskeysRegisterOptionsModel); + + @override + Future registerCredentials( + DerivPasskeysRegisterCredentialsEntity entity, { + String? loginId, + }) => + dataSource + .registerCredentials( + dataSource.mapper + .mapDerivPasskeysRegisterCredentialsEntity(entity) + .copyWith( + loginid: loginId, + ), + ) + .then(dataSource.mapper.mapDerivPasskeyModel); + + @override + Future> getPasskeysList({String? loginId}) => + dataSource.getPasskeysList(loginId: loginId).then( + (List models) => + models.map(dataSource.mapper.mapDerivPasskeyModel).toList()); +} diff --git a/packages/deriv_passkeys/lib/src/data/repositories/passkey_analytics_repository.dart b/packages/deriv_passkeys/lib/src/data/repositories/passkey_analytics_repository.dart new file mode 100644 index 000000000..a72f33172 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/data/repositories/passkey_analytics_repository.dart @@ -0,0 +1,286 @@ +import 'package:analytics/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart'; +import 'package:deriv_passkeys/src/core/constants/analytics_actions_enums.dart'; +import 'package:deriv_passkeys/src/core/constants/tracking_event_params_helper.dart'; +import 'package:deriv_passkeys/src/domain/base_repositories/base_passkey_analytics_repository.dart'; + +/// [AnalyticsRepository] is an implementation of [BasePasskeyAnalyticsRepository]. +class AnalyticsRepository extends BasePasskeyAnalyticsRepository { + /// Constructor for [AnalyticsRepository]. + AnalyticsRepository._( + this._appId, { + required this.derivRudderstack, + }); + + static AnalyticsRepository? _instance; + + /// Singleton instance of [AnalyticsRepository]. + static AnalyticsRepository get instance => + _instance ??= throw Exception('AnalyticsRepository is not initialized'); + + /// Instance of [DerivRudderstack]. + final DerivRudderstack derivRudderstack; + + /// Initialize [AnalyticsRepository]. + static void init( + String appId, { + required DerivRudderstack derivRudderstack, + }) => + _instance = AnalyticsRepository._( + appId, + derivRudderstack: derivRudderstack, + ); + + String? _mainFormName; + + String? _subFormName; + + /// Deriv client app ID. + final String _appId; + + @override + void trackOpenEffortlessLoginPage() { + final Map trackingData = getEffortlessLoginTrackingParams( + EffortlessPageActions.openEffortlessLoginPage, + ); + + _mainFormName = trackingData['params']['form_name']; + _subFormName = 'passkey_effortless'; + + _addAppName(trackingData); + + derivRudderstack.track( + eventName: trackingData['event_name'], + properties: trackingData['params'], + ); + } + + @override + void trackCloseEffortlessLoginPage() { + final Map trackingData = getEffortlessLoginTrackingParams( + EffortlessPageActions.closeEffortlessLoginPage, + ); + + _addAppName(trackingData); + + derivRudderstack.track( + eventName: trackingData['event_name'], + properties: trackingData['params'], + ); + } + + @override + void trackMaybeLater() { + final Map trackingData = getEffortlessLoginTrackingParams( + EffortlessPageActions.maybeLater, + ); + + _addAppName(trackingData); + + derivRudderstack.track( + eventName: trackingData['event_name'], + properties: trackingData['params'], + ); + } + + @override + void trackOpenLearnMorePage() { + final Map trackingData = getLearnMoreTrackingParams( + LearnMorePageActions.openLearnMorePage, + _mainFormName ?? 'testing_main_form', + ); + + _subFormName = 'passkey_info'; + + _addAppName(trackingData); + + derivRudderstack.track( + eventName: trackingData['event_name'], + properties: trackingData['params'], + ); + } + + @override + void trackCloseLearnMorePage() { + final Map trackingData = getLearnMoreTrackingParams( + LearnMorePageActions.closeLearnMorePage, + _mainFormName ?? 'testing_main_form', + ); + + if (_mainFormName == null) { + _mainFormName = 'testing_main_form'; + } else { + _subFormName = _mainFormName!.contains('account_settings') + ? 'passkey_main' + : 'passkey_effortless'; + } + + _addAppName(trackingData); + + derivRudderstack.track( + eventName: trackingData['event_name'], + properties: trackingData['params'], + ); + } + + @override + void trackOpenManagePasskeysPage() { + final Map trackingData = getManagePasskeysTrackingParams( + ManagePasskeysPageActions.openManagePasskeysPage, + ); + + _mainFormName = trackingData['params']['form_name']; + _subFormName = 'passkey_main'; + + _addAppName(trackingData); + + derivRudderstack.track( + eventName: trackingData['event_name'], + properties: trackingData['params'], + ); + } + + @override + void trackCloseManagePasskeysPage() { + final Map trackingData = getManagePasskeysTrackingParams( + ManagePasskeysPageActions.closeManagePasskeysPage, + ); + + _addAppName(trackingData); + + derivRudderstack.track( + eventName: trackingData['event_name'], + properties: trackingData['params'], + ); + } + + @override + void trackCreatePasskey() { + final Map trackingData = getCreatePasskeyTrackingParams( + CreatePasskeyFlowActions.createPasskey, + mainFormName: _mainFormName, + subFormName: _subFormName, + ); + + _addAppName(trackingData); + + derivRudderstack.track( + eventName: trackingData['event_name'], + properties: trackingData['params'], + ); + } + + @override + void trackCreatePasskeySuccess() { + final Map trackingData = getCreatePasskeyTrackingParams( + CreatePasskeyFlowActions.createPasskeySuccess, + mainFormName: _mainFormName, + subFormName: _subFormName, + ); + + _addAppName(trackingData); + + derivRudderstack.track( + eventName: trackingData['event_name'], + properties: trackingData['params'], + ); + } + + @override + void trackPasskeyError(String errorMessage) { + final Map trackingData = getCreatePasskeyTrackingParams( + CreatePasskeyFlowActions.error, + mainFormName: _mainFormName, + subFormName: _subFormName, + errorMessage: errorMessage, + ); + + _addAppName(trackingData); + + derivRudderstack.track( + eventName: trackingData['event_name'], + properties: trackingData['params'], + ); + } + + @override + void trackContinueTrading() { + final Map trackingData = getCreatePasskeyTrackingParams( + CreatePasskeyFlowActions.continueTrading, + mainFormName: _mainFormName, + subFormName: _subFormName, + ); + + _addAppName(trackingData); + + derivRudderstack.track( + eventName: trackingData['event_name'], + properties: trackingData['params'], + ); + } + + @override + void trackAddMorePasskeys() { + final Map trackingData = getCreatePasskeyTrackingParams( + CreatePasskeyFlowActions.addMorePasskeys, + mainFormName: _mainFormName, + subFormName: _subFormName, + ); + + _addAppName(trackingData); + + derivRudderstack.track( + eventName: trackingData['event_name'], + properties: trackingData['params'], + ); + } + + @override + void trackRenamePasskey() { + final Map trackingData = getRenamePasskeyTrackingParams( + RenamePasskeyFlowActions.renamePasskey, + ); + + _addAppName(trackingData); + + derivRudderstack.track( + eventName: trackingData['event_name'], + properties: trackingData['params'], + ); + } + + @override + void trackCancelRenamePasskey() { + final Map trackingData = getRenamePasskeyTrackingParams( + RenamePasskeyFlowActions.cancelRenamePasskey, + ); + + _addAppName(trackingData); + + derivRudderstack.track( + eventName: trackingData['event_name'], + properties: trackingData['params'], + ); + } + + @override + void trackRenamePasskeySuccess() { + final Map trackingData = getRenamePasskeyTrackingParams( + RenamePasskeyFlowActions.renamePasskeySuccess, + ); + + _addAppName(trackingData); + + derivRudderstack.track( + eventName: trackingData['event_name'], + properties: trackingData['params'], + ); + } + + void _addAppName(Map data) { + data['params']['form_name'] += _appId == '23789' + ? 'derivgo' + : _appId == '1408' + ? 'p2p' + : 'unsupported'; + } +} diff --git a/packages/deriv_passkeys/lib/src/domain/base_repositories/base_deriv_passkeys_repository.dart b/packages/deriv_passkeys/lib/src/domain/base_repositories/base_deriv_passkeys_repository.dart new file mode 100644 index 000000000..b69cd1217 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/domain/base_repositories/base_deriv_passkeys_repository.dart @@ -0,0 +1,39 @@ +import 'package:deriv_passkeys/src/domain/entities/passkeys_connection_info_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkey_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_options_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_register_credentials_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_register_options_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_verify_credentials_request_body_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_verify_credentials_response_entity.dart'; + +/// [BaseDerivPasskeysRepository] defines a contract for all DerivPasskeys repositories. +/// +/// All DerivPasskeys repositories should extend this class, and implement its abstract methods. +abstract base class BaseDerivPasskeysRepository { + /// Get options for authentication with DerivPasskeys. + Future getOptions({ + required PasskeysConnectionInfoEntity passkeysConnectionInfoEntity, + }); + + /// Verify the user's DerivPasskeys credentials. + Future verifyCredentials({ + required DerivPasskeysVerifyCredentialsRequestBodyEntity requestBodyEntity, + required String jwtToken, + required PasskeysConnectionInfoEntity passkeysConnectionInfoEntity, + String? userAgent, + }); + + /// Get options for registration with DerivPasskeys. + Future getRegisterOptions({ + String? loginId, + }); + + /// Register credentials with DerivPasskeys. + Future registerCredentials( + DerivPasskeysRegisterCredentialsEntity entity, { + String? loginId, + }); + + /// Get passkeys list. + Future> getPasskeysList({String? loginId}); +} diff --git a/packages/deriv_passkeys/lib/src/domain/base_repositories/base_passkey_analytics_repository.dart b/packages/deriv_passkeys/lib/src/domain/base_repositories/base_passkey_analytics_repository.dart new file mode 100644 index 000000000..e1a4addc1 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/domain/base_repositories/base_passkey_analytics_repository.dart @@ -0,0 +1,47 @@ +/// [BasePasskeyAnalyticsRepository] defines the interface for analytics repositories. +abstract class BasePasskeyAnalyticsRepository { + /// Track opening effortless login page. + void trackOpenEffortlessLoginPage(); + + /// Track closing effortless login page. + void trackCloseEffortlessLoginPage(); + + /// Track pressing maybe later button. + void trackMaybeLater(); + + /// Track opening learn more page. + void trackOpenLearnMorePage(); + + /// Track closing learn more page. + void trackCloseLearnMorePage(); + + /// Track opening manage passkeys page. + void trackOpenManagePasskeysPage(); + + /// Track closing manage passkeys page. + void trackCloseManagePasskeysPage(); + + /// Track pressing create passkey button. + void trackCreatePasskey(); + + /// Track create passkey success. + void trackCreatePasskeySuccess(); + + /// Track passkey error. + void trackPasskeyError(String errorMessage); + + /// Track pressing continue trading button. + void trackContinueTrading(); + + /// Track pressing add more passkeys button. + void trackAddMorePasskeys(); + + /// Track pressing rename passkey button. + void trackRenamePasskey(); + + /// Track cancel rename passkey. + void trackCancelRenamePasskey(); + + /// Track rename passkey success. + void trackRenamePasskeySuccess(); +} diff --git a/packages/deriv_passkeys/lib/src/domain/domain.dart b/packages/deriv_passkeys/lib/src/domain/domain.dart new file mode 100644 index 000000000..9fbf5dc74 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/domain/domain.dart @@ -0,0 +1,5 @@ +library deriv_passkeys; + +export './entities/passkeys_connection_info_entity.dart'; +export './entities/deriv_passkeys_verify_credentials_response_entity.dart'; +export './entities/deriv_passkey_entity.dart'; diff --git a/packages/deriv_passkeys/lib/src/domain/entities/account_entity.dart b/packages/deriv_passkeys/lib/src/domain/entities/account_entity.dart new file mode 100644 index 000000000..f50d6f6b4 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/domain/entities/account_entity.dart @@ -0,0 +1,19 @@ +import 'package:equatable/equatable.dart'; + +/// A Dart class that contains the account entity. +final class AccountEntity extends Equatable { + /// Creates a [AccountEntity]. + const AccountEntity({ + required this.loginId, + required this.token, + }); + + /// Account login ID. + final String loginId; + + /// Account token. + final String token; + + @override + List get props => [loginId, token]; +} diff --git a/packages/deriv_passkeys/lib/src/domain/entities/deriv_passkey_entity.dart b/packages/deriv_passkeys/lib/src/domain/entities/deriv_passkey_entity.dart new file mode 100644 index 000000000..e2f1f29a1 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/domain/entities/deriv_passkey_entity.dart @@ -0,0 +1,53 @@ +import 'package:equatable/equatable.dart'; + +/// [DerivPasskeyEntity] DTO for DerivPasskey data coming from the WebsocketAPI. +class DerivPasskeyEntity extends Equatable { + /// Creates a [DerivPasskeyEntity]. + const DerivPasskeyEntity({ + required this.createdAt, + required this.id, + required this.lastUsed, + required this.name, + required this.passkeyId, + required this.storedOn, + }); + + /// Creates a [DerivPasskeyEntity] from a JSON object. + factory DerivPasskeyEntity.fromJson(Map json) => + DerivPasskeyEntity( + createdAt: json['created_at'] as int, + id: json['id'] as String, + lastUsed: json['last_used'] as int?, + name: json['name'] as String, + passkeyId: json['passkey_id'] as String, + storedOn: json['stored_on'] as String, + ); + + /// [createdAt] is the creation date. + final int createdAt; + + /// [id] is the passkey ID. + final String id; + + /// [lastUsed] is the last used date. + final int? lastUsed; + + /// [name] is the passkey name. + final String name; + + /// [passkeyId] is the passkey ID. + final String passkeyId; + + /// [storedOn] is the device the passkey is stored on. + final String storedOn; + + @override + List get props => [ + createdAt, + id, + lastUsed, + name, + passkeyId, + storedOn, + ]; +} diff --git a/packages/deriv_passkeys/lib/src/domain/entities/deriv_passkeys_options_entity.dart b/packages/deriv_passkeys/lib/src/domain/entities/deriv_passkeys_options_entity.dart new file mode 100644 index 000000000..6532faf99 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/domain/entities/deriv_passkeys_options_entity.dart @@ -0,0 +1,60 @@ +import 'package:equatable/equatable.dart'; + +/// [DerivPasskeysOptionsEntity] represents a DerivPasskeysOptions entity. +final class DerivPasskeysOptionsEntity extends Equatable { + /// Creates a [DerivPasskeysOptionsEntity]. + /// [challenge] is the challenge string. + /// [rpId] is the relying party ID. + /// [timeout] is the timeout in seconds. + /// [userVerification] is the user verification method. + const DerivPasskeysOptionsEntity({ + required this.challenge, + required this.rpId, + required this.timeout, + required this.userVerification, + required this.allowCredentials, + }); + + /// Creates a [DerivPasskeysOptionsEntity] from a JSON object. + factory DerivPasskeysOptionsEntity.fromJson(Map json) => + DerivPasskeysOptionsEntity( + challenge: json['challenge'] as String, + rpId: json['rpId'] as String, + timeout: json['timeout'] as int, + userVerification: json['userVerification'] as String, + allowCredentials: json['allowCredentials'] as List, + ); + + /// [challenge] is the challenge string. + final String challenge; + + /// [rpId] is the relying party ID. + final String rpId; + + /// [timeout] is the timeout in seconds. + final int timeout; + + /// [userVerification] is the user verification method. + final String userVerification; + + /// [allowCredentials] is the allowed credentials. + final List allowCredentials; + + /// Creates a JSON object from a [DerivPasskeysOptionsEntity]. + Map toJson() => { + 'challenge': challenge, + 'rpId': rpId, + 'timeout': timeout, + 'userVerification': userVerification, + 'allowCredentials': allowCredentials, + }; + + @override + List get props => [ + challenge, + rpId, + timeout, + userVerification, + allowCredentials, + ]; +} diff --git a/packages/deriv_passkeys/lib/src/domain/entities/deriv_passkeys_register_credentials_entity.dart b/packages/deriv_passkeys/lib/src/domain/entities/deriv_passkeys_register_credentials_entity.dart new file mode 100644 index 000000000..5a95b4c9f --- /dev/null +++ b/packages/deriv_passkeys/lib/src/domain/entities/deriv_passkeys_register_credentials_entity.dart @@ -0,0 +1,25 @@ +import 'package:equatable/equatable.dart'; + +/// [DerivPasskeysRegisterCredentialsEntity] is the entity class for the register credentials. +final class DerivPasskeysRegisterCredentialsEntity extends Equatable { + /// Creates a [DerivPasskeysRegisterCredentialsEntity]. + const DerivPasskeysRegisterCredentialsEntity({ + required this.publicKeyCredential, + required this.name, + }); + + /// [publicKeyCredential] is the challenge string. + final Map publicKeyCredential; + + /// [name] is the name of the passkey. + final String name; + + /// Creates a JSON object from a [DerivPasskeysRegisterCredentialsEntity]. + Map toJson() => { + 'publicKeyCredential': publicKeyCredential, + 'name': name, + }; + + @override + List get props => [publicKeyCredential, name]; +} diff --git a/packages/deriv_passkeys/lib/src/domain/entities/deriv_passkeys_register_options_entity.dart b/packages/deriv_passkeys/lib/src/domain/entities/deriv_passkeys_register_options_entity.dart new file mode 100644 index 000000000..00b7fccb6 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/domain/entities/deriv_passkeys_register_options_entity.dart @@ -0,0 +1,23 @@ +import 'package:equatable/equatable.dart'; + +/// [DerivPasskeysRegisterOptionsEntity] DTO for DerivPasskeysOptions data coming from the WebsocketAPI. +final class DerivPasskeysRegisterOptionsEntity extends Equatable { + /// Creates a [DerivPasskeysRegisterOptionsEntity]. + const DerivPasskeysRegisterOptionsEntity({ + required this.options, + }); + + /// Creates a [DerivPasskeysRegisterOptionsEntity] from a JSON object. + factory DerivPasskeysRegisterOptionsEntity.fromJson( + Map json) => + DerivPasskeysRegisterOptionsEntity(options: json); + + /// [options] is the challenge string. + final Map options; + + /// Creates a JSON object from a [DerivPasskeysRegisterOptionsEntity]. + Map toJson() => options; + + @override + List get props => [options]; +} diff --git a/packages/deriv_passkeys/lib/src/domain/entities/deriv_passkeys_verify_credentials_request_body_entity.dart b/packages/deriv_passkeys/lib/src/domain/entities/deriv_passkeys_verify_credentials_request_body_entity.dart new file mode 100644 index 000000000..4e6b06be8 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/domain/entities/deriv_passkeys_verify_credentials_request_body_entity.dart @@ -0,0 +1,40 @@ +import 'package:equatable/equatable.dart'; + +/// A dart class that serves as a request body for the `DerivPasskeysVerifyCredentials` request. +final class DerivPasskeysVerifyCredentialsRequestBodyEntity extends Equatable { + /// Creates a [DerivPasskeysVerifyCredentialsRequestBodyEntity]. + const DerivPasskeysVerifyCredentialsRequestBodyEntity({ + required this.appId, + required this.publicKeyCredential, + required this.type, + }); + + /// Creates a [DerivPasskeysVerifyCredentialsRequestBodyEntity] from a JSON object. + factory DerivPasskeysVerifyCredentialsRequestBodyEntity.fromJson( + Map json) => + DerivPasskeysVerifyCredentialsRequestBodyEntity( + appId: json['app_id'] as String, + publicKeyCredential: + json['publicKeyCredential'] as Map, + type: json['type'] as String, + ); + + /// The app ID. + final String appId; + + /// The public key credential returned by the authenticator. + final Map publicKeyCredential; + + /// The type. + final String type; + + /// Converts the [DerivPasskeysVerifyCredentialsRequestBodyEntity] to a JSON object. + Map toJson() => { + 'app_id': appId, + 'publicKeyCredential': publicKeyCredential, + 'type': type, + }; + + @override + List get props => [appId, publicKeyCredential, type]; +} diff --git a/packages/deriv_passkeys/lib/src/domain/entities/deriv_passkeys_verify_credentials_response_entity.dart b/packages/deriv_passkeys/lib/src/domain/entities/deriv_passkeys_verify_credentials_response_entity.dart new file mode 100644 index 000000000..7c684274f --- /dev/null +++ b/packages/deriv_passkeys/lib/src/domain/entities/deriv_passkeys_verify_credentials_response_entity.dart @@ -0,0 +1,20 @@ +import 'package:deriv_passkeys/src/domain/entities/account_entity.dart'; +import 'package:equatable/equatable.dart'; + +/// A Dart class that contains the response to verify credentials. +final class DerivPasskeysVerifyCredentialsResponseEntity extends Equatable { + /// Creates a [DerivPasskeysVerifyCredentialsResponseEntity]. + const DerivPasskeysVerifyCredentialsResponseEntity({ + required this.accounts, + required this.refreshToken, + }); + + /// List of accounts. + final List accounts; + + /// Refresh token from the REST API to verify credentials. + final String refreshToken; + + @override + List get props => [accounts, refreshToken]; +} diff --git a/packages/deriv_passkeys/lib/src/domain/entities/passkeys_connection_info_entity.dart b/packages/deriv_passkeys/lib/src/domain/entities/passkeys_connection_info_entity.dart new file mode 100644 index 000000000..213d42943 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/domain/entities/passkeys_connection_info_entity.dart @@ -0,0 +1,14 @@ +/// Entity to store connection info. +class PasskeysConnectionInfoEntity { + /// Creates a new [PasskeysConnectionInfoEntity] instance. + PasskeysConnectionInfoEntity({ + required this.endpoint, + required this.appId, + }); + + /// Connection endpoint. + String endpoint; + + /// Deriv client app ID. + String appId; +} diff --git a/packages/deriv_passkeys/lib/src/domain/platform/base_deriv_passkeys_method_channel.dart b/packages/deriv_passkeys/lib/src/domain/platform/base_deriv_passkeys_method_channel.dart new file mode 100644 index 000000000..c145e6540 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/domain/platform/base_deriv_passkeys_method_channel.dart @@ -0,0 +1,35 @@ +import 'package:deriv_passkeys/src/data/platform/deriv_passkeys_method_channel.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +/// The interface that implementations of deriv_passkeys must implement. +abstract class BaseDerivPasskeysMethodChannel extends PlatformInterface { + /// Constructs a BaseDerivPasskeysMethodChannel. + BaseDerivPasskeysMethodChannel() : super(token: _token); + + static final Object _token = Object(); + + static BaseDerivPasskeysMethodChannel _instance = + MethodChannelDerivPasskeys(); + + /// The default instance of [BaseDerivPasskeysMethodChannel] to use. + /// + /// Defaults to [MethodChannelDerivPasskeys]. + static BaseDerivPasskeysMethodChannel get instance => _instance; + + /// Platform-specific implementations should set this with their own + /// platform-specific class that extends [BaseDerivPasskeysMethodChannel] when + /// they register themselves. + static set instance(BaseDerivPasskeysMethodChannel instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + /// Returns the platform version. + Future isPlatformSupported(); + + /// Creates a passkey credential. + Future createCredential(String options); + + /// Gets a passkey credential. + Future getCredential(String options); +} diff --git a/packages/deriv_passkeys/lib/src/exceptions/platform_exceptions.dart b/packages/deriv_passkeys/lib/src/exceptions/platform_exceptions.dart new file mode 100644 index 000000000..6db77cf7c --- /dev/null +++ b/packages/deriv_passkeys/lib/src/exceptions/platform_exceptions.dart @@ -0,0 +1,29 @@ +import 'package:flutter/services.dart'; + +/// An exception that is thrown when the platform fails to create a passkey. +class CanceledPlatformException extends PlatformException { + /// Creates a [CanceledPlatformException]. + CanceledPlatformException({ + required String code, + required String message, + required String details, + }) : super( + code: code, + message: message, + details: details, + ); +} + +/// An exception that is thrown when the platform cannot find a matching credential. +class NoCredentialPlatformException extends PlatformException { + /// Creates a [NoCredentialPlatformException]. + NoCredentialPlatformException({ + required String code, + required String message, + required String details, + }) : super( + code: code, + message: message, + details: details, + ); +} diff --git a/packages/deriv_passkeys/lib/src/exceptions/server_exceptions.dart b/packages/deriv_passkeys/lib/src/exceptions/server_exceptions.dart new file mode 100644 index 000000000..b85a27380 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/exceptions/server_exceptions.dart @@ -0,0 +1,14 @@ +/// Server exception +class ServerException implements Exception { + /// Creates a [ServerException]. + ServerException({ + required this.errorCode, + required this.message, + }); + + /// The error code. + String errorCode; + + /// The error message. + String message; +} diff --git a/packages/deriv_passkeys/lib/src/interactor/interactor.dart b/packages/deriv_passkeys/lib/src/interactor/interactor.dart new file mode 100644 index 000000000..c0db4ce94 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/interactor/interactor.dart @@ -0,0 +1,3 @@ +library deriv_passkeys; + +export './services/deriv_passkeys_service.dart'; diff --git a/packages/deriv_passkeys/lib/src/interactor/services/deriv_passkeys_service.dart b/packages/deriv_passkeys/lib/src/interactor/services/deriv_passkeys_service.dart new file mode 100644 index 000000000..3086d6a30 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/interactor/services/deriv_passkeys_service.dart @@ -0,0 +1,116 @@ +import 'dart:convert'; + +import 'package:deriv_passkeys/src/domain/base_repositories/base_deriv_passkeys_repository.dart'; +import 'package:deriv_passkeys/src/domain/entities/passkeys_connection_info_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkey_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_register_credentials_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_verify_credentials_request_body_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_verify_credentials_response_entity.dart'; +import 'package:deriv_passkeys/src/domain/platform/base_deriv_passkeys_method_channel.dart'; +import 'package:flutter/services.dart'; + +/// A wrapper class that contains methods to interact with the native platform for passkey. +class DerivPasskeysService { + /// Constructs a [DerivPasskeysService] with [BaseDerivPasskeysRepository]. + DerivPasskeysService(this.repository); + + /// The repository used to get data for Passkeys functionalities. + final BaseDerivPasskeysRepository repository; + + /// Returns true if the device supports passkey. + Future isSupported() async { + final bool? isPlatformSupportedResult = + await BaseDerivPasskeysMethodChannel.instance.isPlatformSupported(); + return isPlatformSupportedResult ?? false; + } + + /// Creates a passkey credential. + Future createCredential({ + String? loginId, + }) async { + final Map getRegisterOptionsResult = + (await repository.getRegisterOptions(loginId: loginId)).options; + final Map publicKeyCredentialUserEntityJson = + { + 'id': _base64UrlEncodeString( + getRegisterOptionsResult['user']['id'].toString()), + 'name': getRegisterOptionsResult['user']['name'], + 'displayName': getRegisterOptionsResult['user']['displayName'] + }; + + getRegisterOptionsResult['user'] = publicKeyCredentialUserEntityJson; + + final String options = jsonEncode(getRegisterOptionsResult); + + final String? credentials = + await BaseDerivPasskeysMethodChannel.instance.createCredential(options); + if (credentials == null) { + throw PlatformException( + code: 'null-response', + message: 'Unable to get response from Passkey.'); + } + final Map decodedCredentials = jsonDecode(credentials); + final DerivPasskeyEntity getRegisterPasskeysResult = + await repository.registerCredentials( + DerivPasskeysRegisterCredentialsEntity( + publicKeyCredential: decodedCredentials, + name: 'Passkey', + ), + loginId: loginId, + ); + return getRegisterPasskeysResult; + } + + /// Gets a list of passkeys. + Future> getPasskeysList({ + String? loginId, + }) async { + final List getPasskeysListResult = + await repository.getPasskeysList(loginId: loginId); + return getPasskeysListResult; + } + + /// Gets a passkey credential. + Future verifyCredential({ + required String jwtToken, + required PasskeysConnectionInfoEntity passkeysConnectionInfoEntity, + String? userAgent, + }) async { + final Map getOptionsResult = (await repository.getOptions( + passkeysConnectionInfoEntity: passkeysConnectionInfoEntity, + )) + .toJson(); + final String options = jsonEncode(getOptionsResult); + + final String? response = + await BaseDerivPasskeysMethodChannel.instance.getCredential(options); + + if (response == null) { + throw PlatformException( + code: 'null-response', + message: 'Unable to get response from Passkey.'); + } + + final Map decodedResponse = jsonDecode(response); + + final DerivPasskeysVerifyCredentialsResponseEntity + getVerifyCredentialsResult = await repository.verifyCredentials( + requestBodyEntity: DerivPasskeysVerifyCredentialsRequestBodyEntity( + appId: passkeysConnectionInfoEntity.appId, + publicKeyCredential: decodedResponse, + type: 'passkeys', + ), + jwtToken: jwtToken, + passkeysConnectionInfoEntity: passkeysConnectionInfoEntity, + userAgent: userAgent, + ); + + return getVerifyCredentialsResult; + } +} + +String _base64UrlEncodeString(String input) { + final List bytes = utf8.encode(input); + final String base64Str = base64Url.encode(bytes); + return base64Str; +} diff --git a/packages/deriv_passkeys/lib/src/presentation/constants/assets.dart b/packages/deriv_passkeys/lib/src/presentation/constants/assets.dart new file mode 100644 index 000000000..9e7fd6cb3 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/presentation/constants/assets.dart @@ -0,0 +1,32 @@ +/// [Assets] contains all the assets used in the deriv_passkeys plugin. +class Assets { + /// [passkeySvgIcon] is the svg icon used for passkey. + static const String passkeySvgIcon = 'assets/svg/passkey_icon.svg'; + + /// [effortlessPasskeysIcon] is the svg icon used for passkey. + static const String effortlessPasskeysIcon = + 'assets/svg/effortless_login_passkey_icon.svg'; + + /// [passkeyCreatedSuccessIcon] is the svg icon used for passkey. + static const String passkeyCreatedSuccessIcon = + 'assets/svg/passkey_created_success_icon.svg'; + + /// [fingerPrintIcon] is the svg icon used for passkey. + static const String fingerPrintIcon = 'assets/svg/fingerprint_icon.svg'; + + /// [syncIcon] is the svg icon used for passkey. + static const String syncIcon = 'assets/svg/sync_icon.svg'; + + /// [lockIcon] is the svg icon used for passkey. + static const String lockIcon = 'assets/svg/lock_icon.svg'; + + /// [lightBulbIcon] is the svg icon used for passkey. + static const String lightBulbIcon = 'assets/svg/light_bulb_icon.svg'; + + /// [learnMorePasskeysIcon] is the svg icon used for passkey. + static const String learnMorePasskeysIcon = + 'assets/svg/learn_more_passkeys_icon.svg'; + + /// [addPasskeyIcon] is the svg icon used for passkey. + static const String addPasskeyIcon = 'assets/svg/add_passkey_icon.svg'; +} diff --git a/packages/deriv_passkeys/lib/src/presentation/mixins/passkey_event_tracking_mixin.dart b/packages/deriv_passkeys/lib/src/presentation/mixins/passkey_event_tracking_mixin.dart new file mode 100644 index 000000000..b5a8a4e51 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/presentation/mixins/passkey_event_tracking_mixin.dart @@ -0,0 +1,65 @@ +import 'package:deriv_passkeys/src/data/repositories/passkey_analytics_repository.dart'; +import 'package:deriv_passkeys/src/domain/base_repositories/base_passkey_analytics_repository.dart'; +import 'package:flutter/foundation.dart'; + +/// Mixin that provides analytics tracking functions. +mixin PasskeyEventTrackingMixin implements BasePasskeyAnalyticsRepository { + /// The analytics repository. + @visibleForTesting + BasePasskeyAnalyticsRepository get analyticsRepository => + AnalyticsRepository.instance; + + @override + void trackOpenEffortlessLoginPage() => + analyticsRepository.trackOpenEffortlessLoginPage(); + + @override + void trackCloseEffortlessLoginPage() => + analyticsRepository.trackCloseEffortlessLoginPage(); + + @override + void trackMaybeLater() => analyticsRepository.trackMaybeLater(); + + @override + void trackOpenLearnMorePage() => analyticsRepository.trackOpenLearnMorePage(); + + @override + void trackCloseLearnMorePage() => + analyticsRepository.trackCloseLearnMorePage(); + + @override + void trackOpenManagePasskeysPage() => + analyticsRepository.trackOpenManagePasskeysPage(); + + @override + void trackCloseManagePasskeysPage() => + analyticsRepository.trackCloseManagePasskeysPage(); + + @override + void trackCreatePasskey() => analyticsRepository.trackCreatePasskey(); + + @override + void trackCreatePasskeySuccess() => + analyticsRepository.trackCreatePasskeySuccess(); + + @override + void trackPasskeyError(String errorMessage) => + analyticsRepository.trackPasskeyError(errorMessage); + + @override + void trackContinueTrading() => analyticsRepository.trackContinueTrading(); + + @override + void trackAddMorePasskeys() => analyticsRepository.trackAddMorePasskeys(); + + @override + void trackRenamePasskey() => analyticsRepository.trackRenamePasskey(); + + @override + void trackCancelRenamePasskey() => + analyticsRepository.trackCancelRenamePasskey(); + + @override + void trackRenamePasskeySuccess() => + analyticsRepository.trackRenamePasskeySuccess(); +} diff --git a/packages/deriv_passkeys/lib/src/presentation/pages/effortless_passkeys_login_page.dart b/packages/deriv_passkeys/lib/src/presentation/pages/effortless_passkeys_login_page.dart new file mode 100644 index 000000000..766d7d4a5 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/presentation/pages/effortless_passkeys_login_page.dart @@ -0,0 +1,313 @@ +import 'package:deriv_passkeys/src/core/extensions/context_extensions.dart'; +import 'package:deriv_passkeys/src/presentation/constants/assets.dart'; +import 'package:deriv_passkeys/src/presentation/mixins/passkey_event_tracking_mixin.dart'; +import 'package:deriv_passkeys/src/presentation/pages/learn_more_passkeys_page.dart'; +import 'package:deriv_passkeys/src/presentation/pages/passkey_created_page.dart'; +import 'package:deriv_passkeys/src/presentation/states/bloc/deriv_passkeys_bloc.dart'; +import 'package:deriv_passkeys/src/presentation/utils/handle_errors_utils.dart'; +import 'package:deriv_passkeys/src/presentation/widgets/icon_text_row_widget.dart'; +import 'package:deriv_passkeys/src/presentation/widgets/passkey_created_call_to_action.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; + +/// A stateless widget to build the Effortless Passkeys page. +class EffortlessPasskeysPage extends StatelessWidget + with PasskeyEventTrackingMixin { + /// Creates a [EffortlessPasskeysPage]. + EffortlessPasskeysPage({ + required this.onPageClosed, + required this.addMorePasskeysNavigationCallback, + required this.continueTradingNavigationCallback, + this.onExitPasskeysFlow, + this.effortlessPasskeysPageKeys, + super.key, + }) { + trackOpenEffortlessLoginPage(); + } + + /// The route name for the effortless passkeys page. + static const String routeName = 'effortless_passkeys_page'; + + /// Callback to be called when the user wants to add more passkeys. + final void Function(BuildContext context) addMorePasskeysNavigationCallback; + + /// Callback to be called when the user wants to continue trading. + final void Function(BuildContext context) continueTradingNavigationCallback; + + /// Callback to be called when the flow is complete. + final void Function(BuildContext context) onPageClosed; + + /// Callback to be called when user exits the passkey flow and continues working with the app. + final void Function()? onExitPasskeysFlow; + + /// Pass an object of keys to assign to specific widget in this page. + final EffortlessPasskeysPageKeys? effortlessPasskeysPageKeys; + + @override + Widget build(BuildContext context) => WillPopScope( + onWillPop: () async { + trackCloseEffortlessLoginPage(); + return true; + }, + child: BlocListener( + listener: (BuildContext context, DerivPasskeysState state) { + if (state is DerivPasskeysCreatedSuccessfullyState) { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (BuildContext context) => PasskeyCreatedPage( + onExitPasskeysFlow: onExitPasskeysFlow, + onPageClose: (BuildContext context) { + onPageClosed(context); + }, + bottomCallToAction: PasskeysCreatedCallToAction( + addMorePasskeysNavigationCallback: + (BuildContext context) { + trackAddMorePasskeys(); + addMorePasskeysNavigationCallback(context); + }, + continueTradingNavigationCallback: + (BuildContext context) { + trackContinueTrading(); + continueTradingNavigationCallback(context); + if (onExitPasskeysFlow != null) { + onExitPasskeysFlow!.call(); + } + }, + ), + ), + ), + ); + } else if (state is DerivPasskeysErrorState) { + trackPasskeyError('${state.errorCode}: ${state.message}'); + handlePasskeysError(context, state); + } + }, + child: Scaffold( + body: SafeArea( + child: LayoutBuilder( + builder: (_, BoxConstraints constraints) => + SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minWidth: constraints.maxWidth, + minHeight: constraints.maxHeight), + child: IntrinsicHeight( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + alignment: Alignment.topRight, + child: Padding( + padding: const EdgeInsets.all(16), + child: TextButton( + onPressed: () { + trackMaybeLater(); + onPageClosed(context); + if (onExitPasskeysFlow != null) { + onExitPasskeysFlow!.call(); + } + }, + child: Text( + context.derivPasskeysLocalizations.maybeLater + .toUpperCase(), + key: effortlessPasskeysPageKeys + ?.maybeLaterTextButtonKey, + style: TextStyle( + color: context.theme.colors.coral, + ), + ), + ), + ), + ), + Expanded( + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 96, + ), + child: Column( + mainAxisAlignment: + MainAxisAlignment.spaceEvenly, + children: [ + SvgPicture.asset( + Assets.effortlessPasskeysIcon, + package: 'deriv_passkeys', + ), + Text( + context.derivPasskeysLocalizations + .effortlessLoginWithPasskeys, + key: effortlessPasskeysPageKeys + ?.loginWithPasskeyTextKey, + style: const TextStyle(fontSize: 20), + ), + const SizedBox( + height: 24, + ), + IconTextRowWidget( + assetName: Assets.fingerPrintIcon, + text: context.derivPasskeysLocalizations + .noNeedToRememberPassword, + textKey: effortlessPasskeysPageKeys + ?.noNeedToRememberPasswordRowKey, + ), + Divider( + color: context.theme.colors.hover, + ), + IconTextRowWidget( + assetName: Assets.syncIcon, + text: context.derivPasskeysLocalizations + .syncAcrossDevices, + textKey: effortlessPasskeysPageKeys + ?.syncAcrossDevicesRowKey, + ), + Divider( + color: context.theme.colors.hover, + ), + IconTextRowWidget( + assetName: Assets.lockIcon, + text: context.derivPasskeysLocalizations + .useYourBiometrics, + textKey: effortlessPasskeysPageKeys + ?.useBiometricsRowKey, + ), + Divider( + color: context.theme.colors.hover, + ), + SizedBox( + width: double.infinity, + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: + '${context.derivPasskeysLocalizations.learnMoreAboutPasskeys} ', + style: TextStyle( + color: context + .theme.colors.general, + )), + WidgetSpan( + alignment: + PlaceholderAlignment.middle, + child: InkWell( + key: effortlessPasskeysPageKeys + ?.hyperlinkInkWellKey, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => + LearnMorePasskeysPage( + onPageClosed: + (BuildContext + context) { + Navigator.pop(context); + }, addMorePasskeysNavigationCallback: + (BuildContext + context) { + trackAddMorePasskeys(); + addMorePasskeysNavigationCallback( + context); + }, continueTradingNavigationCallback: + (BuildContext + context) { + trackContinueTrading(); + continueTradingNavigationCallback( + context); + }), + ), + ); + }, + child: Text( + '${context.derivPasskeysLocalizations.here}.', + style: TextStyle( + color: context + .theme.colors.coral), + key: effortlessPasskeysPageKeys + ?.hereRichTextKey, + ), + ), + ), + ], + ), + ), + ) + ], + ), + ), + ), + Container( + width: double.infinity, + child: Padding( + padding: const EdgeInsets.all(16), + child: PrimaryButton( + onPressed: () { + trackCreatePasskey(); + context.read().add( + DerivPasskeysCreateCredentialEvent()); + }, + child: Text( + context + .derivPasskeysLocalizations.createPasskey, + key: effortlessPasskeysPageKeys + ?.createPasskeyButtonKey, + style: TextStyle( + color: context.theme.colors.prominent, + ), + ), + ), + ), + ) + ], + ), + ), + ), + ), + ), + ), + ), + ), + ); +} + +/// A class that allows users to pass keys to [EffortlessPasskeysPage]. +class EffortlessPasskeysPageKeys { + /// Constructs [EffortlessPasskeysPageKeys]. + EffortlessPasskeysPageKeys({ + this.createPasskeyButtonKey, + this.hyperlinkInkWellKey, + this.maybeLaterTextButtonKey, + this.loginWithPasskeyTextKey, + this.noNeedToRememberPasswordRowKey, + this.syncAcrossDevicesRowKey, + this.useBiometricsRowKey, + this.hereRichTextKey, + }); + + /// Key for create passkey button widget. + final Key? createPasskeyButtonKey; + + /// Key for hyper link ink well widget. + final Key? hyperlinkInkWellKey; + + /// Key for maybe later text button widget. + final Key? maybeLaterTextButtonKey; + + /// Key for login with passkey text widget. + final Key? loginWithPasskeyTextKey; + + /// Key for no need to remember password row widget. + final Key? noNeedToRememberPasswordRowKey; + + /// Key for sync across devices row widget. + final Key? syncAcrossDevicesRowKey; + + /// Key for use biometrics row widget. + final Key? useBiometricsRowKey; + + /// Key for here rich text widget. + final Key? hereRichTextKey; +} diff --git a/packages/deriv_passkeys/lib/src/presentation/pages/learn_more_passkeys_page.dart b/packages/deriv_passkeys/lib/src/presentation/pages/learn_more_passkeys_page.dart new file mode 100644 index 000000000..f4adc2f3a --- /dev/null +++ b/packages/deriv_passkeys/lib/src/presentation/pages/learn_more_passkeys_page.dart @@ -0,0 +1,284 @@ +import 'package:deriv_passkeys/deriv_passkeys.dart'; +import 'package:deriv_passkeys/src/core/extensions/context_extensions.dart'; +import 'package:deriv_passkeys/src/presentation/constants/assets.dart'; +import 'package:deriv_passkeys/src/presentation/mixins/passkey_event_tracking_mixin.dart'; +import 'package:deriv_passkeys/src/presentation/pages/passkey_created_page.dart'; +import 'package:deriv_passkeys/src/presentation/widgets/passkey_created_call_to_action.dart'; +import 'package:deriv_passkeys/src/presentation/widgets/section_title_and_content.dart'; +import 'package:deriv_passkeys/src/presentation/widgets/unordered_list_widget.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/widgets/primary_button.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +/// A stateless widget to build the Learn More Passkeys page. +class LearnMorePasskeysPage extends StatelessWidget + with PasskeyEventTrackingMixin { + /// Creates a [LearnMorePasskeysPage]. + LearnMorePasskeysPage({ + required this.onPageClosed, + required this.addMorePasskeysNavigationCallback, + required this.continueTradingNavigationCallback, + this.onExitPasskeysFlow, + super.key, + }) { + trackOpenLearnMorePage(); + } + + /// Callback to be called when the flow is complete. + final void Function(BuildContext context) onPageClosed; + + /// Callback to be called when the user wants to add more passkeys. + final void Function(BuildContext context) addMorePasskeysNavigationCallback; + + /// Callback to be called when the user wants to continue trading. + final void Function(BuildContext context) continueTradingNavigationCallback; + + /// Callback to be called when user exits the passkey flow and continues working with the app. + final void Function()? onExitPasskeysFlow; + + @override + Widget build(BuildContext context) => WillPopScope( + onWillPop: () async { + trackCloseLearnMorePage(); + return true; + }, + child: BlocListener( + listener: (BuildContext context, DerivPasskeysState state) { + if (state is DerivPasskeysCreatedSuccessfullyState) { + Navigator.pop(context); + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (BuildContext context) => PasskeyCreatedPage( + onPageClose: onPageClosed, + onExitPasskeysFlow: onExitPasskeysFlow, + bottomCallToAction: PasskeysCreatedCallToAction( + addMorePasskeysNavigationCallback: + addMorePasskeysNavigationCallback, + continueTradingNavigationCallback: + (BuildContext context) { + continueTradingNavigationCallback(context); + if (onExitPasskeysFlow != null) { + onExitPasskeysFlow!.call(); + } + }, + ), + ), + ), + ); + } + }, + child: Scaffold( + appBar: AppBar(), + body: Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: SafeArea( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 14), + child: SvgPicture.asset( + Assets.effortlessPasskeysIcon, + package: 'deriv_passkeys', + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 24), + child: Text( + context + .derivPasskeysLocalizations.effortlessLogin, + style: const TextStyle(fontSize: 20), + ), + ), + SectionTitleAndContent( + title: context + .derivPasskeysLocalizations.whatArePasskeys, + texts: [ + context.derivPasskeysLocalizations + .whatArePasskeysDescriptionPoint1, + context.derivPasskeysLocalizations + .whatArePasskeysDescriptionPoint2 + ], + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Divider( + color: context.theme.colors.hover, + ), + ), + SectionTitleAndContent( + title: context + .derivPasskeysLocalizations.whyPasskeys, + texts: [ + context.derivPasskeysLocalizations + .whyPasskeysDescription1, + context.derivPasskeysLocalizations + .whyPasskeysDescription2 + ], + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Divider( + color: context.theme.colors.hover, + ), + ), + SectionTitleAndContent( + title: context.read().isDp2p + ? context.derivPasskeysLocalizations + .p2pHowToCreatePasskey + : context.derivPasskeysLocalizations + .howToCreatePasskey, + texts: [ + context.read().isDp2p + ? context.derivPasskeysLocalizations + .p2pHowToCreatePasskeyDescription1 + : context.derivPasskeysLocalizations + .howToCreatePasskeyDescription1, + context.read().isDp2p + ? context.derivPasskeysLocalizations + .p2pHowToCreatePasskeyDescription2 + : context.derivPasskeysLocalizations + .howToCreatePasskeyDescription2 + ], + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Divider( + color: context.theme.colors.hover, + ), + ), + SectionTitleAndContent( + title: context.derivPasskeysLocalizations + .whereArePasskeysSaved, + texts: [ + context.derivPasskeysLocalizations + .whereArePasskeysSavedDescriptionAndroid, + context.derivPasskeysLocalizations + .whereArePasskeysSavedDescriptionIOS + ], + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Divider( + color: context.theme.colors.hover, + ), + ), + SectionTitleAndContent( + title: context.derivPasskeysLocalizations + .whatHappensIfEmailChanged, + texts: [ + context.derivPasskeysLocalizations + .whatHappensIfEmailChangedDescription1, + context.derivPasskeysLocalizations + .whatHappensIfEmailChangedDescription2 + ], + ), + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + margin: const EdgeInsets.only(top: 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: context.theme.colors.active, + ), + ), + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: Alignment.centerLeft, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SvgPicture.asset( + Assets.lightBulbIcon, + package: 'deriv_passkeys', + ), + const SizedBox( + width: 8, + ), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + '${context.derivPasskeysLocalizations.tips}:', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: + context.theme.colors.prominent, + ), + ), + Text( + '${context.derivPasskeysLocalizations.beforeUsingPasskeys}:', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: context.theme.colors.general, + ), + ), + const SizedBox( + height: 4, + ), + UnorderedList( + texts: [ + context.derivPasskeysLocalizations + .enableScreenLock, + context.derivPasskeysLocalizations + .signInGoogleOrIcloud, + context.derivPasskeysLocalizations + .enableBluetooth + ], + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: context.theme.colors.general, + ), + ), + ], + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), + ), + Container( + color: context.theme.colors.secondary, + width: double.infinity, + child: Padding( + padding: const EdgeInsets.only( + left: 24, right: 24, bottom: 24, top: 16), + child: PrimaryButton( + onPressed: () { + context + .read() + .add(DerivPasskeysCreateCredentialEvent()); + }, + child: Text( + context.derivPasskeysLocalizations.createPasskey, + style: TextStyle( + color: context.theme.colors.prominent, + ), + ), + ), + ), + ), + ], + ), + ), + ), + ); +} diff --git a/packages/deriv_passkeys/lib/src/presentation/pages/manage_passkeys_page.dart b/packages/deriv_passkeys/lib/src/presentation/pages/manage_passkeys_page.dart new file mode 100644 index 000000000..6a2b97bc6 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/presentation/pages/manage_passkeys_page.dart @@ -0,0 +1,236 @@ +import 'package:deriv_passkeys/src/core/extensions/context_extensions.dart'; +import 'package:deriv_passkeys/src/presentation/constants/assets.dart'; +import 'package:deriv_passkeys/src/presentation/mixins/passkey_event_tracking_mixin.dart'; +import 'package:deriv_passkeys/src/presentation/pages/learn_more_passkeys_page.dart'; +import 'package:deriv_passkeys/src/presentation/pages/passkey_created_page.dart'; +import 'package:deriv_passkeys/src/presentation/states/bloc/deriv_passkeys_bloc.dart'; +import 'package:deriv_passkeys/src/presentation/utils/handle_errors_utils.dart'; +import 'package:deriv_passkeys/src/presentation/widgets/passkey_created_call_to_action.dart'; +import 'package:deriv_passkeys/src/presentation/widgets/passkey_widget.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; + +/// A stateless widget to build the Manage Passkeys page. +class ManagePasskeysPage extends StatefulWidget { + /// Creates a [ManagePasskeysPage]. + const ManagePasskeysPage({ + required this.addMorePasskeysNavigationCallback, + required this.continueTradingNavigationCallback, + this.onExitPasskeysFlow, + super.key, + }); + + /// The route name for the manage passkeys page. + static const String routeName = 'manage_passkeys_page'; + + /// Callback to be called when the user wants to add more passkeys. + final void Function(BuildContext context) addMorePasskeysNavigationCallback; + + /// Callback to be called when the user wants to continue trading. + final void Function(BuildContext context) continueTradingNavigationCallback; + + /// Callback to be called when user exits the passkey flow and continues working with the app. + final void Function()? onExitPasskeysFlow; + + @override + State createState() => _ManagePasskeysPageState(); +} + +class _ManagePasskeysPageState extends State + with PasskeyEventTrackingMixin { + @override + void initState() { + super.initState(); + trackOpenManagePasskeysPage(); + context + .read() + .add(const DerivPasskeysGetPasskeysListEvent()); + } + + @override + Widget build(BuildContext context) => WillPopScope( + onWillPop: () async { + trackCloseManagePasskeysPage(); + return true; + }, + child: Scaffold( + backgroundColor: context.theme.colors.primary, + appBar: AppBar( + title: const Text('Passkeys', style: TextStyle(fontSize: 20)), + actions: [ + InkWell( + child: Padding( + padding: const EdgeInsets.all(16), + child: SvgPicture.asset( + Assets.learnMorePasskeysIcon, + package: 'deriv_passkeys', + ), + ), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => LearnMorePasskeysPage( + onPageClosed: (BuildContext context) { + Navigator.pop(context); + }, + addMorePasskeysNavigationCallback: + (BuildContext context) { + trackAddMorePasskeys(); + widget.addMorePasskeysNavigationCallback(context); + }, + continueTradingNavigationCallback: + (BuildContext context) { + trackContinueTrading(); + widget.continueTradingNavigationCallback(context); + }, + ), + ), + ); + }, + ), + ], + ), + body: BlocConsumer( + listener: (BuildContext context, DerivPasskeysState state) { + if (state is DerivPasskeysCreatedSuccessfullyState) { + trackCreatePasskeySuccess(); + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => PasskeyCreatedPage( + onExitPasskeysFlow: () { + if (widget.onExitPasskeysFlow != null) { + widget.onExitPasskeysFlow!.call(); + } + }, + onPageClose: (BuildContext context) { + Navigator.pop(context); + }, + bottomCallToAction: PasskeysCreatedCallToAction( + addMorePasskeysNavigationCallback: + (BuildContext context) { + trackAddMorePasskeys(); + widget.addMorePasskeysNavigationCallback(context); + }, + continueTradingNavigationCallback: + (BuildContext context) { + trackContinueTrading(); + widget.continueTradingNavigationCallback(context); + + if (widget.onExitPasskeysFlow != null) { + widget.onExitPasskeysFlow!.call(); + } + }, + ), + ), + ), + ); + } else if (state is DerivPasskeysErrorState) { + trackPasskeyError('${state.errorCode}: ${state.message}'); + handlePasskeysError(context, state); + } + }, builder: (BuildContext context, DerivPasskeysState state) { + if (state is DerivPasskeysLoadedState) { + return SafeArea( + child: SizedBox( + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildDerivPasskeysLoadedContent( + context, + state, + ), + Container( + width: double.infinity, + child: Padding( + padding: const EdgeInsets.all(16), + child: PrimaryButton( + onPressed: () { + trackCreatePasskey(); + context + .read() + .add(DerivPasskeysCreateCredentialEvent()); + }, + child: Text( + context.derivPasskeysLocalizations.createPasskey, + style: TextStyle( + color: context.theme.colors.prominent, + ), + ), + ), + ), + ) + ], + ), + ), + ); + } + return const SizedBox(); + }), + ), + ); + + Widget _buildDerivPasskeysListContent( + BuildContext context, + DerivPasskeysLoadedState state, + ) => + Expanded( + child: Padding( + padding: const EdgeInsets.all(24), + child: ListView.separated( + separatorBuilder: (BuildContext context, int index) => + const Divider(), + itemCount: state.passkeysList.length, + itemBuilder: (BuildContext context, int index) => Padding( + padding: const EdgeInsets.only(bottom: 16), + child: PasskeyWidget(passkey: state.passkeysList[index]), + ), + ), + ), + ); + + Widget _buildDerivPasskeysEmptyContent(BuildContext context) => Expanded( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 14), + child: SvgPicture.asset( + Assets.addPasskeyIcon, + package: 'deriv_passkeys', + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 24), + child: Text( + context.derivPasskeysLocalizations.experienceSaferLogins, + style: const TextStyle(fontSize: 20), + ), + ), + SizedBox( + width: double.infinity, + child: Text( + context.derivPasskeysLocalizations.enhanceSecurity, + textAlign: TextAlign.center, + ), + ) + ], + ), + ), + ); + + Widget _buildDerivPasskeysLoadedContent( + BuildContext context, DerivPasskeysLoadedState state) { + if (state.passkeysList.isEmpty) { + return _buildDerivPasskeysEmptyContent(context); + } + return _buildDerivPasskeysListContent(context, state); + } +} diff --git a/packages/deriv_passkeys/lib/src/presentation/pages/passkey_created_page.dart b/packages/deriv_passkeys/lib/src/presentation/pages/passkey_created_page.dart new file mode 100644 index 000000000..c91707bfa --- /dev/null +++ b/packages/deriv_passkeys/lib/src/presentation/pages/passkey_created_page.dart @@ -0,0 +1,91 @@ +import 'package:deriv_passkeys/src/core/extensions/context_extensions.dart'; +import 'package:deriv_passkeys/src/presentation/constants/assets.dart'; +import 'package:deriv_passkeys/src/presentation/mixins/passkey_event_tracking_mixin.dart'; +import 'package:deriv_passkeys/src/presentation/utils/platform_utils.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +/// A stateless widget to build the passkey created successfully page. +class PasskeyCreatedPage extends StatelessWidget + with PasskeyEventTrackingMixin { + /// Creates a [PasskeyCreatedPage]. + PasskeyCreatedPage({ + required this.onPageClose, + required this.bottomCallToAction, + this.onExitPasskeysFlow, + super.key, + }) { + trackCreatePasskeySuccess(); + } + + /// A callback function that will be called when the user clicks on the 'Continue' button. + final void Function(BuildContext context) onPageClose; + + /// Callback to be called when user exits the passkey flow and continues working with the app. + final void Function()? onExitPasskeysFlow; + + /// The widget to be displayed at the bottom of the page. + final Widget bottomCallToAction; + + @override + Widget build(BuildContext context) => Scaffold( + appBar: AppBar( + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: () { + onPageClose(context); + if (onExitPasskeysFlow != null) { + onExitPasskeysFlow!.call(); + } + }, + ), + ), + body: SafeArea( + child: SizedBox( + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 14), + child: SvgPicture.asset( + Assets.passkeyCreatedSuccessIcon, + package: 'deriv_passkeys', + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 24), + child: Text( + context.derivPasskeysLocalizations + .passkeyCreatedSuccessTitle, + style: const TextStyle(fontSize: 20), + ), + ), + Text( + context.derivPasskeysLocalizations + .passkeyCreatedSuccessMessage( + platformName(context)), + style: TextStyle( + fontSize: 14, + color: context.theme.colors.general, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + bottomCallToAction, + ], + ), + ), + ), + ); +} diff --git a/packages/deriv_passkeys/lib/src/presentation/presentation.dart b/packages/deriv_passkeys/lib/src/presentation/presentation.dart new file mode 100644 index 000000000..d533211de --- /dev/null +++ b/packages/deriv_passkeys/lib/src/presentation/presentation.dart @@ -0,0 +1,6 @@ +library deriv_passkeys; + +export './pages/manage_passkeys_page.dart'; +export './pages/effortless_passkeys_login_page.dart'; +export './widgets/continue_with_passkey_button.dart'; +export './states/bloc/deriv_passkeys_bloc.dart'; diff --git a/packages/deriv_passkeys/lib/src/presentation/states/bloc/deriv_passkeys_bloc.dart b/packages/deriv_passkeys/lib/src/presentation/states/bloc/deriv_passkeys_bloc.dart new file mode 100644 index 000000000..3280f06d3 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/presentation/states/bloc/deriv_passkeys_bloc.dart @@ -0,0 +1,176 @@ +import 'dart:io'; + +import 'package:analytics/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart'; +import 'package:deriv_passkeys/src/data/repositories/passkey_analytics_repository.dart'; +import 'package:deriv_passkeys/src/domain/entities/account_entity.dart'; +import 'package:deriv_passkeys/src/exceptions/platform_exceptions.dart'; +import 'package:deriv_passkeys/src/domain/entities/passkeys_connection_info_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkey_entity.dart'; +import 'package:deriv_passkeys/src/exceptions/server_exceptions.dart'; +import 'package:deriv_passkeys/src/interactor/services/deriv_passkeys_service.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_verify_credentials_response_entity.dart'; + +part 'deriv_passkeys_state.dart'; + +part 'deriv_passkeys_event.dart'; + +/// [DerivPasskeysBloc] handles the state within the DerivPasskeys flow. +class DerivPasskeysBloc extends Bloc { + /// Creates a [DerivPasskeysBloc]. + DerivPasskeysBloc({ + required DerivPasskeysService derivPasskeysService, + required this.connectionInfo, + required Future Function() getJwtToken, + }) : super(DerivPasskeysLoadingState()) { + on( + (SetDerivPasskeysInitializedEvent event, + Emitter emit) async { + emit(DerivPasskeysInitializedState()); + }); + + on( + (SetDerivPasskeysNotSupportedEvent event, + Emitter emit) async { + emit(DerivPasskeysNotSupportedState()); + }); + + on( + (DerivPasskeysVerifyCredentialEvent event, + Emitter emit) async { + if (state is DerivPasskeysLoadingState) { + return; + } + + emit(DerivPasskeysLoadingState()); + + final String jwtToken = await getJwtToken(); + + await derivPasskeysService + .verifyCredential( + jwtToken: jwtToken, + passkeysConnectionInfoEntity: passkeysConnectionInfo ?? connectionInfo, + userAgent: WebSocket.userAgent, + ) + .then((DerivPasskeysVerifyCredentialsResponseEntity + derivPasskeysVerifyCredentialsResponseEntity) { + emit( + DerivPasskeysCredentialVerifiedState( + accounts: derivPasskeysVerifyCredentialsResponseEntity.accounts, + refreshToken: + derivPasskeysVerifyCredentialsResponseEntity.refreshToken, + ), + ); + }).catchError((Object error) { + if (error is CanceledPlatformException) { + emit(DerivPasskeysLoadedState(passkeysList)); + } else if (error is NoCredentialPlatformException) { + emit(const NoCredentialErrorState()); + } else if (error is ServerException) { + emit( + DerivPasskeysErrorState( + error.message, + errorCode: error.errorCode, + ), + ); + } else { + emit(const DerivPasskeysErrorState('Error verifying passkey')); + } + }); + }); + + on( + (DerivPasskeysCreateCredentialEvent event, + Emitter emit) async { + if (state is DerivPasskeysLoadingState) { + return; + } + emit(DerivPasskeysLoadingState()); + await derivPasskeysService + .createCredential(loginId: loginId) + .then((DerivPasskeyEntity credential) async { + emit(DerivPasskeysCreatedSuccessfullyState()); + final DerivPasskeyEntity derivPasskeyEntity = credential; + passkeysList.add(derivPasskeyEntity); + emit(DerivPasskeysLoadedState(passkeysList)); + }).catchError((Object error) { + if (error is ServerException) { + emit( + DerivPasskeysErrorState( + error.message, + errorCode: error.errorCode, + ), + ); + } else { + emit(const DerivPasskeysErrorState('Error creating passkey')); + } + emit(DerivPasskeysLoadedState(passkeysList)); + }); + }); + + on( + (DerivPasskeysGetPasskeysListEvent event, + Emitter emit) async { + emit(DerivPasskeysLoadingState()); + loginId = event.loginId; + await derivPasskeysService + .getPasskeysList(loginId: loginId) + .then((List _passkeysList) { + passkeysList = _passkeysList; + emit(DerivPasskeysLoadedState(passkeysList)); + }).catchError((Object error) { + if (error is ServerException) { + emit( + DerivPasskeysErrorState( + error.message, + errorCode: error.errorCode, + ), + ); + } else { + emit(DerivPasskeysErrorState(error.toString())); + } + }); + }); + + on( + (DerivPasskeysLogoutEvent event, Emitter emit) { + passkeysList.clear(); + if (state is DerivPasskeysNotSupportedState) { + emit(DerivPasskeysNotSupportedState()); + } else { + emit(DerivPasskeysInitializedState()); + } + }); + + derivPasskeysService.isSupported().then((bool isSupported) { + if (isSupported) { + add(const SetDerivPasskeysInitializedEvent()); + } else { + add(const SetDerivPasskeysNotSupportedEvent()); + } + }); + + /// Initialize the analytics repository. + AnalyticsRepository.init( + connectionInfo.appId, + derivRudderstack: DerivRudderstack(), + ); + } + + /// Default account loginId used for multi-token authorization + String? loginId; + + /// Passkeys connection info entity. + final PasskeysConnectionInfoEntity connectionInfo; + + /// The list of passkeys. + List passkeysList = []; + + /// Passkeys info entity which can be used to assign new changes to the + /// connection info at anytime. + PasskeysConnectionInfoEntity? passkeysConnectionInfo; + + /// Determines whether the app is DP2P or not. + bool get isDp2p => connectionInfo.appId == '1408'; +} diff --git a/packages/deriv_passkeys/lib/src/presentation/states/bloc/deriv_passkeys_event.dart b/packages/deriv_passkeys/lib/src/presentation/states/bloc/deriv_passkeys_event.dart new file mode 100644 index 000000000..1756f8388 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/presentation/states/bloc/deriv_passkeys_event.dart @@ -0,0 +1,73 @@ +part of 'deriv_passkeys_bloc.dart'; + +/// [DerivPasskeysEvent] represents the events within the DerivPasskeys flow. +class DerivPasskeysEvent extends Equatable { + /// Creates a [DerivPasskeysEvent]. + const DerivPasskeysEvent(); + + @override + List get props => []; +} + +/// [DerivPasskeysCreateCredentialEvent] represents the create credential event within the DerivPasskeys flow. +class DerivPasskeysCreateCredentialEvent extends DerivPasskeysEvent {} + +/// [DerivPasskeysVerifyCredentialEvent] represents the get credential event within the DerivPasskeys flow. +class DerivPasskeysVerifyCredentialEvent extends DerivPasskeysEvent {} + +/// [DerivPasskeysGetPasskeysListEvent] represents the get passkeys list event within the DerivPasskeys flow. +class DerivPasskeysGetPasskeysListEvent extends DerivPasskeysEvent { + /// Creates a [DerivPasskeysGetPasskeysListEvent]. + const DerivPasskeysGetPasskeysListEvent({this.loginId}); + + /// Default account loginId used for multi-token authorization + final String? loginId; + + @override + List get props => []; +} + +/// [SetDerivPasskeysInitializedEvent] represents the set initialized event within the DerivPasskeys flow. +class SetDerivPasskeysInitializedEvent extends DerivPasskeysEvent { + /// Creates a [SetDerivPasskeysInitializedEvent]. + const SetDerivPasskeysInitializedEvent(); + + @override + List get props => []; +} + +/// [SetDerivPasskeysNotSupportedEvent] represents the set not supported event within the DerivPasskeys flow. +class SetDerivPasskeysNotSupportedEvent extends DerivPasskeysEvent { + /// Creates a [SetDerivPasskeysNotSupportedEvent]. + const SetDerivPasskeysNotSupportedEvent(); + + @override + List get props => []; +} + +/// [DerivPasskeysRevokeCredentialEvent] represents the revoke credential event within the DerivPasskeys flow. +class DerivPasskeysRevokeCredentialEvent extends DerivPasskeysEvent { + /// Creates a [DerivPasskeysRevokeCredentialEvent]. + const DerivPasskeysRevokeCredentialEvent(this.options); + + /// The options to revoke a credential. + final String options; + + @override + List get props => [options]; +} + +/// [DerivPasskeysEditCredentialEvent] represents the edit credential event within the DerivPasskeys flow. +class DerivPasskeysEditCredentialEvent extends DerivPasskeysEvent { + /// Creates a [DerivPasskeysEditCredentialEvent]. + const DerivPasskeysEditCredentialEvent(this.options); + + /// The options to edit a credential. + final String options; + + @override + List get props => [options]; +} + +/// [DerivPasskeysLogoutEvent] represents the logout event within the DerivPasskeys flow. +class DerivPasskeysLogoutEvent extends DerivPasskeysEvent {} diff --git a/packages/deriv_passkeys/lib/src/presentation/states/bloc/deriv_passkeys_state.dart b/packages/deriv_passkeys/lib/src/presentation/states/bloc/deriv_passkeys_state.dart new file mode 100644 index 000000000..c40413036 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/presentation/states/bloc/deriv_passkeys_state.dart @@ -0,0 +1,73 @@ +part of 'deriv_passkeys_bloc.dart'; + +/// [DerivPasskeysState] represents the state within the DerivPasskeys flow. +abstract class DerivPasskeysState extends Equatable { + /// Creates a [DerivPasskeysState]. + const DerivPasskeysState(); + + @override + List get props => []; +} + +/// [DerivPasskeysLoadedState] represents the loaded state within the DerivPasskeys flow. +class DerivPasskeysLoadedState extends DerivPasskeysState { + /// Creates a [DerivPasskeysLoadedState]. + const DerivPasskeysLoadedState(this.passkeysList); + + /// The passkeys list. + final List passkeysList; + + @override + List get props => [passkeysList]; +} + +/// [DerivPasskeysInitializedState] represents the initialized state within the DerivPasskeys flow. +class DerivPasskeysInitializedState extends DerivPasskeysState {} + +/// [DerivPasskeysLoadingState] represents the loading state within the DerivPasskeys flow. +class DerivPasskeysLoadingState extends DerivPasskeysState {} + +/// [DerivPasskeysCreatedSuccessfullyState] represents the created successfully state within the DerivPasskeys flow. +class DerivPasskeysCreatedSuccessfullyState extends DerivPasskeysState {} + +/// [DerivPasskeysCredentialVerifiedState] represents the credential verified state within the DerivPasskeys flow. +class DerivPasskeysCredentialVerifiedState extends DerivPasskeysState { + /// Creates a [DerivPasskeysCredentialVerifiedState]. + const DerivPasskeysCredentialVerifiedState({ + required this.accounts, + required this.refreshToken, + }); + + /// List of accounts from the response. + final List accounts; + + /// Refresh token from the response. + final String refreshToken; + + @override + List get props => [accounts, refreshToken]; +} + +/// [DerivPasskeysNotSupportedState] represents the not supported state within the DerivPasskeys flow. +class DerivPasskeysNotSupportedState extends DerivPasskeysState {} + +/// [DerivPasskeysErrorState] represents the error state within the DerivPasskeys flow. +class DerivPasskeysErrorState extends DerivPasskeysState { + /// Creates a [DerivPasskeysErrorState]. + const DerivPasskeysErrorState(this.message, {this.errorCode = ''}); + + /// The error message. + final String message; + + /// The error code + final String errorCode; + + @override + List get props => [message, errorCode]; +} + +/// [NoCredentialErrorState] represents the no credential error state within the DerivPasskeys flow. +class NoCredentialErrorState extends DerivPasskeysErrorState { + /// Creates a [NoCredentialErrorState]. + const NoCredentialErrorState() : super('No credential found'); +} diff --git a/packages/deriv_passkeys/lib/src/presentation/utils/date_time_utils.dart b/packages/deriv_passkeys/lib/src/presentation/utils/date_time_utils.dart new file mode 100644 index 000000000..f7949588d --- /dev/null +++ b/packages/deriv_passkeys/lib/src/presentation/utils/date_time_utils.dart @@ -0,0 +1,10 @@ +import 'package:jiffy/jiffy.dart'; + +///returns DateTime from timestamp +DateTime dateTimeFromTimestamp(int timestamp) => + DateTime.fromMillisecondsSinceEpoch(timestamp); + +///returns formatted date +///e.g. April 20th, 2023 +String formattedDate(DateTime date) => + Jiffy.parseFromDateTime(date).format(pattern: 'MMMM do, yyyy'); diff --git a/packages/deriv_passkeys/lib/src/presentation/utils/handle_errors_utils.dart b/packages/deriv_passkeys/lib/src/presentation/utils/handle_errors_utils.dart new file mode 100644 index 000000000..7358e6d8f --- /dev/null +++ b/packages/deriv_passkeys/lib/src/presentation/utils/handle_errors_utils.dart @@ -0,0 +1,58 @@ +//handlePasskeysError +import 'package:deriv_passkeys/deriv_passkeys.dart'; +import 'package:deriv_passkeys/src/core/extensions/context_extensions.dart'; +import 'package:deriv_ui/widgets/popup_alert_dialog.dart'; +import 'package:flutter/material.dart'; + +/// Handles the passkeys error state. +void handlePasskeysError(BuildContext context, DerivPasskeysErrorState state) { + if (state is NoCredentialErrorState) { + showDialog( + context: context, + builder: (BuildContext context) => PopupAlertDialog( + title: context.derivPasskeysLocalizations.noPasskeyFound, + content: + Text(context.derivPasskeysLocalizations.noPasskeyFoundDescription), + positiveButtonLabel: context.derivPasskeysLocalizations.ok, + onPositiveActionPressed: () { + Navigator.pop(context, context.derivPasskeysLocalizations.ok); + }, + ), + ); + } else { + bool showErrorDialogue = false; + String title = context.derivPasskeysLocalizations.unexpectedError; + String content = state.message; + + if (state.errorCode == 'PASSKEYS_SERVICE_ERROR') { + content = state.message; + showErrorDialogue = true; + } + if (state.errorCode == 'PASSKEYS_NOT_FOUND') { + title = context.derivPasskeysLocalizations.noPasskeyFound; + content = state.message; + showErrorDialogue = true; + } + + if (state.errorCode == 'PASSKEYS_OFF' || state.errorCode == 'PasskeysOff') { + title = context.derivPasskeysLocalizations.unable_to_process_your_request; + content = context.derivPasskeysLocalizations + .unable_to_process_your_request_description; + showErrorDialogue = true; + } + + if (showErrorDialogue) { + showDialog( + context: context, + builder: (BuildContext context) => PopupAlertDialog( + title: title, + content: Text(content), + positiveButtonLabel: context.derivPasskeysLocalizations.ok, + onPositiveActionPressed: () { + Navigator.pop(context, context.derivPasskeysLocalizations.ok); + }, + ), + ); + } + } +} diff --git a/packages/deriv_passkeys/lib/src/presentation/utils/platform_utils.dart b/packages/deriv_passkeys/lib/src/presentation/utils/platform_utils.dart new file mode 100644 index 000000000..1e217a7e9 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/presentation/utils/platform_utils.dart @@ -0,0 +1,15 @@ +import 'dart:io'; + +import 'package:deriv_passkeys/src/core/extensions/context_extensions.dart'; +import 'package:flutter/widgets.dart'; + +/// Returns the platform name. +String platformName(BuildContext context) { + if (Platform.isIOS) { + return 'IOS'; + } + if (Platform.isAndroid) { + return 'Android'; + } + return context.derivPasskeysLocalizations.unsupportedPlatform; +} diff --git a/packages/deriv_passkeys/lib/src/presentation/widgets/continue_with_passkey_button.dart b/packages/deriv_passkeys/lib/src/presentation/widgets/continue_with_passkey_button.dart new file mode 100644 index 000000000..9bbfa6ca0 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/presentation/widgets/continue_with_passkey_button.dart @@ -0,0 +1,86 @@ +import 'package:deriv_passkeys/src/presentation/constants/assets.dart'; +import 'package:deriv_passkeys/src/presentation/states/bloc/deriv_passkeys_bloc.dart'; +import 'package:deriv_passkeys/src/presentation/utils/handle_errors_utils.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +/// A button that allows users to continue with passkey +class ContinueWithPasskeyButton extends StatelessWidget { + /// constructs a [ContinueWithPasskeyButton] + const ContinueWithPasskeyButton({ + required this.onTap, + super.key, + }); + + /// Function to handle on tap event. + final VoidCallback onTap; + + @override + Widget build(BuildContext context) => + BlocConsumer( + listener: (BuildContext context, DerivPasskeysState state) { + if (state is DerivPasskeysErrorState) { + handlePasskeysError(context, state); + } + }, + builder: (BuildContext context, DerivPasskeysState state) { + if (state is DerivPasskeysNotSupportedState) { + return const SizedBox(); + } + + return InkWell( + key: const Key('continueWithPasskeysButtonKey'), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: context.theme.colors.active, + ), + ), + child: state is DerivPasskeysLoadingState + ? Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + LoadingIndicator( + valueColor: context.theme.colors.prominent, + strokeWidth: ThemeProvider.margin02, + height: ThemeProvider.iconSize16, + width: ThemeProvider.iconSize16, + ) + ], + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + Assets.passkeySvgIcon, + package: 'deriv_passkeys', + ), + const SizedBox(width: 8), + Text( + 'Passkey', + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent, + ), + ), + ], + ), + ), + onTap: () async { + onTap(); + context + .read() + .add(DerivPasskeysVerifyCredentialEvent()); + }, + ); + }, + ); +} diff --git a/packages/deriv_passkeys/lib/src/presentation/widgets/icon_text_row_widget.dart b/packages/deriv_passkeys/lib/src/presentation/widgets/icon_text_row_widget.dart new file mode 100644 index 000000000..595290de8 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/presentation/widgets/icon_text_row_widget.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +/// A widget that displays an icon and text in a row. +class IconTextRowWidget extends StatelessWidget { + /// Creates an [IconTextRowWidget]. + const IconTextRowWidget({ + required this.assetName, + required this.text, + this.textKey, + }); + + /// The name of the asset to display. + final String assetName; + + /// The text to display. + final String text; + + /// Key for text value + final Key? textKey; + + @override + Widget build(BuildContext context) => Row( + children: [ + SvgPicture.asset( + assetName, + package: 'deriv_passkeys', + ), + const SizedBox(width: 8), + Expanded(child: Text(text, key: textKey)), + ], + ); +} diff --git a/packages/deriv_passkeys/lib/src/presentation/widgets/passkey_created_call_to_action.dart b/packages/deriv_passkeys/lib/src/presentation/widgets/passkey_created_call_to_action.dart new file mode 100644 index 000000000..919aaaaa5 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/presentation/widgets/passkey_created_call_to_action.dart @@ -0,0 +1,49 @@ +import 'package:deriv_passkeys/src/core/extensions/context_extensions.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; + +/// A stateless widget to build the passkeys created call to action. +class PasskeysCreatedCallToAction extends StatelessWidget { + /// Creates a [PasskeysCreatedCallToAction]. + const PasskeysCreatedCallToAction({ + required this.addMorePasskeysNavigationCallback, + required this.continueTradingNavigationCallback, + super.key, + }); + + /// Callback to be called when the user wants to add more passkeys. + final void Function(BuildContext context) addMorePasskeysNavigationCallback; + + /// Callback to be called when the user wants to continue trading. + final void Function(BuildContext context) continueTradingNavigationCallback; + + @override + Widget build(BuildContext context) => Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SecondaryButton( + onPressed: () => addMorePasskeysNavigationCallback(context), + child: Text( + context.derivPasskeysLocalizations.addMorePasskeysButtonText, + style: TextStyle( + color: context.theme.colors.prominent, + ), + ), + ), + const SizedBox(width: 16), + PrimaryButton( + onPressed: () => continueTradingNavigationCallback(context), + child: Text( + context.derivPasskeysLocalizations.continueTradingButtonText, + style: TextStyle( + color: context.theme.colors.prominent, + ), + ), + ), + ], + ), + ); +} diff --git a/packages/deriv_passkeys/lib/src/presentation/widgets/passkey_widget.dart b/packages/deriv_passkeys/lib/src/presentation/widgets/passkey_widget.dart new file mode 100644 index 000000000..5b7f954f5 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/presentation/widgets/passkey_widget.dart @@ -0,0 +1,110 @@ +import 'package:deriv_passkeys/src/domain/entities/deriv_passkey_entity.dart'; +import 'package:deriv_passkeys/src/core/extensions/context_extensions.dart'; +import 'package:deriv_passkeys/src/presentation/constants/assets.dart'; +import 'package:deriv_passkeys/src/presentation/utils/date_time_utils.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +/// [PasskeyWidget] is a widget that displays the passkey information. +class PasskeyWidget extends StatefulWidget { + /// Creates a [PasskeyWidget]. + const PasskeyWidget({ + required this.passkey, + super.key, + }); + + /// The passkey entity. + final DerivPasskeyEntity passkey; + + @override + State createState() => _PasskeyWidgetState(); +} + +class _PasskeyWidgetState extends State { + String _lastUsed() { + String lastUsed = context.derivPasskeysLocalizations.never; + if (widget.passkey.lastUsed != null) { + lastUsed = formattedDate(dateTimeFromTimestamp(widget.passkey.lastUsed!)); + } + return lastUsed; + } + + @override + Widget build(BuildContext context) => Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: SvgPicture.asset( + Assets.passkeySvgIcon, + package: 'deriv_passkeys', + ), + ), + const SizedBox(width: 8), + Expanded( + flex: 10, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(widget.passkey.name, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: context.theme.colors.prominent, + )), + Text( + '${context.derivPasskeysLocalizations.storedOn}: ${widget.passkey.storedOn}', + style: TextStyle( + fontSize: 12, + color: context.theme.colors.general, + ), + ), + Text( + '${context.derivPasskeysLocalizations.lastUsed}: ${_lastUsed()}', + style: TextStyle( + fontSize: 12, + color: context.theme.colors.general, + ), + ), + ], + ), + ), + // TODO(bassam): uncomment and implement once phase 2 starts + // DropdownButtonHideUnderline( + // child: DropdownButton( + // icon: Icon(Icons.more_vert), + // items: [ + // TextButton( + // onPressed: () { + // }, + // child: Text( + // context.derivPasskeysLocalizations.rename, + // style: TextStyle( + // fontSize: 14, + // color: context.theme.colors.general, + // ), + // ), + // ), + // TextButton( + // onPressed: () { + // }, + // child: Text( + // context.derivPasskeysLocalizations.revoke, + // style: TextStyle( + // fontSize: 14, + // color: context.theme.colors.general, + // ), + // ), + // ), + // ] + // .map((Widget item) => DropdownMenuItem( + // value: item, + // child: item, + // )) + // .toList(), + // onChanged: (Widget? value) {}, + // ), + // ), + ], + ); +} diff --git a/packages/deriv_passkeys/lib/src/presentation/widgets/section_title_and_content.dart b/packages/deriv_passkeys/lib/src/presentation/widgets/section_title_and_content.dart new file mode 100644 index 000000000..a70aa359a --- /dev/null +++ b/packages/deriv_passkeys/lib/src/presentation/widgets/section_title_and_content.dart @@ -0,0 +1,42 @@ +import 'package:deriv_passkeys/src/presentation/widgets/unordered_list_widget.dart'; +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; + +/// A widget that displays a section title and content. +class SectionTitleAndContent extends StatelessWidget { + /// Creates a [SectionTitleAndContent]. + const SectionTitleAndContent({ + required this.title, + required this.texts, + }); + + /// The title of the section. + final String title; + + /// The content of the section. + final List texts; + + @override + Widget build(BuildContext context) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 14, + color: context.theme.colors.prominent, + ), + ), + const SizedBox(height: 8), + UnorderedList( + texts: texts, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: context.theme.colors.general, + ), + expandedContent: true, + ), + ], + ); +} diff --git a/packages/deriv_passkeys/lib/src/presentation/widgets/unordered_list_widget.dart b/packages/deriv_passkeys/lib/src/presentation/widgets/unordered_list_widget.dart new file mode 100644 index 000000000..76fc68465 --- /dev/null +++ b/packages/deriv_passkeys/lib/src/presentation/widgets/unordered_list_widget.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; + +/// A widget that displays a list of items with bullet points. +class UnorderedList extends StatelessWidget { + /// Creates an [UnorderedList]. + const UnorderedList({ + required this.texts, + this.style, + this.expandedContent = false, + }); + + /// The list of texts to display. + final List texts; + + /// The style to use for the text. + final TextStyle? style; + + /// Whether the content should be expanded. + final bool expandedContent; + + @override + Widget build(BuildContext context) { + final List widgetList = texts + .map((String text) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + UnorderedListItem( + text: text, + textStyle: style, + expandedContent: expandedContent, + ), + const SizedBox(height: 5), + ], + )) + .toList(); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: widgetList, + ); + } +} + +/// A widget that displays an unordered list item. +@visibleForTesting +class UnorderedListItem extends StatelessWidget { + /// Creates an [UnorderedListItem]. + const UnorderedListItem({ + required this.text, + this.textStyle, + this.expandedContent = false, + }); + + /// The text to display. + final String text; + + /// The style to use for the text. + final TextStyle? textStyle; + + /// Whether the content should be expanded. + final bool expandedContent; + + @override + Widget build(BuildContext context) { + Widget content = Text( + text, + style: textStyle, + ); + if (expandedContent) { + content = Expanded( + child: content, + ); + } + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + width: 5, + ), + Text('\u2022', style: textStyle), + const SizedBox( + width: 5, + ), + content, + ], + ); + } +} diff --git a/packages/deriv_passkeys/pubspec.yaml b/packages/deriv_passkeys/pubspec.yaml new file mode 100644 index 000000000..acb1d8128 --- /dev/null +++ b/packages/deriv_passkeys/pubspec.yaml @@ -0,0 +1,120 @@ +name: deriv_passkeys +description: Deriv Passkeys Flutter Plugin +version: 0.0.5+11 +publish_to: "none" + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + + analytics: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/analytics + ref: analytics-v4.1.0 + + deriv_theme: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_theme + ref: deriv_theme-v2.8.0 + + deriv_localizations: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_localizations + ref: deriv_localizations-v1.7.2 + + deriv_ui: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_ui + ref: deriv_ui-v0.1.1 + + deriv_http_client: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_http_client + ref: deriv_http_client-v2.0.2 + + flutter_deriv_api: + git: + url: git@github.com:deriv-com/flutter-deriv-api.git + ref: v1.3.0 + + flutter_svg: ^2.0.9 + plugin_platform_interface: ^2.0.2 + http: ^0.13.4 + jiffy: ^6.2.1 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + mocktail: ^1.0.3 + bloc_test: ^9.1.7 + +# 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: + uses-material-design: true + # 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. + plugin: + platforms: + android: + package: com.deriv.passkeys.deriv_passkeys + pluginClass: DerivPasskeysPlugin + ios: + pluginClass: DerivPasskeysPlugin + + assets: + - assets/svg/passkey_icon.svg + - assets/svg/effortless_login_passkey_icon.svg + - assets/svg/passkey_created_success_icon.svg + - assets/svg/fingerprint_icon.svg + - assets/svg/sync_icon.svg + - assets/svg/lock_icon.svg + - assets/svg/light_bulb_icon.svg + - assets/svg/learn_more_passkeys_icon.svg + - assets/svg/add_passkey_icon.svg + - assets/svg/face_id.svg + # + # 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/packages/deriv_passkeys/test/core/constants/tracking_event_params_helper_test.dart b/packages/deriv_passkeys/test/core/constants/tracking_event_params_helper_test.dart new file mode 100644 index 000000000..183505ba9 --- /dev/null +++ b/packages/deriv_passkeys/test/core/constants/tracking_event_params_helper_test.dart @@ -0,0 +1,277 @@ +import 'package:deriv_passkeys/src/core/constants/analytics_actions_enums.dart'; +import 'package:deriv_passkeys/src/core/constants/tracking_event_params_helper.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('getTrackingParams tests:\n\t', () { + group('efforless login flow -> ', () { + test('should return correct params for openEffortlessLoginPage', () { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + final Map result = getEffortlessLoginTrackingParams( + EffortlessPageActions.openEffortlessLoginPage, + ); + + expect(result, { + 'event_name': 'open', + 'params': { + 'form_name': 'ce_passkey_effortless_form_', + 'operating_system': 'IOS', + }, + }); + }); + + test('should return correct params for closeEffortlessLoginPage', () { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + final Map result = getEffortlessLoginTrackingParams( + EffortlessPageActions.closeEffortlessLoginPage, + ); + + expect(result, { + 'event_name': 'close', + 'params': { + 'form_name': 'ce_passkey_effortless_form_', + 'operating_system': 'IOS', + }, + }); + }); + + test('should return correct params for maybeLater', () { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + final Map result = getEffortlessLoginTrackingParams( + EffortlessPageActions.maybeLater, + ); + + expect(result, { + 'event_name': 'maybe_later', + 'params': { + 'form_name': 'ce_passkey_effortless_form_', + 'operating_system': 'IOS', + }, + }); + }); + }); + + group('tracking learn more page actions ->', () { + test('should return correct params for openLearnMorePage', () { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + final Map result = getLearnMoreTrackingParams( + LearnMorePageActions.openLearnMorePage, + 'main_form_name', + ); + + expect(result, { + 'event_name': 'info_open', + 'params': { + 'form_name': 'main_form_name', + 'operating_system': 'IOS', + }, + }); + }); + + test('should return correct params for closeLearnMorePage', () { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + final Map result = getLearnMoreTrackingParams( + LearnMorePageActions.closeLearnMorePage, + 'main_form_name', + ); + + expect(result, { + 'event_name': 'info_back', + 'params': { + 'form_name': 'main_form_name', + 'operating_system': 'IOS', + }, + }); + }); + }); + + group('tracking manage passkeys page actions ->', () { + test('should return correct params for openManagePasskeysPage', () { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + final Map result = getManagePasskeysTrackingParams( + ManagePasskeysPageActions.openManagePasskeysPage, + ); + + expect(result, { + 'event_name': 'open', + 'params': { + 'form_name': 'ce_passkey_account_settings_form_', + 'operating_system': 'IOS', + }, + }); + }); + + test('should return correct params for closeManagePasskeysPage', () { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + final Map result = getManagePasskeysTrackingParams( + ManagePasskeysPageActions.closeManagePasskeysPage, + ); + + expect(result, { + 'event_name': 'close', + 'params': { + 'form_name': 'ce_passkey_account_settings_form_', + 'operating_system': 'IOS', + }, + }); + }); + }); + + group('tracking create passkey actions ->', () { + test('should return correct params for createPasskey', () { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + final Map result = getCreatePasskeyTrackingParams( + CreatePasskeyFlowActions.createPasskey, + mainFormName: 'main_form_name', + subFormName: 'sub_form_name', + ); + + expect(result, { + 'event_name': 'create_passkey_started', + 'params': { + 'form_name': 'main_form_name', + 'subform_name': 'sub_form_name', + 'operating_system': 'IOS', + }, + }); + }); + + test('should return correct params for createPasskeySuccess', () { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + final Map result = getCreatePasskeyTrackingParams( + CreatePasskeyFlowActions.createPasskeySuccess, + mainFormName: 'main_form_name', + subFormName: 'sub_form_name', + ); + + expect(result, { + 'event_name': 'create_passkey_finished', + 'params': { + 'form_name': 'main_form_name', + 'subform_name': 'sub_form_name', + 'operating_system': 'IOS', + }, + }); + }); + + test('should return correct params for error', () { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + final Map result = getCreatePasskeyTrackingParams( + CreatePasskeyFlowActions.error, + mainFormName: 'main_form_name', + subFormName: 'sub_form_name', + errorMessage: 'error_message', + ); + + expect(result, { + 'event_name': 'error', + 'params': { + 'form_name': 'main_form_name', + 'subform_name': 'sub_form_name', + 'error_message': 'error_message', + 'operating_system': 'IOS', + }, + }); + }); + + test('should return correct params for continueTrading', () { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + final Map result = getCreatePasskeyTrackingParams( + CreatePasskeyFlowActions.continueTrading, + mainFormName: 'main_form_name', + subFormName: 'sub_form_name', + ); + + expect(result, { + 'event_name': 'create_passkey_continue_trading', + 'params': { + 'form_name': 'main_form_name', + 'subform_name': 'sub_form_name', + 'operating_system': 'IOS', + }, + }); + }); + + test('should return correct params for addMorePasskeys', () { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + final Map result = getCreatePasskeyTrackingParams( + CreatePasskeyFlowActions.addMorePasskeys, + mainFormName: 'main_form_name', + subFormName: 'sub_form_name', + ); + + expect(result, { + 'event_name': 'create_passkey_add_more_passkeys', + 'params': { + 'form_name': 'main_form_name', + 'subform_name': 'sub_form_name', + 'operating_system': 'IOS', + }, + }); + }); + }); + + group('tracking rename passkey page actions ->', () { + test('should return correct params for renamePasskey', () { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + final Map result = getRenamePasskeyTrackingParams( + RenamePasskeyFlowActions.renamePasskey, + ); + + expect(result, { + 'event_name': 'passkey_rename_open', + 'params': { + 'form_name': 'ce_passkey_account_settings_form_', + 'operating_system': 'IOS', + }, + }); + }); + + test('should return correct params for cancelRenamePasskey', () { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + final Map result = getRenamePasskeyTrackingParams( + RenamePasskeyFlowActions.cancelRenamePasskey, + ); + + expect(result, { + 'event_name': 'passkey_rename_back', + 'params': { + 'form_name': 'ce_passkey_account_settings_form_', + 'operating_system': 'IOS', + }, + }); + }); + + test('should return correct params for renamePasskeySuccess', () { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + + final Map result = getRenamePasskeyTrackingParams( + RenamePasskeyFlowActions.renamePasskeySuccess, + ); + + expect(result, { + 'event_name': 'passkey_rename_success', + 'params': { + 'form_name': 'ce_passkey_account_settings_form_', + 'operating_system': 'IOS', + }, + }); + }); + }); + }); +} diff --git a/packages/deriv_passkeys/test/data/deriv_passkeys_data_source_mock_setup.dart b/packages/deriv_passkeys/test/data/deriv_passkeys_data_source_mock_setup.dart new file mode 100644 index 000000000..f9fdcec80 --- /dev/null +++ b/packages/deriv_passkeys/test/data/deriv_passkeys_data_source_mock_setup.dart @@ -0,0 +1,125 @@ +import 'package:deriv_passkeys/src/data/data_sources/deriv_passkeys_data_source.dart'; +import 'package:deriv_passkeys/src/data/mappers/deriv_passkeys_mapper.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkey_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkeys_options_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkeys_register_options_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkeys_verify_credentials_request_body_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkeys_verify_credentials_response_model.dart'; +import 'package:deriv_passkeys/src/data/models/passkeys_connection_info_model.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_register_credentials_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_verify_credentials_request_body_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/passkeys_connection_info_entity.dart'; +import 'package:flutter_deriv_api/basic_api/generated/passkeys_register_send.dart'; +import 'package:mocktail/mocktail.dart'; + +/// Mock class for [DerivPasskeysDataSource]. +class MockDerivPasskeysDataSource extends Mock + implements DerivPasskeysDataSource { + @override + final DerivPasskeysMapper mapper = DerivPasskeysMapper(); +} + +final PasskeysConnectionInfoEntity passkeysConnectionInfoEntity = + PasskeysConnectionInfoEntity(endpoint: 'deriv.com', appId: '1234'); + +final PasskeysConnectionInfoModel passkeysConnectionInfoModel = + PasskeysConnectionInfoModel( + endpoint: 'deriv.com', + appId: '1234', +); + +const DerivPasskeysVerifyCredentialsRequestBodyEntity + derivPasskeysVerifyCredentialsRequestBodyEntity = + DerivPasskeysVerifyCredentialsRequestBodyEntity( + appId: '1234', + publicKeyCredential: {}, + type: '', +); + +final DerivPasskeysVerifyCredentialsRequest + derivPasskeysVerifyCredentialsRequest = + DerivPasskeysVerifyCredentialsRequest( + appId: '1234', + publicKeyCredential: {}, + type: '', +); + +const String jwtToken = 'jwtToken'; +const String userAgent = 'Dart/3.0 (dart:io)'; + +const PasskeysRegisterRequest passkeysRegisterRequest = PasskeysRegisterRequest( + name: '', + publicKeyCredential: {}, +); + +const DerivPasskeysRegisterCredentialsEntity registerCredentialsEntity = + DerivPasskeysRegisterCredentialsEntity( + publicKeyCredential: {}, + name: '', +); +const DerivPasskeyModel derivPasskeyModel = DerivPasskeyModel( + createdAt: 1234, + id: '', + lastUsed: null, + name: '', + passkeyId: '', + storedOn: '', +); + +DerivPasskeysRegisterOptionsModel derivPasskeysRegisterOptionsModel = + DerivPasskeysRegisterOptionsModel( + options: { + 'rp': { + 'id': 'id', + 'name': 'name', + }, + 'user': { + 'id': 'id', + 'name': 'name', + 'displayName': 'displayName', + }, + 'challenge': 'challenge', + 'pubKeyCredParams': [ + { + 'type': 'type', + 'alg': -1, + } + ], + 'timeout': 18000, + 'attestation': 'attestation', + 'extensions': {}, + }, +); + +const DerivPasskeysVerifyCredentialsResponseModel + derivPasskeysVerifyCredentialsResponseModel = + DerivPasskeysVerifyCredentialsResponseModel( + response: { + 'tokens': >[ + { + 'loginid': 'login', + 'token': 'token', + }, + ], + 'refresh_token': 'refresh_token', + }, +); + +final DerivPasskeysOptionsModel derivPasskeysOptionsModel = + DerivPasskeysOptionsModel( + challenge: '', + rpId: '', + timeout: 1234, + userVerification: '', + allowCredentials: [], +); + +/// Sets up the mock data for [DerivPasskeysDataSource]. +void derivPasskeysDataSourceMockSetup() { + registerFallbackValue(derivPasskeysVerifyCredentialsRequest); + registerFallbackValue(derivPasskeysVerifyCredentialsRequestBodyEntity); + registerFallbackValue(passkeysConnectionInfoModel); + registerFallbackValue(jwtToken); + registerFallbackValue(userAgent); + registerFallbackValue(passkeysRegisterRequest); +} diff --git a/packages/deriv_passkeys/test/data/deriv_passkeys_data_source_test.dart b/packages/deriv_passkeys/test/data/deriv_passkeys_data_source_test.dart new file mode 100644 index 000000000..2aaf2657b --- /dev/null +++ b/packages/deriv_passkeys/test/data/deriv_passkeys_data_source_test.dart @@ -0,0 +1,211 @@ +import 'dart:convert'; + +import 'package:deriv_http_client/deriv_http_client.dart'; +import 'package:deriv_passkeys/deriv_passkeys.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkey_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkeys_options_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkeys_register_options_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkeys_verify_credentials_request_body_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkeys_verify_credentials_response_model.dart'; +import 'package:deriv_passkeys/src/data/models/passkeys_connection_info_model.dart'; +import 'package:deriv_passkeys/src/exceptions/server_exceptions.dart'; +import 'package:flutter_deriv_api/api/api_initializer.dart'; +import 'package:flutter_deriv_api/basic_api/generated/passkeys_register_send.dart'; +import 'package:flutter_deriv_api/services/connection/api_manager/mock_api.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:http/http.dart'; + +Future _mockHTTP(Request request) async { + if (request.url.host.contains('error')) { + if (request.url.host == 'error_code_exists') { + return Response( + jsonEncode({ + 'error_code': 'error_code_exists', + 'message': 'message', + }), + 500, + ); + } + if (request.url.host == 'no_error_code') { + return Response( + jsonEncode({ + 'message': 'message', + }), + 500, + ); + } + } + switch (request.url.path) { + case '/oauth2/api/v1/passkeys/login/options': + if (request.method == 'GET') { + return Response( + jsonEncode({ + 'publicKey': { + 'challenge': 'jV6lvuj1d-iVSPiQQg9iwXhiSTCm3CueJ7aLcv2GfVc', + 'rpId': 'deriv.com', + 'timeout': 60000, + 'userVerification': 'preferred' + } + }), + 200, + ); + } + break; + case '/oauth2/api/v1/passkeys/login/verify': + if (request.method == 'POST') { + return Response( + jsonEncode({}), + 200, + ); + } + break; + } + return Response( + jsonEncode({ + 'error_code': 'error_code', + 'message': 'message', + }), + 400, + ); +} + +class FakeHttpClient extends Fake implements HttpClient { + @override + Future get(String url, {String? basicAuthToken}) async => + _mockHTTP(Request('GET', Uri.parse(url))); + + @override + Future> post({ + required String url, + required Map jsonBody, + Map? headers, + }) async { + final Response response = await _mockHTTP(Request('POST', Uri.parse(url))); + return jsonDecode(response.body) as Map; + } +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late DerivPasskeysDataSource dataSource; + final HttpClient mockClient = FakeHttpClient(); + final PasskeysConnectionInfoModel passkeysConnectionInfoModel = + PasskeysConnectionInfoModel( + endpoint: 'deriv.com', + appId: 'appId', + ); + + setUpAll(() { + APIInitializer().initialize(api: MockAPI()); + dataSource = DerivPasskeysDataSource( + mapper: DerivPasskeysMapper(), + client: mockClient, + ); + }); + + group('getOptions', () { + test('should return DerivPasskeysOptionsModel when request is successful', + () async { + expect( + dataSource.getOptions( + passkeysConnectionInfoModel: passkeysConnectionInfoModel, + ), + completion(isA())); + }); + + test( + 'should throw ServerException when request is unsuccessful and response body contains "error_code".', + () async { + expect( + () => dataSource.getOptions( + passkeysConnectionInfoModel: passkeysConnectionInfoModel.copyWith( + endpoint: 'error_code_exists', + ), + ), + throwsA(isA()), + ); + }); + + test( + 'should throw Exception when request is unsuccessful and response body does not contain "error_code".', + () async { + expect( + () => dataSource.getOptions( + passkeysConnectionInfoModel: passkeysConnectionInfoModel.copyWith( + endpoint: 'no_error_code', + ), + ), + throwsA(isA()), + ); + }); + }); + + group('verifyCredentials', () { + final DerivPasskeysVerifyCredentialsRequest + derivPasskeysVerifyCredentialsRequest = + DerivPasskeysVerifyCredentialsRequest( + appId: 'appId', + publicKeyCredential: {}, + type: 'type', + ); + test( + 'should return DerivPasskeysVerifyCredentialsResponseModel when request is successful', + () async { + expect( + dataSource.verifyCredentials( + requestBodyModel: derivPasskeysVerifyCredentialsRequest, + jwtToken: 'jwtToken', + passkeysConnectionInfoModel: passkeysConnectionInfoModel, + ), + completion(isA())); + }); + + test( + 'should throw ServerException when request is unsuccessful and response body contains "error_code".', + () async { + expect( + () => dataSource.verifyCredentials( + requestBodyModel: derivPasskeysVerifyCredentialsRequest, + jwtToken: 'jwtToken', + passkeysConnectionInfoModel: passkeysConnectionInfoModel.copyWith( + endpoint: 'error_code_exists', + ), + ), + throwsA(isA()), + ); + }); + }); + + group('getRegisterOptions', () { + test( + 'should return DerivPasskeysRegisterOptionsModel when request is successful', + () async { + expect(dataSource.getRegisterOptions(), + completion(isA())); + }); + }); + + group('registerCredentials', () { + test('should return DerivPasskeyModel when request is successful', + () async { + expect( + dataSource.registerCredentials( + const PasskeysRegisterRequest( + publicKeyCredential: {}, + name: '', + ), + ), + completion(isA())); + }); + }); + + group('getPasskeysList', () { + test('should return List when request is successful', + () async { + expect(dataSource.getPasskeysList(), + completion(isA>())); + }); + }); +} diff --git a/packages/deriv_passkeys/test/data/deriv_passkeys_mapper_test.dart b/packages/deriv_passkeys/test/data/deriv_passkeys_mapper_test.dart new file mode 100644 index 000000000..f2ebfd2f0 --- /dev/null +++ b/packages/deriv_passkeys/test/data/deriv_passkeys_mapper_test.dart @@ -0,0 +1,134 @@ +import 'package:deriv_passkeys/src/data/models/passkeys_connection_info_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkey_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkeys_options_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkeys_register_options_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkeys_verify_credentials_request_body_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkeys_verify_credentials_response_model.dart'; +import 'package:deriv_passkeys/src/domain/entities/passkeys_connection_info_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkey_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_options_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_register_credentials_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_register_options_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_verify_credentials_request_body_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_verify_credentials_response_entity.dart'; +import 'package:flutter_deriv_api/basic_api/generated/passkeys_register_send.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:deriv_passkeys/src/data/mappers/deriv_passkeys_mapper.dart'; + +void main() { + group('DerivPasskeysMapper', () { + late DerivPasskeysMapper mapper; + + setUp(() { + mapper = DerivPasskeysMapper(); + }); + + test( + 'mapDerivPasskeysOptionsModel should return DerivPasskeysOptionsEntity', + () { + final DerivPasskeysOptionsModel model = DerivPasskeysOptionsModel( + challenge: '', + rpId: '', + timeout: 12345, + userVerification: '', + allowCredentials: []); // Provide necessary data for the model + + final DerivPasskeysOptionsEntity result = + mapper.mapDerivPasskeysOptionsModel(model); + + expect(result, isA()); + }); + + test( + 'mapDerivPasskeysVerifyCredentialsRequestBodyEntity should return DerivPasskeysVerifyCredentialsRequest', + () { + const DerivPasskeysVerifyCredentialsRequestBodyEntity entity = + DerivPasskeysVerifyCredentialsRequestBodyEntity( + appId: '', publicKeyCredential: {}, type: ''); + + final DerivPasskeysVerifyCredentialsRequest result = + mapper.mapDerivPasskeysVerifyCredentialsRequestBodyEntity(entity); + + expect(result, isA()); + }); + + test( + 'mapDerivPasskeysRegisterOptionsModel should return DerivPasskeysRegisterOptionsEntity', + () { + final DerivPasskeysRegisterOptionsModel model = + DerivPasskeysRegisterOptionsModel(options: {}); // Provide necessary data for the model + + final DerivPasskeysRegisterOptionsEntity result = + mapper.mapDerivPasskeysRegisterOptionsModel(model); + + expect(result, isA()); + }); + + test('mapDerivPasskeyModel should return DerivPasskeyEntity', () { + const DerivPasskeyModel model = DerivPasskeyModel( + createdAt: 1234, + id: '', + lastUsed: null, + name: '', + passkeyId: '', + storedOn: '', + ); + + final DerivPasskeyEntity result = mapper.mapDerivPasskeyModel(model); + + expect(result, isA()); + }); + + test( + 'mapDerivPasskeysRegisterCredentialsEntity should return PasskeysRegisterRequest', + () { + const DerivPasskeysRegisterCredentialsEntity entity = + DerivPasskeysRegisterCredentialsEntity( + publicKeyCredential: {}, + name: '', + ); + + final PasskeysRegisterRequest result = + mapper.mapDerivPasskeysRegisterCredentialsEntity(entity); + + expect(result, isA()); + }); + + test( + 'mapDerivPasskeysVerifyCredentialsResponseModel should return DerivPasskeysVerifyCredentialsResponseEntity', + () { + const DerivPasskeysVerifyCredentialsResponseModel model = + DerivPasskeysVerifyCredentialsResponseModel( + response: { + 'tokens': [ + { + 'loginid': '', + 'token': '', + } + ], + 'refresh_token': 'refresh_token', + }, + ); + + final DerivPasskeysVerifyCredentialsResponseEntity result = + mapper.mapDerivPasskeysVerifyCredentialsResponseModel(model); + + expect(result, isA()); + }); + + test('mapConnectionInfoEntity should return PasskeysConnectionInfoModel', + () { + final PasskeysConnectionInfoEntity entity = PasskeysConnectionInfoEntity( + endpoint: '', + appId: '', + ); + + final PasskeysConnectionInfoModel result = + mapper.mapConnectionInfoEntity(entity); + + expect(result, isA()); + }); + }); +} diff --git a/packages/deriv_passkeys/test/data/deriv_passkeys_method_channel_test.dart b/packages/deriv_passkeys/test/data/deriv_passkeys_method_channel_test.dart new file mode 100644 index 000000000..df6e20fb1 --- /dev/null +++ b/packages/deriv_passkeys/test/data/deriv_passkeys_method_channel_test.dart @@ -0,0 +1,147 @@ +import 'package:deriv_passkeys/src/data/platform/deriv_passkeys_method_channel.dart'; +import 'package:deriv_passkeys/src/exceptions/platform_exceptions.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class MockMethodChannel extends MethodChannel { + MockMethodChannel() : super('deriv_passkeys'); + + Future handler(MethodCall methodCall) async => + invokeMethod(methodCall.method, methodCall.arguments); + + @override + Future invokeMethod(String method, [dynamic arguments]) async { + switch (method) { + case 'isPlatformSupported': + return true as T?; + case 'createCredential': + final String options = arguments['options']; + if (options == 'valid_options') { + return 'credential created' as T?; + } else if (options == 'user_cancelled') { + throw PlatformException( + code: 'CreateCredentialCancellationException', + message: 'CreateCredentialCancellationException', + ); + } else { + throw PlatformException( + code: 'invalid_argument', + message: 'Invalid options provided', + ); + } + case 'getCredential': + final String options = arguments['options']; + if (options == 'valid_options') { + return 'credential retrieved' as T?; + } else if (options == 'user_cancelled') { + throw PlatformException( + code: 'GetCredentialCancellationException', + message: 'GetCredentialCancellationException', + ); + } else if (options == 'no_credential') { + throw PlatformException( + code: 'NoCredentialException', + message: 'GetCredentialCancellationException', + ); + } else { + throw PlatformException( + code: 'invalid_argument', + message: 'Invalid options provided', + ); + } + default: + throw PlatformException( + code: 'method_not_implemented', + message: 'Method not implemented on platform', + ); + } + } +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late MethodChannelDerivPasskeys platform; + late MockMethodChannel mockChannel; + + setUp(() { + mockChannel = MockMethodChannel(); + platform = MethodChannelDerivPasskeys(); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + mockChannel, + mockChannel.handler, + ); + }); + + tearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(mockChannel, null); + }); + + test('isSupported', () async { + expect(await platform.isPlatformSupported(), isTrue); + }); + + test('createCredential with valid options', () async { + const String options = 'valid_options'; + final String? response = await platform.createCredential(options); + expect(response, 'credential created'); + }); + + test('createCredential with user cancelled throws CanceledPlatformException', + () { + const String options = 'user_cancelled'; + expect( + () => platform.createCredential(options), + throwsA(isA()), + ); + }); + + test('createCredential with invalid options throws PlatformException', () { + const String options = 'invalid options'; + expect( + () => platform.createCredential(options), + throwsA(isA()), + ); + }); + + test('getCredential with valid options', () async { + const String options = 'valid_options'; + final String? response = await platform.getCredential(options); + expect(response, 'credential retrieved'); + }); + + test('getCredential with user cancelled throws CanceledPlatformException', + () { + const String options = 'user_cancelled'; + expect( + () => platform.getCredential(options), + throwsA(isA()), + ); + }); + + test('getCredential with no credential throws NoCredentialPlatformException', + () { + const String options = 'no_credential'; + expect( + () => platform.getCredential(options), + throwsA(isA()), + ); + }); + + test('getCredential with invalid options throws PlatformException', () { + const String options = 'invalid options'; + expect( + () => platform.getCredential(options), + throwsA(isA()), + ); + }); + + test('Method not implemented throws PlatformException', () async { + expect( + () => platform.methodChannel.invokeMethod('nonExistentMethod'), + throwsA(isA()), + ); + }); +} diff --git a/packages/deriv_passkeys/test/data/deriv_passkeys_repository_test.dart b/packages/deriv_passkeys/test/data/deriv_passkeys_repository_test.dart new file mode 100644 index 000000000..7b4bd0c4e --- /dev/null +++ b/packages/deriv_passkeys/test/data/deriv_passkeys_repository_test.dart @@ -0,0 +1,187 @@ +import 'package:deriv_passkeys/deriv_passkeys.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkey_model.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkeys_register_options_model.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_options_entity.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkeys_register_options_entity.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +import 'deriv_passkeys_data_source_mock_setup.dart'; + +void main() { + late DerivPasskeysRepository repository; + late MockDerivPasskeysDataSource mockDerivPasskeysDataSource; + + setUpAll(() { + derivPasskeysDataSourceMockSetup(); + mockDerivPasskeysDataSource = MockDerivPasskeysDataSource(); + repository = DerivPasskeysRepository(mockDerivPasskeysDataSource); + }); + + group('getOptions', () { + test('should return DerivPasskeysOptionsEntity', () async { + when(() => mockDerivPasskeysDataSource.getOptions( + passkeysConnectionInfoModel: + any(named: 'passkeysConnectionInfoModel'), + )).thenAnswer( + (_) async => derivPasskeysOptionsModel, + ); + + final DerivPasskeysOptionsEntity result = await repository.getOptions( + passkeysConnectionInfoEntity: passkeysConnectionInfoEntity, + ); + + expect( + result, + equals( + mockDerivPasskeysDataSource.mapper.mapDerivPasskeysOptionsModel( + derivPasskeysOptionsModel, + ), + ), + ); + verify(() => mockDerivPasskeysDataSource.getOptions( + passkeysConnectionInfoModel: + any(named: 'passkeysConnectionInfoModel'), + )).called(1); + }); + }); + + group('verifyCredentials', () { + test('should return DerivPasskeysVerifyCredentialsResponseEntity', + () async { + when(() => mockDerivPasskeysDataSource.verifyCredentials( + requestBodyModel: any(named: 'requestBodyModel'), + jwtToken: any(named: 'jwtToken'), + passkeysConnectionInfoModel: + any(named: 'passkeysConnectionInfoModel'), + userAgent: any(named: 'userAgent'), + )) + .thenAnswer((_) async => derivPasskeysVerifyCredentialsResponseModel); + + final DerivPasskeysVerifyCredentialsResponseEntity result = + await repository.verifyCredentials( + requestBodyEntity: derivPasskeysVerifyCredentialsRequestBodyEntity, + jwtToken: jwtToken, + passkeysConnectionInfoEntity: passkeysConnectionInfoEntity, + ); + + expect( + result, + equals( + mockDerivPasskeysDataSource.mapper + .mapDerivPasskeysVerifyCredentialsResponseModel( + derivPasskeysVerifyCredentialsResponseModel, + ), + ), + ); + verify(() => mockDerivPasskeysDataSource.verifyCredentials( + requestBodyModel: any(named: 'requestBodyModel'), + jwtToken: any(named: 'jwtToken'), + passkeysConnectionInfoModel: + any(named: 'passkeysConnectionInfoModel'), + userAgent: any(named: 'userAgent'), + )).called(1); + }); + }); + + group('getRegisterOptions', () { + test('should return DerivPasskeysRegisterOptionsEntity', () async { + final DerivPasskeysRegisterOptionsModel + derivPasskeysRegisterOptionsModel = DerivPasskeysRegisterOptionsModel( + options: {}, + ); + when(() => mockDerivPasskeysDataSource.getRegisterOptions()) + .thenAnswer((_) async => derivPasskeysRegisterOptionsModel); + + final DerivPasskeysRegisterOptionsEntity result = + await repository.getRegisterOptions(); + + expect( + result, + equals( + mockDerivPasskeysDataSource.mapper + .mapDerivPasskeysRegisterOptionsModel( + derivPasskeysRegisterOptionsModel, + ), + ), + ); + verify(() => mockDerivPasskeysDataSource.getRegisterOptions()).called(1); + }); + }); + + group('registerCredentials', () { + test('should return DerivPasskeyEntity', () async { + when(() => mockDerivPasskeysDataSource.registerCredentials( + any(), + )).thenAnswer((_) async => derivPasskeyModel); + + final DerivPasskeyEntity result = + await repository.registerCredentials(registerCredentialsEntity); + + expect( + result, + equals( + mockDerivPasskeysDataSource.mapper.mapDerivPasskeyModel( + derivPasskeyModel, + ), + ), + ); + verify(() => mockDerivPasskeysDataSource.registerCredentials( + any(), + )).called(1); + }); + }); + + group('getPasskeysList', () { + test('should return List', () async { + const DerivPasskeyModel passkeyModel1 = DerivPasskeyModel( + createdAt: 1234, + id: '1', + lastUsed: null, + name: '', + passkeyId: '', + storedOn: '', + ); + const DerivPasskeyModel passkeyModel2 = DerivPasskeyModel( + createdAt: 1234, + id: '2', + lastUsed: null, + name: '', + passkeyId: '', + storedOn: '', + ); + const DerivPasskeyEntity passkeyEntity1 = DerivPasskeyEntity( + createdAt: 1234, + id: '1', + lastUsed: null, + name: '', + passkeyId: '', + storedOn: '', + ); + const DerivPasskeyEntity passkeyEntity2 = DerivPasskeyEntity( + createdAt: 1234, + id: '2', + lastUsed: null, + name: '', + passkeyId: '', + storedOn: '', + ); + final List passkeyModels = [ + passkeyModel1, + passkeyModel2 + ]; + final List passkeyEntities = [ + passkeyEntity1, + passkeyEntity2 + ]; + when(() => mockDerivPasskeysDataSource.getPasskeysList()) + .thenAnswer((_) async => passkeyModels); + + final List result = + await repository.getPasskeysList(); + + expect(result, equals(passkeyEntities)); + verify(() => mockDerivPasskeysDataSource.getPasskeysList()).called(1); + }); + }); +} diff --git a/packages/deriv_passkeys/test/data/passkey_analytics_repository_test.dart b/packages/deriv_passkeys/test/data/passkey_analytics_repository_test.dart new file mode 100644 index 000000000..3ea8f51fc --- /dev/null +++ b/packages/deriv_passkeys/test/data/passkey_analytics_repository_test.dart @@ -0,0 +1,168 @@ +import 'package:analytics/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart'; +import 'package:deriv_passkeys/src/data/repositories/passkey_analytics_repository.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +final class MockDerivRudderstack extends Mock implements DerivRudderstack {} + +void main() { + group('AnalyticsRepository test.', () { + /// [AnalyticsRepository] instance. + late final AnalyticsRepository analyticsRepository; + + /// [MockDerivRudderstack] instance. + late final MockDerivRudderstack mockDerivRudderstack; + + setUpAll(() { + mockDerivRudderstack = MockDerivRudderstack(); + + when(() => mockDerivRudderstack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'), + )).thenAnswer( + (_) => Future.value(true), + ); + + AnalyticsRepository.init( + 'test', + derivRudderstack: mockDerivRudderstack, + ); + + analyticsRepository = AnalyticsRepository.instance; + }); + + test('should track opening effortlessLoginPage.', () { + analyticsRepository.trackOpenEffortlessLoginPage(); + + verify(() => mockDerivRudderstack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'), + )).called(1); + }); + + test('should track closing effortlessLoginPage.', () { + analyticsRepository.trackCloseEffortlessLoginPage(); + + verify(() => mockDerivRudderstack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'), + )).called(1); + }); + + test('should track maybe later is pressed.', () { + analyticsRepository.trackMaybeLater(); + + verify(() => mockDerivRudderstack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'), + )).called(1); + }); + + test('should track opening learn more page.', () { + analyticsRepository.trackOpenLearnMorePage(); + + verify(() => mockDerivRudderstack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'), + )).called(1); + }); + + test('should track closing learn more page.', () { + analyticsRepository.trackCloseLearnMorePage(); + + verify(() => mockDerivRudderstack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'), + )).called(1); + }); + + test('should track opening manage passkeys page.', () { + analyticsRepository.trackOpenManagePasskeysPage(); + + verify(() => mockDerivRudderstack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'), + )).called(1); + }); + + test('should track closing manage passkeys page.', () { + analyticsRepository.trackCloseManagePasskeysPage(); + + verify(() => mockDerivRudderstack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'), + )).called(1); + }); + + test('should track create passkey.', () { + analyticsRepository.trackCreatePasskey(); + + verify(() => mockDerivRudderstack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'), + )).called(1); + }); + test('should track passkey error.', () { + analyticsRepository.trackPasskeyError('error message'); + + verify(() => mockDerivRudderstack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'), + )).called(1); + }); + + test('should track continue trading.', () { + analyticsRepository.trackContinueTrading(); + + verify(() => mockDerivRudderstack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'), + )).called(1); + }); + + test('should track add more passkeys.', () { + analyticsRepository.trackAddMorePasskeys(); + + verify(() => mockDerivRudderstack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'), + )).called(1); + }); + + test('should track create passkey success.', () { + analyticsRepository.trackCreatePasskeySuccess(); + + verify(() => mockDerivRudderstack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'), + )).called(1); + }); + + test('should track rename passkey.', () { + analyticsRepository.trackRenamePasskey(); + + verify(() => mockDerivRudderstack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'), + )).called(1); + }); + + test('should track cancel rename passkey.', () { + analyticsRepository.trackCancelRenamePasskey(); + + verify(() => mockDerivRudderstack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'), + )).called(1); + }); + + test('should track rename passkey success.', () { + analyticsRepository.trackRenamePasskeySuccess(); + + verify(() => mockDerivRudderstack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'), + )).called(1); + }); + }); +} diff --git a/packages/deriv_passkeys/test/interactor/deriv_passkeys_service_test.dart b/packages/deriv_passkeys/test/interactor/deriv_passkeys_service_test.dart new file mode 100644 index 000000000..c98d1e886 --- /dev/null +++ b/packages/deriv_passkeys/test/interactor/deriv_passkeys_service_test.dart @@ -0,0 +1,147 @@ +import 'package:deriv_passkeys/deriv_passkeys.dart'; +import 'package:deriv_passkeys/src/data/models/deriv_passkey_model.dart'; +import 'package:deriv_passkeys/src/data/platform/deriv_passkeys_method_channel.dart'; +import 'package:deriv_passkeys/src/domain/platform/base_deriv_passkeys_method_channel.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import '../data/deriv_passkeys_data_source_mock_setup.dart'; + +class MockBaseDerivPasskeysMethodChannel + with MockPlatformInterfaceMixin + implements BaseDerivPasskeysMethodChannel { + Future Function(String options)? mockCreateCredential; + Future Function(String options)? mockGetCredential; + + @override + Future isPlatformSupported() => Future.value(true); + + @override + Future createCredential(String options) => + mockCreateCredential != null + ? mockCreateCredential!(options) + : Future.value('{}'); + + @override + Future getCredential(String options) => mockGetCredential != null + ? mockGetCredential!(options) + : Future.value('{}'); +} + +void main() { + final BaseDerivPasskeysMethodChannel initialPlatform = + BaseDerivPasskeysMethodChannel.instance; + + test('$MethodChannelDerivPasskeys is the default instance', () { + expect(initialPlatform, isInstanceOf()); + }); + + group('DerivPasskeys', () { + late DerivPasskeysService derivPasskeysService; + final MockDerivPasskeysDataSource mockDerivPasskeysDataSource = + MockDerivPasskeysDataSource(); + + setUp(() { + derivPasskeysDataSourceMockSetup(); + derivPasskeysService = DerivPasskeysService( + DerivPasskeysRepository( + mockDerivPasskeysDataSource, + ), + ); + BaseDerivPasskeysMethodChannel.instance = + MockBaseDerivPasskeysMethodChannel(); + }); + + tearDown(() { + BaseDerivPasskeysMethodChannel.instance = initialPlatform; + }); + + test('isSupported returns true', () async { + final bool isSupported = await derivPasskeysService.isSupported(); + expect(isSupported, true); + }); + + test('createCredential returns response if not null', () async { + when(() => mockDerivPasskeysDataSource.getRegisterOptions()) + .thenAnswer((_) async => derivPasskeysRegisterOptionsModel); + when(() => mockDerivPasskeysDataSource.registerCredentials( + any(), + )).thenAnswer((_) async => derivPasskeyModel); + final DerivPasskeyEntity response = + await derivPasskeysService.createCredential(); + expect(response, isNotNull); + expect(response, isA()); + }); + + test('createCredential throws PlatformException if response is null', + () async { + when(() => mockDerivPasskeysDataSource.getRegisterOptions()) + .thenAnswer((_) async => derivPasskeysRegisterOptionsModel); + + BaseDerivPasskeysMethodChannel + .instance = MockBaseDerivPasskeysMethodChannel() + ..mockCreateCredential = (String options) => Future.value(); + expect( + () => derivPasskeysService.createCredential(), + throwsA(isA()), + ); + }); + + test('verifyCredential returns response if not null', () async { + when(() => mockDerivPasskeysDataSource.getOptions( + passkeysConnectionInfoModel: + any(named: 'passkeysConnectionInfoModel'), + )).thenAnswer( + (_) async => derivPasskeysOptionsModel, + ); + when(() => mockDerivPasskeysDataSource.verifyCredentials( + requestBodyModel: any(named: 'requestBodyModel'), + jwtToken: any(named: 'jwtToken'), + passkeysConnectionInfoModel: + any(named: 'passkeysConnectionInfoModel'), + userAgent: any(named: 'userAgent'), + )) + .thenAnswer((_) async => derivPasskeysVerifyCredentialsResponseModel); + final DerivPasskeysVerifyCredentialsResponseEntity response = + await derivPasskeysService.verifyCredential( + jwtToken: jwtToken, + passkeysConnectionInfoEntity: passkeysConnectionInfoEntity, + ); + expect(response, isNotNull); + expect(response, isA()); + }); + + test('verifyCredential throws PlatformException if response is null', + () async { + when(() => mockDerivPasskeysDataSource.getOptions( + passkeysConnectionInfoModel: + any(named: 'passkeysConnectionInfoModel'), + )).thenAnswer( + (_) async => derivPasskeysOptionsModel, + ); + BaseDerivPasskeysMethodChannel.instance = + MockBaseDerivPasskeysMethodChannel() + ..mockGetCredential = (String options) => Future.value(); + + expect( + () => derivPasskeysService.verifyCredential( + jwtToken: jwtToken, + passkeysConnectionInfoEntity: passkeysConnectionInfoEntity, + ), + throwsA(isInstanceOf())); + }); + test('getPasskeysList returns response if not null', () async { + when(() => mockDerivPasskeysDataSource.getPasskeysList()) + .thenAnswer((_) async => [derivPasskeyModel]); + final List response = await DerivPasskeysService( + DerivPasskeysRepository( + mockDerivPasskeysDataSource, + ), + ).getPasskeysList(); + expect(response, isNotNull); + expect(response, isA>()); + }); + }); +} diff --git a/packages/deriv_passkeys/test/presentation/mixins/passkey_event_tracking_mixin_test.dart b/packages/deriv_passkeys/test/presentation/mixins/passkey_event_tracking_mixin_test.dart new file mode 100644 index 000000000..c04e8aa07 --- /dev/null +++ b/packages/deriv_passkeys/test/presentation/mixins/passkey_event_tracking_mixin_test.dart @@ -0,0 +1,146 @@ +import 'package:analytics/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart'; +import 'package:deriv_passkeys/src/data/repositories/passkey_analytics_repository.dart'; +import 'package:deriv_passkeys/src/presentation/mixins/passkey_event_tracking_mixin.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +final class MockDerivRudderstack extends Mock implements DerivRudderstack {} + +// class AnalyticsMixinObject = Object with AnalyticsMixin; + +class AnalyticsMixinObject with PasskeyEventTrackingMixin {} + +void main() { + group('Analytics mixin tests:\n', () { + late final MockDerivRudderstack mockRudderStack; + late final AnalyticsMixinObject analyticsMixin; + + setUpAll(() { + analyticsMixin = AnalyticsMixinObject(); + mockRudderStack = MockDerivRudderstack(); + + AnalyticsRepository.init( + 'test', + derivRudderstack: mockRudderStack, + ); + when( + () => mockRudderStack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'), + ), + ).thenAnswer( + (_) => Future.value(true), + ); + }); + + test('Analytics mixin has a AnalyticsRepository parameter.', () { + expect( + analyticsMixin.analyticsRepository, + AnalyticsRepository.instance, + ); + }); + + test('should track opening effortlessLoginPage.', () { + analyticsMixin.trackOpenEffortlessLoginPage(); + + verify(() => AnalyticsRepository.instance.trackOpenEffortlessLoginPage()) + .called(1); + }); + + test('should track closing effortlessLoginPage.', () { + analyticsMixin.trackCloseEffortlessLoginPage(); + + verify(() => AnalyticsRepository.instance.trackCloseEffortlessLoginPage()) + .called(1); + }); + + test('should track maybe later is pressed.', () { + analyticsMixin.trackMaybeLater(); + + verify(() => AnalyticsRepository.instance.trackMaybeLater()).called(1); + }); + + test('should track opening learn more page.', () { + analyticsMixin.trackOpenLearnMorePage(); + + verify(() => AnalyticsRepository.instance.trackOpenLearnMorePage()) + .called(1); + }); + + test('should track closing learn more page.', () { + analyticsMixin.trackCloseLearnMorePage(); + + verify(() => AnalyticsRepository.instance.trackCloseLearnMorePage()) + .called(1); + }); + + test('should track opening manage passkeys page.', () { + analyticsMixin.trackOpenManagePasskeysPage(); + + verify(() => AnalyticsRepository.instance.trackOpenManagePasskeysPage()) + .called(1); + }); + + test('should track closing manage passkeys page.', () { + analyticsMixin.trackCloseManagePasskeysPage(); + + verify(() => AnalyticsRepository.instance.trackCloseManagePasskeysPage()) + .called(1); + }); + + test('should track create passkey.', () { + analyticsMixin.trackCreatePasskey(); + + verify(() => AnalyticsRepository.instance.trackCreatePasskey()).called(1); + }); + + test('should track create passkey success.', () { + analyticsMixin.trackCreatePasskeySuccess(); + + verify(() => AnalyticsRepository.instance.trackCreatePasskeySuccess()) + .called(1); + }); + + test('should track passkey error.', () { + analyticsMixin.trackPasskeyError('error message'); + + verify(() => + AnalyticsRepository.instance.trackPasskeyError('error message')) + .called(1); + }); + + test('should track continue trading.', () { + analyticsMixin.trackContinueTrading(); + + verify(() => AnalyticsRepository.instance.trackContinueTrading()) + .called(1); + }); + + test('should track add more passkeys.', () { + analyticsMixin.trackAddMorePasskeys(); + + verify(() => AnalyticsRepository.instance.trackAddMorePasskeys()) + .called(1); + }); + + test('should track rename passkey.', () { + analyticsMixin.trackRenamePasskey(); + + verify(() => AnalyticsRepository.instance.trackRenamePasskey()).called(1); + }); + + test('should track cancel rename passkey.', () { + analyticsMixin.trackCancelRenamePasskey(); + + verify(() => AnalyticsRepository.instance.trackCancelRenamePasskey()) + .called(1); + }); + + test('should track rename passkey success.', () { + analyticsMixin.trackRenamePasskeySuccess(); + + verify(() => AnalyticsRepository.instance.trackRenamePasskeySuccess()) + .called(1); + }); + }); +} diff --git a/packages/deriv_passkeys/test/presentation/pages/learn_more_passkeys_page_test.dart b/packages/deriv_passkeys/test/presentation/pages/learn_more_passkeys_page_test.dart new file mode 100644 index 000000000..f6abb7bbe --- /dev/null +++ b/packages/deriv_passkeys/test/presentation/pages/learn_more_passkeys_page_test.dart @@ -0,0 +1,115 @@ +import 'package:analytics/sdk/rudderstack/sdk/deriv_rudderstack_sdk.dart'; +import 'package:deriv_localizations/l10n/generated/deriv_passkeys/deriv_passkeys_localizations.dart'; +import 'package:deriv_localizations/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_en.dart'; +import 'package:deriv_passkeys/deriv_passkeys.dart'; +import 'package:deriv_passkeys/src/data/repositories/passkey_analytics_repository.dart'; +import 'package:deriv_passkeys/src/presentation/widgets/section_title_and_content.dart'; +import 'package:deriv_passkeys/src/presentation/widgets/unordered_list_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:deriv_passkeys/src/presentation/pages/learn_more_passkeys_page.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../states/bloc/deriv_passkeys_bloc_setup.dart'; + +final class MockDerivRudderstack extends Mock implements DerivRudderstack {} + +class _TestPage extends StatelessWidget { + const _TestPage(); + + @override + Widget build(BuildContext context) => MaterialApp( + localizationsDelegates: const >[ + DerivPasskeysLocalizations.delegate, + ], + locale: const Locale('en'), + home: LearnMorePasskeysPage( + onPageClosed: (BuildContext context) { + Navigator.pop(context); + }, + addMorePasskeysNavigationCallback: (BuildContext context) {}, + continueTradingNavigationCallback: (BuildContext context) {}, + ), + ); +} + +void main() { + late MockDerivRudderstack derivRudderstack; + group('LearnMorePasskeysPage', () { + setUp(() { + setupDerivPasskeysBloc(); + + derivRudderstack = MockDerivRudderstack(); + + when(() => derivRudderstack.track( + eventName: any(named: 'eventName'), + properties: any(named: 'properties'), + )).thenAnswer( + (_) => Future.value(true), + ); + + AnalyticsRepository.init( + 'test', + derivRudderstack: derivRudderstack, + ); + }); + + testWidgets('renders page correctly', (WidgetTester tester) async { + await tester.pumpWidget( + BlocProvider( + create: (BuildContext context) => derivPasskeysBloc, + child: const _TestPage(), + ), + ); + + expect(find.byType(AppBar), findsOneWidget); + expect(find.byType(SingleChildScrollView), findsOneWidget); + expect(find.byType(SvgPicture), findsNWidgets(2)); + expect(find.byType(SectionTitleAndContent), findsNWidgets(5)); + expect(find.byType(Divider), findsNWidgets(4)); + expect(find.byType(UnorderedList), findsNWidgets(6)); + }); + + testWidgets('displays correct text', (WidgetTester tester) async { + await tester.pumpWidget( + BlocProvider( + create: (BuildContext context) => derivPasskeysBloc, + child: const _TestPage(), + ), + ); + + // Verify that the page displays the correct text + expect(find.text(DerivPasskeysLocalizationsEn().effortlessLogin), + findsOneWidget); + expect(find.text(DerivPasskeysLocalizationsEn().whatArePasskeys), + findsOneWidget); + expect(find.text(DerivPasskeysLocalizationsEn().whyPasskeys), + findsOneWidget); + expect(find.text(DerivPasskeysLocalizationsEn().howToCreatePasskey), + findsOneWidget); + expect(find.text(DerivPasskeysLocalizationsEn().whereArePasskeysSaved), + findsOneWidget); + expect( + find.text(DerivPasskeysLocalizationsEn().whatHappensIfEmailChanged), + findsOneWidget); + expect(find.textContaining(DerivPasskeysLocalizationsEn().tips), + findsOneWidget); + expect( + find.textContaining( + DerivPasskeysLocalizationsEn().beforeUsingPasskeys), + findsOneWidget); + expect( + find.textContaining(DerivPasskeysLocalizationsEn().enableScreenLock), + findsOneWidget); + expect( + find.textContaining( + DerivPasskeysLocalizationsEn().signInGoogleOrIcloud), + findsOneWidget); + expect( + find.textContaining(DerivPasskeysLocalizationsEn().enableBluetooth), + findsOneWidget); + }); + }); +} diff --git a/packages/deriv_passkeys/test/presentation/states/bloc/deriv_passkeys_bloc_setup.dart b/packages/deriv_passkeys/test/presentation/states/bloc/deriv_passkeys_bloc_setup.dart new file mode 100644 index 000000000..f256e2ef2 --- /dev/null +++ b/packages/deriv_passkeys/test/presentation/states/bloc/deriv_passkeys_bloc_setup.dart @@ -0,0 +1,48 @@ +import 'package:deriv_passkeys/deriv_passkeys.dart'; +import 'package:deriv_passkeys/src/domain/entities/account_entity.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockDerivPasskeysService extends Mock implements DerivPasskeysService {} + +class MockPasskeysConnectionInfoEntity extends PasskeysConnectionInfoEntity { + MockPasskeysConnectionInfoEntity( + {required super.endpoint, required super.appId}); +} + +late DerivPasskeysBloc derivPasskeysBloc; +late MockDerivPasskeysService mockDerivPasskeysService; +late MockPasskeysConnectionInfoEntity mockPasskeysConnectionInfoEntity; + +void setupDerivPasskeysBloc() { + mockDerivPasskeysService = MockDerivPasskeysService(); + mockPasskeysConnectionInfoEntity = MockPasskeysConnectionInfoEntity( + appId: 'appId', + endpoint: '', + ); + when(() => mockDerivPasskeysService.isSupported()) + .thenAnswer((_) async => true); + derivPasskeysBloc = DerivPasskeysBloc( + derivPasskeysService: mockDerivPasskeysService, + connectionInfo: mockPasskeysConnectionInfoEntity, + getJwtToken: () async => 'jwtToken', + ); + registerFallbackValue(mockPasskeysConnectionInfoEntity); + registerFallbackValue(() async => 'userAgent'); +} + +void setupSuccessDerivPasskeysVerifyCredentialEvent() { + const DerivPasskeysVerifyCredentialsResponseEntity mockResponseEntity = + DerivPasskeysVerifyCredentialsResponseEntity( + accounts: [ + AccountEntity(loginId: 'VRTC1234', token: 'token'), + ], + refreshToken: 'refresh_token', + ); + + when(() => mockDerivPasskeysService.verifyCredential( + jwtToken: any(named: 'jwtToken'), + passkeysConnectionInfoEntity: + any(named: 'passkeysConnectionInfoEntity'), + userAgent: any(named: 'userAgent'), + )).thenAnswer((_) async => mockResponseEntity); +} diff --git a/packages/deriv_passkeys/test/presentation/states/bloc/deriv_passkeys_bloc_test.dart b/packages/deriv_passkeys/test/presentation/states/bloc/deriv_passkeys_bloc_test.dart new file mode 100644 index 000000000..0627ead7d --- /dev/null +++ b/packages/deriv_passkeys/test/presentation/states/bloc/deriv_passkeys_bloc_test.dart @@ -0,0 +1,270 @@ +import 'package:deriv_passkeys/src/exceptions/platform_exceptions.dart'; +import 'package:deriv_passkeys/src/exceptions/server_exceptions.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:deriv_passkeys/deriv_passkeys.dart'; + +import 'deriv_passkeys_bloc_setup.dart'; + +void main() { + setUp(() { + setupDerivPasskeysBloc(); + }); + + group('DerivPasskeysBloc', () { + test('initial state should be DerivPasskeysInitializedState', () { + expect(derivPasskeysBloc.state, isA()); + }); + + test( + 'initial state should be DerivPasskeysNotSupportedState if not supported', + () async { + when(() => mockDerivPasskeysService.isSupported()) + .thenAnswer((_) async => Future(() => false)); + final DerivPasskeysBloc unsupportedDerivPasskeysBloc = DerivPasskeysBloc( + derivPasskeysService: mockDerivPasskeysService, + connectionInfo: mockPasskeysConnectionInfoEntity, + getJwtToken: () async => 'jwtToken', + ); + await expectLater( + unsupportedDerivPasskeysBloc.stream, + emits( + isA(), + ), + ); + }); + + test( + 'SetDerivPasskeysNotSupportedEvent should emit DerivPasskeysNotSupportedState', + () { + expectLater( + derivPasskeysBloc.stream, + emits(isA()), + ); + + derivPasskeysBloc.add(const SetDerivPasskeysNotSupportedEvent()); + }); + + test( + 'DerivPasskeysVerifyCredentialEvent should emit DerivPasskeysCredentialVerifiedState', + () { + setupSuccessDerivPasskeysVerifyCredentialEvent(); + + expectLater( + derivPasskeysBloc.stream, + emitsInOrder(>[ + isA(), + isA(), + ]), + ); + + derivPasskeysBloc.add(DerivPasskeysVerifyCredentialEvent()); + }); + + test( + 'DerivPasskeysVerifyCredentialEvent should emit DerivPasskeysLoadedState if CanceledPlatformException is thrown', + () { + when(() => mockDerivPasskeysService.verifyCredential( + jwtToken: any(named: 'jwtToken'), + passkeysConnectionInfoEntity: + any(named: 'passkeysConnectionInfoEntity'), + userAgent: any(named: 'userAgent'), + )).thenAnswer((_) async => throw CanceledPlatformException( + code: '', + message: '', + details: '', + )); + + expectLater( + derivPasskeysBloc.stream, + emitsInOrder(>[ + isA(), + isA(), + ]), + ); + + derivPasskeysBloc.add(DerivPasskeysVerifyCredentialEvent()); + }); + + test( + 'DerivPasskeysVerifyCredentialEvent should emit NoCredentialErrorState if NoCredentialPlatformException is thrown', + () { + when(() => mockDerivPasskeysService.verifyCredential( + jwtToken: any(named: 'jwtToken'), + passkeysConnectionInfoEntity: + any(named: 'passkeysConnectionInfoEntity'), + userAgent: any(named: 'userAgent'), + )).thenAnswer((_) async => throw NoCredentialPlatformException( + code: '', + message: '', + details: '', + )); + + expectLater( + derivPasskeysBloc.stream, + emitsInOrder(>[ + isA(), + isA(), + ]), + ); + + derivPasskeysBloc.add(DerivPasskeysVerifyCredentialEvent()); + }); + + test( + 'DerivPasskeysVerifyCredentialEvent should emit DerivPasskeysErrorState if ServerException is thrown', + () { + when(() => mockDerivPasskeysService.verifyCredential( + jwtToken: any(named: 'jwtToken'), + passkeysConnectionInfoEntity: + any(named: 'passkeysConnectionInfoEntity'), + userAgent: any(named: 'userAgent'), + )).thenAnswer((_) async => throw ServerException( + errorCode: '', + message: '', + )); + + expectLater( + derivPasskeysBloc.stream, + emitsInOrder(>[ + isA(), + isA(), + ]), + ); + + derivPasskeysBloc.add(DerivPasskeysVerifyCredentialEvent()); + }); + + test( + 'DerivPasskeysVerifyCredentialEvent should emit DerivPasskeysErrorState if any other error is thrown', + () { + when(() => mockDerivPasskeysService.verifyCredential( + jwtToken: any(named: 'jwtToken'), + passkeysConnectionInfoEntity: + any(named: 'passkeysConnectionInfoEntity'), + userAgent: any(named: 'userAgent'), + )).thenAnswer((_) async => throw Exception()); + + expectLater( + derivPasskeysBloc.stream, + emitsInOrder(>[ + isA(), + isA(), + ]), + ); + + derivPasskeysBloc.add(DerivPasskeysVerifyCredentialEvent()); + }); + + test( + 'DerivPasskeysCreateCredentialEvent should emit DerivPasskeysCreatedSuccessfullyState', + () { + const DerivPasskeyEntity mockCredential = DerivPasskeyEntity( + id: 'id', + name: 'name', + createdAt: 1234, + lastUsed: null, + passkeyId: '', + storedOn: '', + ); + + when(() => mockDerivPasskeysService.createCredential()) + .thenAnswer((_) async => mockCredential); + + expectLater( + derivPasskeysBloc.stream, + emitsInOrder(>[ + isA(), + isA(), + isA(), + ]), + ); + + derivPasskeysBloc.add(DerivPasskeysCreateCredentialEvent()); + }); + + test( + 'DerivPasskeysCreateCredentialEvent should emit DerivPasskeysLoadedState if CanceledPlatformException is thrown', + () { + when(() => mockDerivPasskeysService.createCredential()) + .thenAnswer((_) async => throw CanceledPlatformException( + code: '', + message: '', + details: '', + )); + + expectLater( + derivPasskeysBloc.stream, + emitsInOrder(>[ + isA(), + isA(), + isA(), + ]), + ); + + derivPasskeysBloc.add(DerivPasskeysCreateCredentialEvent()); + }); + + test( + 'DerivPasskeysCreateCredentialEvent should emit DerivPasskeysErrorState if any other error is thrown', + () { + when(() => mockDerivPasskeysService.createCredential()) + .thenAnswer((_) async => throw Exception()); + + expectLater( + derivPasskeysBloc.stream, + emitsInOrder(>[ + isA(), + isA(), + ]), + ); + + derivPasskeysBloc.add(DerivPasskeysCreateCredentialEvent()); + }); + + test( + 'DerivPasskeysGetPasskeysListEvent should emit DerivPasskeysLoadedState', + () { + final List mockPasskeys = [ + const DerivPasskeyEntity( + id: 'id', + name: 'name', + createdAt: 1234, + lastUsed: null, + passkeyId: '', + storedOn: '', + ), + ]; + + when(() => mockDerivPasskeysService.getPasskeysList()) + .thenAnswer((_) async => mockPasskeys); + + expectLater( + derivPasskeysBloc.stream, + emitsInOrder(>[ + isA(), + isA(), + ]), + ); + + derivPasskeysBloc.add(const DerivPasskeysGetPasskeysListEvent()); + }); + + test( + 'DerivPasskeysGetPasskeysListEvent should emit DerivPasskeysErrorState if any error is thrown', + () { + when(() => mockDerivPasskeysService.getPasskeysList()) + .thenAnswer((_) async => throw Exception()); + + expectLater( + derivPasskeysBloc.stream, + emitsInOrder(>[ + isA(), + isA(), + ]), + ); + + derivPasskeysBloc.add(const DerivPasskeysGetPasskeysListEvent()); + }); + }); +} diff --git a/packages/deriv_passkeys/test/presentation/states/bloc/deriv_passkeys_event_test.dart b/packages/deriv_passkeys/test/presentation/states/bloc/deriv_passkeys_event_test.dart new file mode 100644 index 000000000..8991ea9bc --- /dev/null +++ b/packages/deriv_passkeys/test/presentation/states/bloc/deriv_passkeys_event_test.dart @@ -0,0 +1,64 @@ +import 'package:deriv_passkeys/deriv_passkeys.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('DerivPasskeysCreateCredentialEvent', () { + test('props should be empty', () { + final DerivPasskeysEvent event = DerivPasskeysCreateCredentialEvent(); + + expect(event.props, isEmpty); + }); + }); + + group('DerivPasskeysVerifyCredentialEvent', () { + test('props should be empty', () { + final DerivPasskeysEvent event = DerivPasskeysVerifyCredentialEvent(); + + expect(event.props, isEmpty); + }); + }); + + group('DerivPasskeysGetPasskeysListEvent', () { + test('props should be empty', () { + const DerivPasskeysEvent event = DerivPasskeysGetPasskeysListEvent(); + + expect(event.props, isEmpty); + }); + }); + + group('SetDerivPasskeysInitializedEvent', () { + test('props should be empty', () { + const DerivPasskeysEvent event = SetDerivPasskeysInitializedEvent(); + + expect(event.props, isEmpty); + }); + }); + + group('SetDerivPasskeysNotSupportedEvent', () { + test('props should be empty', () { + const DerivPasskeysEvent event = SetDerivPasskeysNotSupportedEvent(); + + expect(event.props, isEmpty); + }); + }); + + group('DerivPasskeysRevokeCredentialEvent', () { + test('props should contain options', () { + const String options = 'revoke_options'; + const DerivPasskeysEvent event = + DerivPasskeysRevokeCredentialEvent(options); + + expect(event.props, [options]); + }); + }); + + group('DerivPasskeysEditCredentialEvent', () { + test('props should contain options', () { + const String options = 'edit_options'; + const DerivPasskeysEvent event = + DerivPasskeysEditCredentialEvent(options); + + expect(event.props, [options]); + }); + }); +} diff --git a/packages/deriv_passkeys/test/presentation/states/bloc/deriv_passkeys_state_test.dart b/packages/deriv_passkeys/test/presentation/states/bloc/deriv_passkeys_state_test.dart new file mode 100644 index 000000000..df0532bb7 --- /dev/null +++ b/packages/deriv_passkeys/test/presentation/states/bloc/deriv_passkeys_state_test.dart @@ -0,0 +1,79 @@ +import 'package:deriv_passkeys/deriv_passkeys.dart'; +import 'package:deriv_passkeys/src/domain/entities/account_entity.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('DerivPasskeysLoadedState', () { + test('props should contain passkeysList', () { + final List passkeysList = []; + final DerivPasskeysState state = DerivPasskeysLoadedState(passkeysList); + expect(state.props, contains(passkeysList)); + }); + }); + + group('DerivPasskeysInitializedState', () { + test('props should be empty', () { + final DerivPasskeysState state = DerivPasskeysInitializedState(); + expect(state.props, isEmpty); + }); + }); + + group('DerivPasskeysLoadingState', () { + test('props should be empty', () { + final DerivPasskeysState state = DerivPasskeysLoadingState(); + expect(state.props, isEmpty); + }); + }); + + group('DerivPasskeysCreatedSuccessfullyState', () { + test('props should be empty', () { + final DerivPasskeysState state = DerivPasskeysCreatedSuccessfullyState(); + expect(state.props, isEmpty); + }); + }); + + group('DerivPasskeysCredentialVerifiedState', () { + test('props should contain token', () { + const String token = 'example_token'; + const String refreshToken = 'example_refresh_token'; + const DerivPasskeysState state = DerivPasskeysCredentialVerifiedState( + accounts: [ + AccountEntity(loginId: 'example_login_id', token: token), + ], + refreshToken: refreshToken, + ); + expect( + state.props, + containsAll([ + [ + const AccountEntity(loginId: 'example_login_id', token: token), + ], + refreshToken + ])); + }); + }); + + group('DerivPasskeysNotSupportedState', () { + test('props should be empty', () { + final DerivPasskeysState state = DerivPasskeysNotSupportedState(); + expect(state.props, isEmpty); + }); + }); + + group('DerivPasskeysErrorState', () { + test('props should contain message and errorCode', () { + const String message = 'example_message'; + const String errorCode = 'example_error_code'; + const DerivPasskeysState state = + DerivPasskeysErrorState(message, errorCode: errorCode); + expect(state.props, containsAll([message, errorCode])); + }); + }); + + group('NoCredentialErrorState', () { + test('props should contain message and errorCode', () { + const NoCredentialErrorState state = NoCredentialErrorState(); + expect(state.props, contains('No credential found')); + }); + }); +} diff --git a/packages/deriv_passkeys/test/presentation/utils/date_time_utils_test.dart b/packages/deriv_passkeys/test/presentation/utils/date_time_utils_test.dart new file mode 100644 index 000000000..22c28d549 --- /dev/null +++ b/packages/deriv_passkeys/test/presentation/utils/date_time_utils_test.dart @@ -0,0 +1,22 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:deriv_passkeys/src/presentation/utils/date_time_utils.dart'; + +void main() { + group('DateTimeUtils', () { + test('dateTimeFromTimestamp should return DateTime', () { + const int timestamp = 1630444800000; + + final DateTime result = dateTimeFromTimestamp(timestamp); + + expect(result, isA()); + }); + + test('formattedDate should return formatted date string', () { + final DateTime date = DateTime(2023, 4, 20); // Provide a valid date + + final String result = formattedDate(date); + + expect(result, isA()); + }); + }); +} diff --git a/packages/deriv_passkeys/test/presentation/widgets/continue_with_passkey_button_test.dart b/packages/deriv_passkeys/test/presentation/widgets/continue_with_passkey_button_test.dart new file mode 100644 index 000000000..f3f580ed6 --- /dev/null +++ b/packages/deriv_passkeys/test/presentation/widgets/continue_with_passkey_button_test.dart @@ -0,0 +1,160 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:deriv_localizations/l10n/generated/deriv_passkeys/deriv_passkeys_localizations.dart'; +import 'package:deriv_localizations/l10n/generated/deriv_passkeys/deriv_passkeys_localizations_en.dart'; +import 'package:deriv_passkeys/src/presentation/states/bloc/deriv_passkeys_bloc.dart'; +import 'package:deriv_passkeys/src/presentation/widgets/continue_with_passkey_button.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MockDerivPasskeysBloc + extends MockBloc + implements DerivPasskeysBloc {} + +class _TestPage extends StatelessWidget { + const _TestPage(); + + @override + Widget build(BuildContext context) => MaterialApp( + localizationsDelegates: const >[ + DerivPasskeysLocalizations.delegate, + ], + locale: const Locale('en'), + home: Scaffold( + body: ContinueWithPasskeyButton( + onTap: () {}, + ), + ), + ); +} + +void main() { + group('ContinueWithPasskeyButton', () { + late MockDerivPasskeysBloc derivPasskeysBloc; + setUp(() { + derivPasskeysBloc = MockDerivPasskeysBloc(); + + when(() => derivPasskeysBloc.state).thenReturn( + DerivPasskeysInitializedState(), + ); + }); + + testWidgets('renders correctly', (WidgetTester tester) async { + await tester.pumpWidget( + BlocProvider( + create: (BuildContext context) => derivPasskeysBloc, + child: const _TestPage(), + ), + ); + + expect(find.byType(InkWell), findsOneWidget); + expect(find.byType(Container), findsOneWidget); + expect(find.byType(SvgPicture), findsOneWidget); + expect(find.text('Passkey'), findsOneWidget); + }); + + testWidgets('does not render if passkeys not supported', + (WidgetTester tester) async { + when(() => derivPasskeysBloc.state).thenReturn( + DerivPasskeysNotSupportedState(), + ); + await tester.pumpWidget( + BlocProvider( + create: (BuildContext context) => derivPasskeysBloc, + child: const _TestPage(), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byType(InkWell), findsNothing); + expect(find.byType(SvgPicture), findsNothing); + expect(find.text('Passkey'), findsNothing); + }); + + // if you want to test the onPressed callback, you can use the following code + testWidgets('should call DerivPasskeysVerifyCredentialEvent when pressed', + (WidgetTester tester) async { + await tester.pumpWidget( + BlocProvider( + create: (BuildContext context) => derivPasskeysBloc, + child: const _TestPage(), + ), + ); + await tester.pumpAndSettle(); + await tester.tap(find.byType(InkWell)); + await tester.pumpAndSettle(); + + verify(() => derivPasskeysBloc.add(DerivPasskeysVerifyCredentialEvent())) + .called(1); + }); + + testWidgets( + 'should show alert dialog when NoCredentialErrorState is emitted', + (WidgetTester tester) async { + whenListen( + derivPasskeysBloc, + Stream.fromIterable( + [const NoCredentialErrorState()])); + await tester.pumpWidget( + BlocProvider( + create: (BuildContext context) => derivPasskeysBloc, + child: const _TestPage(), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byType(AlertDialog), findsOneWidget); + expect(find.text(DerivPasskeysLocalizationsEn().noPasskeyFound), + findsOneWidget); + expect( + find.text(DerivPasskeysLocalizationsEn().noPasskeyFoundDescription), + findsOneWidget); + expect(find.text(DerivPasskeysLocalizationsEn().ok.toUpperCase()), + findsOneWidget); + + await tester + .tap(find.text(DerivPasskeysLocalizationsEn().ok.toUpperCase())); + await tester.pumpAndSettle(); + + expect(find.byType(AlertDialog), findsNothing); + }); + + testWidgets( + 'should show alert dialog with error message when DerivPasskeysErrorState is emitted with errorCode PASSKEYS_SERVICE_ERROR', + (WidgetTester tester) async { + whenListen( + derivPasskeysBloc, + Stream.fromIterable([ + const DerivPasskeysErrorState( + 'error_message', + errorCode: 'PASSKEYS_SERVICE_ERROR', + ) + ])); + await tester.pumpWidget( + BlocProvider( + create: (BuildContext context) => derivPasskeysBloc, + child: const _TestPage(), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.byType(AlertDialog), findsOneWidget); + expect(find.text(DerivPasskeysLocalizationsEn().unexpectedError), + findsOneWidget); + expect(find.text('error_message'), findsOneWidget); + expect(find.text(DerivPasskeysLocalizationsEn().ok.toUpperCase()), + findsOneWidget); + + await tester + .tap(find.text(DerivPasskeysLocalizationsEn().ok.toUpperCase())); + await tester.pumpAndSettle(); + + expect(find.byType(AlertDialog), findsNothing); + }); + }); +} diff --git a/packages/deriv_passkeys/test/presentation/widgets/icon_text_row_widget_test.dart b/packages/deriv_passkeys/test/presentation/widgets/icon_text_row_widget_test.dart new file mode 100644 index 000000000..e1ad6fb79 --- /dev/null +++ b/packages/deriv_passkeys/test/presentation/widgets/icon_text_row_widget_test.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:deriv_passkeys/src/presentation/widgets/icon_text_row_widget.dart'; + +void main() { + testWidgets('IconTextRowWidget displays icon and text correctly', + (WidgetTester tester) async { + const String assetName = 'assets/svg/passkey_icon.svg'; + const String text = 'Sample Text'; + + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: IconTextRowWidget( + assetName: assetName, + text: text, + ), + ), + ), + ); + + final Finder svgFinder = find.byType(SvgPicture); + final Finder textFinder = find.text(text); + + expect(svgFinder, findsOneWidget); + expect(textFinder, findsOneWidget); + + final SvgPicture svgWidget = tester.widget(svgFinder) as SvgPicture; + final Text textWidget = tester.widget(textFinder) as Text; + + expect((svgWidget.bytesLoader as SvgAssetLoader).packageName, + 'deriv_passkeys'); + expect((svgWidget.bytesLoader as SvgAssetLoader).assetName, assetName); + expect(textWidget.data, text); + }); +} diff --git a/packages/deriv_passkeys/test/presentation/widgets/passkey_widget_test.dart b/packages/deriv_passkeys/test/presentation/widgets/passkey_widget_test.dart new file mode 100644 index 000000000..c4d05714d --- /dev/null +++ b/packages/deriv_passkeys/test/presentation/widgets/passkey_widget_test.dart @@ -0,0 +1,37 @@ +import 'package:deriv_localizations/l10n/generated/deriv_passkeys/deriv_passkeys_localizations.dart'; +import 'package:deriv_passkeys/src/domain/entities/deriv_passkey_entity.dart'; +import 'package:deriv_passkeys/src/presentation/widgets/passkey_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('PasskeyWidget', () { + const DerivPasskeyEntity passkey = DerivPasskeyEntity( + storedOn: 'Device', + lastUsed: 1638297600000, + createdAt: 1638297600000, + id: '', + name: 'Passkey', + passkeyId: '', + ); + + testWidgets('should display passkey information', + (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + localizationsDelegates: >[ + DerivPasskeysLocalizations.delegate, + ], + locale: Locale('en'), + home: Scaffold( + body: PasskeyWidget(passkey: passkey), + ), + ), + ); + + expect(find.text('Passkey'), findsOneWidget); + expect(find.textContaining('Device'), findsOneWidget); + expect(find.textContaining('November 30th, 2021'), findsOneWidget); + }); + }); +} diff --git a/packages/deriv_passkeys/test/presentation/widgets/section_title_and_content_test.dart b/packages/deriv_passkeys/test/presentation/widgets/section_title_and_content_test.dart new file mode 100644 index 000000000..a8a1bf8eb --- /dev/null +++ b/packages/deriv_passkeys/test/presentation/widgets/section_title_and_content_test.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:deriv_passkeys/src/presentation/widgets/section_title_and_content.dart'; + +void main() { + group('SectionTitleAndContent', () { + testWidgets('should display the title and content', + (WidgetTester tester) async { + const String title = 'Test Title'; + const String content = 'Test Content'; + + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: SectionTitleAndContent( + title: title, + texts: [content], + ), + ), + ), + ); + + final Finder titleFinder = find.text(title); + final Finder contentFinder = find.text(content); + + expect(titleFinder, findsOneWidget); + expect(contentFinder, findsOneWidget); + }); + }); +} diff --git a/packages/deriv_passkeys/test/presentation/widgets/unordered_list_widget_test.dart b/packages/deriv_passkeys/test/presentation/widgets/unordered_list_widget_test.dart new file mode 100644 index 000000000..dba0b13b9 --- /dev/null +++ b/packages/deriv_passkeys/test/presentation/widgets/unordered_list_widget_test.dart @@ -0,0 +1,53 @@ +import 'package:deriv_passkeys/src/presentation/widgets/unordered_list_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('UnorderedList', () { + const List texts = ['Item 1', 'Item 2', 'Item 3']; + const TextStyle? style = TextStyle(fontSize: 16); + + testWidgets('Renders the correct number of items', + (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: UnorderedList(texts: texts, style: style), + ), + ), + ); + + final Finder listItemFinder = find.byType(UnorderedListItem); + expect(listItemFinder, findsNWidgets(texts.length)); + }); + + testWidgets('Renders the correct text', (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: UnorderedList(texts: texts, style: style), + ), + ), + ); + + for (final String text in texts) { + final Finder textFinder = find.text(text); + expect(textFinder, findsOneWidget); + } + }); + + testWidgets('Renders the correct bullet points', + (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: UnorderedList(texts: texts, style: style), + ), + ), + ); + + final Finder bulletPointFinder = find.text('\u2022'); + expect(bulletPointFinder, findsNWidgets(texts.length)); + }); + }); +} diff --git a/packages/deriv_rudderstack/CHANGELOG.md b/packages/deriv_rudderstack/CHANGELOG.md index 0ac923a0d..d55843604 100644 --- a/packages/deriv_rudderstack/CHANGELOG.md +++ b/packages/deriv_rudderstack/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.2.0 + + - **FEAT**(analytics): Add indicator events ([#846](https://github.com/regentmarkets/flutter-deriv-packages/issues/846)). ([1d30ddc9](https://github.com/regentmarkets/flutter-deriv-packages/commit/1d30ddc9c7510e280cc90fc7e1308b945d5758ed)) + +## 1.1.1 + + - **REFACTOR**(deriv_passkeys): Add call back to call after pass key flow finished ([#831](https://github.com/regentmarkets/flutter-deriv-packages/issues/831)). ([444e963e](https://github.com/regentmarkets/flutter-deriv-packages/commit/444e963e949334ae81b170c73c1a35afad7a1e0e)) + ## 1.1.0 Migrated to null safety diff --git a/packages/deriv_rudderstack/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/packages/deriv_rudderstack/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java index 4325fcb20..f6ffee652 100644 --- a/packages/deriv_rudderstack/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java +++ b/packages/deriv_rudderstack/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java @@ -17,7 +17,7 @@ public final class GeneratedPluginRegistrant { public static void registerWith(@NonNull FlutterEngine flutterEngine) { try { flutterEngine.getPlugins().add(new com.deriv.deriv_rudderstack.DerivRudderstackPlugin()); - } catch(Exception e) { + } catch (Exception e) { Log.e(TAG, "Error registering plugin deriv_rudderstack, com.deriv.deriv_rudderstack.DerivRudderstackPlugin", e); } } diff --git a/packages/deriv_rudderstack/example/pubspec.lock b/packages/deriv_rudderstack/example/pubspec.lock index 1d8d0ec9e..9d00b7f87 100644 --- a/packages/deriv_rudderstack/example/pubspec.lock +++ b/packages/deriv_rudderstack/example/pubspec.lock @@ -37,17 +37,17 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.18.0" deriv_rudderstack: dependency: "direct main" description: path: ".." relative: true source: path - version: "1.1.0" + version: "1.1.1" fake_async: dependency: transitive description: @@ -66,46 +66,62 @@ packages: description: flutter source: sdk version: "0.0.0" - js: + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: dependency: transitive description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "3.0.1" matcher: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.15.0" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" sky_engine: dependency: transitive description: flutter @@ -115,26 +131,26 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -155,10 +171,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.7.2" vector_math: dependency: transitive description: @@ -167,6 +183,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + url: "https://pub.dev" + source: hosted + version: "14.2.5" sdks: - dart: ">=3.0.0 <4.0.0" - flutter: ">=3.10.2" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/packages/deriv_rudderstack/pubspec.lock b/packages/deriv_rudderstack/pubspec.lock index 9b554c600..df9b83888 100644 --- a/packages/deriv_rudderstack/pubspec.lock +++ b/packages/deriv_rudderstack/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.18.0" fake_async: dependency: transitive description: @@ -59,46 +59,62 @@ packages: description: flutter source: sdk version: "0.0.0" - js: + leak_tracker: dependency: transitive description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" matcher: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.15.0" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" sky_engine: dependency: transitive description: flutter @@ -108,26 +124,26 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -148,10 +164,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.7.2" vector_math: dependency: transitive description: @@ -160,6 +176,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + url: "https://pub.dev" + source: hosted + version: "14.2.5" sdks: - dart: ">=3.0.0 <4.0.0" - flutter: ">=3.10.2" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/packages/deriv_rudderstack/pubspec.yaml b/packages/deriv_rudderstack/pubspec.yaml index bdc9ae481..5fb8def1f 100644 --- a/packages/deriv_rudderstack/pubspec.yaml +++ b/packages/deriv_rudderstack/pubspec.yaml @@ -1,6 +1,6 @@ name: deriv_rudderstack description: A new Flutter plugin to wrap RudderStack SDK. -version: 1.1.0 +version: 1.2.0 homepage: https://deriv.com/ publish_to: "none" diff --git a/packages/deriv_store_launcher/CHANGELOG.md b/packages/deriv_store_launcher/CHANGELOG.md index 761894f12..80941b49a 100644 --- a/packages/deriv_store_launcher/CHANGELOG.md +++ b/packages/deriv_store_launcher/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.0.2 + + - **FEAT**(deriv_store_launcher): [DRGO-1284] Added new functionalities(launch app, ios support, isAppInstalled function) to deriv_store_launcher. ([#875](https://github.com/regentmarkets/flutter-deriv-packages/issues/875)). ([27066759](https://github.com/regentmarkets/flutter-deriv-packages/commit/27066759d5cb1f25e1ed8b942aa28b8e99666054)) + +## 0.0.1+1 + + - **REFACTOR**(deriv_store_launcher): udpated kotlin and gradle version ([#400](https://github.com/regentmarkets/flutter-deriv-packages/issues/400)). ([ad9d72e1](https://github.com/regentmarkets/flutter-deriv-packages/commit/ad9d72e10186e695a72d022d3b3f6ebdd5120666)) + ## 0.0.1 - initial release. diff --git a/packages/deriv_store_launcher/README.md b/packages/deriv_store_launcher/README.md index 6c137ae82..690041cd6 100644 --- a/packages/deriv_store_launcher/README.md +++ b/packages/deriv_store_launcher/README.md @@ -1,25 +1,211 @@ -# deriv_store_launcher +# Deriv Store Launcher Flutter Plugin Documentation -A plugin to lunch app stores base on platform and manufacturer. +## Table of Contents +- [Overview](#overview) +- [Installation](#installation) + - [iOS Setup](#ios-setup) + - [Android Setup](#android-setup) +- [Usage](#usage) + - [Open Store Listing](#open-store-listing) + - [Check if App is Installed](#check-if-app-is-installed) + - [Open App or Redirect to Store](#open-app-or-redirect-to-store) +- [API Reference](#api-reference) + - [openStoreListing](#openstorelisting) + - [isAppInstalled](#isappinstalled) + - [openApp](#openapp) +- [Platform Specific Configuration](#platform-specific-configuration) + - [iOS Info.plist Configuration](#ios-infoplist-configuration) + - [Android Manifest Configuration](#android-manifest-configuration) -## Getting Started +--- -This project is a starting point for a Flutter -[plug-in package](https://flutter.dev/developing-packages/), -a specialized package that includes platform-specific implementation code for -Android and/or iOS. +## Overview -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +The `DerivStoreLauncher` Flutter plugin allows you to: +- Launch an app if it is installed on the device. +- Redirect the user to the appropriate App Store (Google Play or Apple App Store) if the desired app is not installed on the device. +- Check if a specific app is installed on the device. -## iOS specific configuration +This plugin supports **Android** and **iOS** platforms and assumes reasonable defaults for each platform. -Don't forget to add the following to `Info.plist` +--- + +## Installation + +To use this plugin, it's necessary to add platform-specific configurations for both iOS and Android. + +### iOS Setup + +1. Open your `ios/Runner/Info.plist` file. +2. Add the `LSApplicationQueriesSchemes` key. This is required so that your app can query other installed apps on the device. ```xml LSApplicationQueriesSchemes itms-apps + derivx + deriv + metatrader-5 ``` + +### Android Setup + +1. Open your `AndroidManifest.xml`. +2. Grant the necessary permissions to check installed apps by adding the following permission: + +```xml + +``` + +> **Note:** This permission is required for checking if an app is installed on Android devices. Be mindful when submitting apps with this permission to the Google Play Store, as it may require extra justification. + +--- + +## Usage + +Import the `DerivStoreLauncher` package and start using its available functions. + +```dart +import 'package:deriv_store_launcher/deriv_store_launcher.dart'; +``` + +### Open Store Listing + +This opens the respective app store (Google Play or iOS App Store), depending on the platform. + +```dart +await DerivStoreLauncher.openStoreListing( + androidPackageName: 'com.example.yourapp', + iosAppStoreId: '123456789', + iosUrlScheme: 'yourapp://', +); +``` + +### Check if App is Installed + +Check if a specified app is installed using the iOS URL scheme or Android package name. + +```dart +bool isInstalled = await DerivStoreLauncher.isAppInstalled( + androidPackageName: 'com.example.yourapp', + iosUrlScheme: 'yourapp://', +); +``` + +### Open App or Redirect to Store + +Use this method to open an installed app directly. If the app is not installed, you can either redirect the user to the app store or handle it manually. + +```dart +bool isAppOpened = await DerivStoreLauncher.openApp( + androidPackageName: 'com.example.yourapp', + iosUrlScheme: 'yourapp://', + iosAppStoreId: '123456789', + openStore: true, // Automatically redirects to store if app is not installed +); +``` + +--- + +## API Reference + +### openStoreListing + +Opens the app's store listing depending on the platform: + +- **Google Play Store** for Android. +- **App Store** for iOS. + +#### Parameters: +- `androidPackageName`: The package name of the Android app (e.g., `com.example.yourapp`). +- `iosAppStoreId`: The App Store ID for navigating to the iOS app store listing. +- `iosUrlScheme`: The URL scheme that identifies the app on iOS (e.g., `myapp://`). + +#### Example: +```dart +await DerivStoreLauncher.openStoreListing( + androidPackageName: 'com.example.yourapp', + iosAppStoreId: '123456789', + iosUrlScheme: 'yourapp://', +); +``` + +### isAppInstalled + +Checks if the specified app is installed on the device: + +- On Android, the `androidPackageName` is used. +- On iOS, the `iosUrlScheme` is used. + +#### Parameters: +- `androidPackageName`: The package name of the Android app. +- `iosUrlScheme`: The custom URL scheme of the iOS app to check if installed. + +#### Returns: +- A `Future` indicating whether the app is installed. + +#### Example: +```dart +bool isInstalled = await DerivStoreLauncher.isAppInstalled( + androidPackageName: 'com.example.yourapp', + iosUrlScheme: 'yourapp://', +); +``` + +### openApp + +Opens the app if it is installed, otherwise redirects to the app store listing if the `openStore` option is `true`. + +#### Parameters: +- `androidPackageName`: The package name of the Android app. +- `iosUrlScheme`: The URL scheme of the iOS app. +- `iosAppStoreId`: The App Store ID for iOS, required if `openStore` is `true`. +- `openStore`: A boolean flag indicating whether to redirect to the store if the app isn’t installed. Default: `false`. + +#### Returns: +- A `Future` indicating whether the app was opened successfully. If the app is not installed and the `openStore` flag is `true`, it opens the app store. + +#### Example: +```dart +bool isAppOpened = await DerivStoreLauncher.openApp( + androidPackageName: 'com.example.yourapp', + iosUrlScheme: 'yourapp://', + iosAppStoreId: '123456789', + openStore: true, +); +``` + +--- + +## Platform Specific Configuration + +### iOS Info.plist Configuration + +To check for installed apps or open specific URL schemes on iOS, the `LSApplicationQueriesSchemes` key must be added in your **Info.plist**. This allows the system to query whether other apps are installed on the system. + +Add the following lines in `Info.plist`: + +```xml +LSApplicationQueriesSchemes + + itms-apps + yourapp + +``` + +### Android Manifest Configuration + +To check for installed apps on Android, you will need to grant the `QUERY_ALL_PACKAGES` permission in your **AndroidManifest.xml**. This allows your app to retrieve information about all installed apps on the device. + +Add this to your `AndroidManifest.xml`: + +```xml + +``` + +This permission is necessary for your app to query external apps and invoke them; however, Google Play may require a specific declaration justifying your use of this permission. You may also need to test across Android 11+ devices for potential restrictions. + +--- + +Now you're ready to use the `DerivStoreLauncher` in your Flutter project to open apps or store listings with ease. Happy coding! 🎉 \ No newline at end of file diff --git a/packages/deriv_store_launcher/android/build.gradle b/packages/deriv_store_launcher/android/build.gradle index a85aad2db..a0a7ee049 100644 --- a/packages/deriv_store_launcher/android/build.gradle +++ b/packages/deriv_store_launcher/android/build.gradle @@ -2,14 +2,14 @@ group 'com.deriv.app.deriv_store_launcher' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.5.20' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.0' + classpath 'com.android.tools.build:gradle:7.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/packages/deriv_store_launcher/android/src/main/kotlin/com/deriv/app/deriv_store_launcher/DerivStoreLauncherPlugin.kt b/packages/deriv_store_launcher/android/src/main/kotlin/com/deriv/app/deriv_store_launcher/DerivStoreLauncherPlugin.kt index 864d867af..69715942a 100644 --- a/packages/deriv_store_launcher/android/src/main/kotlin/com/deriv/app/deriv_store_launcher/DerivStoreLauncherPlugin.kt +++ b/packages/deriv_store_launcher/android/src/main/kotlin/com/deriv/app/deriv_store_launcher/DerivStoreLauncherPlugin.kt @@ -1,112 +1,126 @@ package com.deriv.app.deriv_store_launcher -import android.content.ComponentName import android.content.Context import android.content.Intent -import android.content.pm.ResolveInfo +import android.content.pm.PackageManager import android.net.Uri - import androidx.annotation.NonNull - import io.flutter.embedding.engine.plugins.FlutterPlugin - import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel -import io.flutter.plugin.common.MethodChannel.MethodCallHandler -import io.flutter.plugin.common.MethodChannel.Result -import io.flutter.plugin.common.PluginRegistry.Registrar -import android.content.pm.PackageManager -class DerivStoreLauncherPlugin : FlutterPlugin, MethodCallHandler { +class DerivStoreLauncherPlugin : FlutterPlugin, MethodChannel.MethodCallHandler { - companion object { - private val TAG = "DERIV_STORE_LUNCHER_PLUGIN" + private lateinit var context: Context - @JvmStatic - fun registerWith(registrar: Registrar) { - val channel = MethodChannel(registrar.messenger(), "deriv_store_launcher") - - channel.setMethodCallHandler(DerivStoreLauncherPlugin()) + override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + context = flutterPluginBinding.applicationContext + val channel = MethodChannel(flutterPluginBinding.binaryMessenger, "deriv_store_launcher") + channel.setMethodCallHandler(this) } - } - - private lateinit var context: Context - - override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - val channel = MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "deriv_store_launcher") - - context = flutterPluginBinding.applicationContext - channel.setMethodCallHandler(this) - } - - override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { - if (call.method == "openStore") { - if (!call.hasArgument("app_id")) { - result.error(TAG, "Missing Parameter in method: (${call.method})", null) - - return - } + override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: MethodChannel.Result) { + val packageName = call.argument("package_name") ?: "" + when (call.method) { + "openStore" -> handleOpenStore(packageName, result) + "isAppInstalled" -> handleIsAppInstalled(packageName, result) + "openApp" -> handleOpenApp(packageName, call.argument("open_store"), result) + else -> result.notImplemented() + } + } - val packageName: String = call.argument("app_id")!! + override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {} - if (openStore(packageName)) { - result.success(true) - } else { - result.error(TAG, "Unknown Error in method: (${call.method})", null) - } - } else { - result.notImplemented() + private fun handleOpenStore(packageName: String, result: MethodChannel.Result) { + if (packageName.isNotEmpty()) { + result.success(openStore(packageName)) + } else { + result.error("INVALID_ARGUMENT", "Empty or null package name", null) + } } - } - override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { } - - private fun isPackageInstalled(packageName: String, packageManager: PackageManager): Boolean { - return try { - packageManager.getApplicationInfo(packageName, 0).enabled + private fun handleIsAppInstalled(packageName: String, result: MethodChannel.Result) { + if (packageName.isNotEmpty()) { + result.success(isAppInstalled(packageName)) + } else { + result.error("INVALID_ARGUMENT", "Empty or null package name", null) + } } - catch (e: PackageManager.NameNotFoundException) { - false + + private fun handleOpenApp( + packageName: String, + shouldOpenStore: String?, + result: MethodChannel.Result + ) { + if (packageName.isNotEmpty()) { + result.success(openApp(packageName, shouldOpenStore == "true")) + } else { + result.error("INVALID_ARGUMENT", "Empty or null package name", null) + } } - } - - private fun openStore(appId: String): Boolean { - if (isPackageInstalled("com.android.vending", context.packageManager)) { - return launchIntent("market://details?id=$appId", "com.android.vending") - } else if (isPackageInstalled("com.huawei.appmarket", context.packageManager)) { - return launchIntent("appmarket://details?id=$appId", "com.huawei.appmarket") - } else { - return launchWeb("https://play.google.com/store/apps/details?id=$appId") + + private fun isAppInstalled(packageName: String): Boolean { + return try { + context.packageManager.getPackageInfo(packageName ?: "", PackageManager.GET_ACTIVITIES) + true + } catch (e: PackageManager.NameNotFoundException) { + false + } } - } - private fun launchIntent(url: String, packageName: String): Boolean { - val intent = Intent(Intent.ACTION_VIEW).apply { - data = Uri.parse(url) + private fun openStore(packageName: String): Boolean { + return when { + isAppInstalled("com.android.vending") -> launchIntent( + "market://details?id=$packageName", + "com.android.vending" + ) - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + isAppInstalled("com.huawei.appmarket") -> launchIntent( + "appmarket://details?id=$packageName", + "com.huawei.appmarket" + ) - setPackage(packageName) + else -> launchWeb("https://play.google.com/store/apps/details?id=$packageName") + } } - context.startActivity(intent) - - return true - } + private fun launchIntent(url: String, packageName: String): Boolean { + val intent = Intent(Intent.ACTION_VIEW).apply { + data = Uri.parse(url) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED or Intent.FLAG_ACTIVITY_CLEAR_TOP) + setPackage(packageName) + } + + return try { + context.startActivity(intent) + true + } catch (e: Exception) { + false + } + } - private fun launchWeb(url: String): Boolean { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + private fun launchWeb(url: String): Boolean { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + return if (intent.resolveActivity(context.packageManager) != null) { + context.startActivity(intent) + true + } else { + false + } + } - if (intent.resolveActivity(context.packageManager) == null) { - return false - } else { - context.startActivity(intent) - - return true + private fun openApp(packageName: String, shouldOpenStore: Boolean): Boolean { + val intent = context.packageManager.getLaunchIntentForPackage(packageName) + + return if (intent != null) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(intent) + true + } else if (shouldOpenStore) { + openStore(packageName) + } else { + false + } } - } -} +} \ No newline at end of file diff --git a/packages/deriv_store_launcher/example/android/app/src/main/AndroidManifest.xml b/packages/deriv_store_launcher/example/android/app/src/main/AndroidManifest.xml index eaade81bf..70d0d62ec 100644 --- a/packages/deriv_store_launcher/example/android/app/src/main/AndroidManifest.xml +++ b/packages/deriv_store_launcher/example/android/app/src/main/AndroidManifest.xml @@ -1,28 +1,31 @@ - + + + + android:icon="@mipmap/ic_launcher" + android:label="deriv_store_launcher_example"> + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" /> - - + + + + diff --git a/packages/deriv_widgetbook/android/app/src/main/AndroidManifest.xml b/packages/deriv_widgetbook/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..6492b88eb --- /dev/null +++ b/packages/deriv_widgetbook/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/packages/deriv_widgetbook/android/app/src/main/kotlin/com/example/deriv_widgetbook/MainActivity.kt b/packages/deriv_widgetbook/android/app/src/main/kotlin/com/example/deriv_widgetbook/MainActivity.kt new file mode 100644 index 000000000..db88d41d7 --- /dev/null +++ b/packages/deriv_widgetbook/android/app/src/main/kotlin/com/example/deriv_widgetbook/MainActivity.kt @@ -0,0 +1,6 @@ +package com.deriv.widgetbook + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/packages/deriv_widgetbook/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/deriv_widgetbook/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/packages/deriv_widgetbook/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/deriv_widgetbook/android/app/src/main/res/drawable/launch_background.xml b/packages/deriv_widgetbook/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/packages/deriv_widgetbook/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/deriv_widgetbook/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/deriv_widgetbook/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..db77bb4b7 Binary files /dev/null and b/packages/deriv_widgetbook/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/deriv_widgetbook/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/deriv_widgetbook/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..17987b79b Binary files /dev/null and b/packages/deriv_widgetbook/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/deriv_widgetbook/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/deriv_widgetbook/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..09d439148 Binary files /dev/null and b/packages/deriv_widgetbook/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/deriv_widgetbook/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/deriv_widgetbook/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..d5f1c8d34 Binary files /dev/null and b/packages/deriv_widgetbook/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/deriv_widgetbook/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/deriv_widgetbook/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..4d6372eeb Binary files /dev/null and b/packages/deriv_widgetbook/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/deriv_widgetbook/android/app/src/main/res/values-night/styles.xml b/packages/deriv_widgetbook/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/packages/deriv_widgetbook/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/deriv_widgetbook/android/app/src/main/res/values/styles.xml b/packages/deriv_widgetbook/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..cb1ef8805 --- /dev/null +++ b/packages/deriv_widgetbook/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/deriv_widgetbook/android/app/src/profile/AndroidManifest.xml b/packages/deriv_widgetbook/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..399f6981d --- /dev/null +++ b/packages/deriv_widgetbook/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/deriv_widgetbook/android/build.gradle b/packages/deriv_widgetbook/android/build.gradle new file mode 100644 index 000000000..f7eb7f63c --- /dev/null +++ b/packages/deriv_widgetbook/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/packages/deriv_widgetbook/android/gradle.properties b/packages/deriv_widgetbook/android/gradle.properties new file mode 100644 index 000000000..94adc3a3f --- /dev/null +++ b/packages/deriv_widgetbook/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/deriv_widgetbook/android/settings.gradle b/packages/deriv_widgetbook/android/settings.gradle new file mode 100644 index 000000000..55c4ca8b1 --- /dev/null +++ b/packages/deriv_widgetbook/android/settings.gradle @@ -0,0 +1,20 @@ +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") + + plugins { + id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false + } +} + +include ":app" + +apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/packages/deriv_widgetbook/assets/icons/ic_flag_en.png b/packages/deriv_widgetbook/assets/icons/ic_flag_en.png new file mode 100644 index 000000000..c676e2eb5 Binary files /dev/null and b/packages/deriv_widgetbook/assets/icons/ic_flag_en.png differ diff --git a/packages/deriv_widgetbook/assets/icons/ic_flag_id.png b/packages/deriv_widgetbook/assets/icons/ic_flag_id.png new file mode 100644 index 000000000..6e89392c2 Binary files /dev/null and b/packages/deriv_widgetbook/assets/icons/ic_flag_id.png differ diff --git a/packages/deriv_widgetbook/assets/icons/ic_flag_zh.png b/packages/deriv_widgetbook/assets/icons/ic_flag_zh.png new file mode 100644 index 000000000..c854beeca Binary files /dev/null and b/packages/deriv_widgetbook/assets/icons/ic_flag_zh.png differ diff --git a/packages/deriv_widgetbook/assets/icons/ic_no_connection.svg b/packages/deriv_widgetbook/assets/icons/ic_no_connection.svg new file mode 100644 index 000000000..71c329c3b --- /dev/null +++ b/packages/deriv_widgetbook/assets/icons/ic_no_connection.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/deriv_widgetbook/ios/.gitignore b/packages/deriv_widgetbook/ios/.gitignore new file mode 100644 index 000000000..7a7f9873a --- /dev/null +++ b/packages/deriv_widgetbook/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/packages/deriv_widgetbook/ios/Flutter/AppFrameworkInfo.plist b/packages/deriv_widgetbook/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..9625e105d --- /dev/null +++ b/packages/deriv_widgetbook/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/packages/deriv_widgetbook/ios/Flutter/Debug.xcconfig b/packages/deriv_widgetbook/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..ec97fc6f3 --- /dev/null +++ b/packages/deriv_widgetbook/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/packages/deriv_widgetbook/ios/Flutter/Release.xcconfig b/packages/deriv_widgetbook/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..c4855bfe2 --- /dev/null +++ b/packages/deriv_widgetbook/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/packages/deriv_widgetbook/ios/Podfile b/packages/deriv_widgetbook/ios/Podfile new file mode 100644 index 000000000..fdcc671eb --- /dev/null +++ b/packages/deriv_widgetbook/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/packages/deriv_widgetbook/ios/Podfile.lock b/packages/deriv_widgetbook/ios/Podfile.lock new file mode 100644 index 000000000..c2d418ef1 --- /dev/null +++ b/packages/deriv_widgetbook/ios/Podfile.lock @@ -0,0 +1,51 @@ +PODS: + - Flutter (1.0.0) + - flutter_inappwebview_ios (0.0.1): + - Flutter + - flutter_inappwebview_ios/Core (= 0.0.1) + - OrderedSet (~> 5.0) + - flutter_inappwebview_ios/Core (0.0.1): + - Flutter + - OrderedSet (~> 5.0) + - flutter_system_proxy (0.0.1): + - Flutter + - OrderedSet (5.0.0) + - url_launcher_ios (0.0.1): + - Flutter + - webview_flutter_wkwebview (0.0.1): + - Flutter + +DEPENDENCIES: + - Flutter (from `Flutter`) + - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) + - flutter_system_proxy (from `.symlinks/plugins/flutter_system_proxy/ios`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) + +SPEC REPOS: + trunk: + - OrderedSet + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + flutter_inappwebview_ios: + :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" + flutter_system_proxy: + :path: ".symlinks/plugins/flutter_system_proxy/ios" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + webview_flutter_wkwebview: + :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" + +SPEC CHECKSUMS: + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0 + flutter_system_proxy: 96eb97e3857a1d1bc533a6f7387a1f0dcb63d782 + OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c + url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 + webview_flutter_wkwebview: 4f3e50f7273d31e5500066ed267e3ae4309c5ae4 + +PODFILE CHECKSUM: 70d9d25280d0dd177a5f637cdb0f0b0b12c6a189 + +COCOAPODS: 1.14.3 diff --git a/packages/deriv_widgetbook/ios/Runner.xcodeproj/project.pbxproj b/packages/deriv_widgetbook/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..8a6fc4ccc --- /dev/null +++ b/packages/deriv_widgetbook/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,725 @@ +// !$*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 */; }; + 77BA0080768650B20EF6F9C1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 94A547B264B4A0EA52B0FA92 /* Pods_Runner.framework */; }; + 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 */; }; + EC10655D1A1692792112FA1C /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DF03113D0DF3C68FE8B73F20 /* Pods_RunnerTests.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 */ + 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 = ""; }; + 674D3A15401A4C2B51067628 /* 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 = ""; }; + 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 = ""; }; + 84B9EF0144D211F85F16B699 /* 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 = ""; }; + 94A547B264B4A0EA52B0FA92 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 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 = ""; }; + 9B2AEE97AF9F7F7E4CEA66C7 /* 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 = ""; }; + A3D60BC3912841F76CB6ED88 /* 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 = ""; }; + B5C5DD99A2F79B90EC613EA6 /* 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 = ""; }; + DF03113D0DF3C68FE8B73F20 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E2FB9F4EE7AB6EC1B913A561 /* 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 */ + 647AE22B78BF0B1783FE8D41 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EC10655D1A1692792112FA1C /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 77BA0080768650B20EF6F9C1 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 145FB731FC322F9E8F469FF2 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 94A547B264B4A0EA52B0FA92 /* Pods_Runner.framework */, + DF03113D0DF3C68FE8B73F20 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 5CF357B3130D4120F3F86AE2 /* Pods */ = { + isa = PBXGroup; + children = ( + A3D60BC3912841F76CB6ED88 /* Pods-Runner.debug.xcconfig */, + 9B2AEE97AF9F7F7E4CEA66C7 /* Pods-Runner.release.xcconfig */, + 674D3A15401A4C2B51067628 /* Pods-Runner.profile.xcconfig */, + E2FB9F4EE7AB6EC1B913A561 /* Pods-RunnerTests.debug.xcconfig */, + B5C5DD99A2F79B90EC613EA6 /* Pods-RunnerTests.release.xcconfig */, + 84B9EF0144D211F85F16B699 /* 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 */, + 5CF357B3130D4120F3F86AE2 /* Pods */, + 145FB731FC322F9E8F469FF2 /* 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 = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 2752D886F4C7423E1928F189 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 647AE22B78BF0B1783FE8D41 /* 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 = ( + AA8535F63ECAEE445C0BF8BA /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 09E964267AAEF627536E3B3C /* [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 = { + 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 */ + 09E964267AAEF627536E3B3C /* [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; + }; + 2752D886F4C7423E1928F189 /* [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; + }; + 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"; + }; + AA8535F63ECAEE445C0BF8BA /* [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)"; + DEVELOPMENT_TEAM = 36S5Q8S4V5; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.deriv.widgetbook; + 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 = E2FB9F4EE7AB6EC1B913A561 /* 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.deriv.widgetbook; + 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 = B5C5DD99A2F79B90EC613EA6 /* 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.deriv.widgetbook; + 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 = 84B9EF0144D211F85F16B699 /* 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.deriv.widgetbook; + 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 = 36S5Q8S4V5; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.deriv.widgetbook; + 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 = 36S5Q8S4V5; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.deriv.widgetbook; + 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/packages/deriv_widgetbook/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/deriv_widgetbook/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/packages/deriv_widgetbook/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/deriv_widgetbook/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/deriv_widgetbook/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/deriv_widgetbook/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/deriv_widgetbook/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/deriv_widgetbook/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/deriv_widgetbook/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/deriv_widgetbook/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/deriv_widgetbook/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..87131a09b --- /dev/null +++ b/packages/deriv_widgetbook/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/deriv_widgetbook/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/deriv_widgetbook/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..21a3cc14c --- /dev/null +++ b/packages/deriv_widgetbook/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/deriv_widgetbook/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/deriv_widgetbook/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/deriv_widgetbook/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/deriv_widgetbook/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/deriv_widgetbook/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/deriv_widgetbook/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/deriv_widgetbook/ios/Runner/AppDelegate.swift b/packages/deriv_widgetbook/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..70693e4a8 --- /dev/null +++ b/packages/deriv_widgetbook/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/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/packages/deriv_widgetbook/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/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 000000000..dc9ada472 Binary files /dev/null and b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000..7353c41ec Binary files /dev/null and b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000..797d452e4 Binary files /dev/null and b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000..6ed2d933e Binary files /dev/null and b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000..4cd7b0099 Binary files /dev/null and b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000..fe730945a Binary files /dev/null and b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000..321773cd8 Binary files /dev/null and b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000..797d452e4 Binary files /dev/null and b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000..502f463a9 Binary files /dev/null and b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000..0ec303439 Binary files /dev/null and b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000..0ec303439 Binary files /dev/null and b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000..e9f5fea27 Binary files /dev/null and b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000..84ac32ae7 Binary files /dev/null and b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000..8953cba09 Binary files /dev/null and b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000..0467bf12a Binary files /dev/null and b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..0bedcf2fd --- /dev/null +++ b/packages/deriv_widgetbook/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/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/deriv_widgetbook/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/deriv_widgetbook/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/packages/deriv_widgetbook/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/deriv_widgetbook/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/packages/deriv_widgetbook/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/deriv_widgetbook/ios/Runner/Base.lproj/Main.storyboard b/packages/deriv_widgetbook/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/packages/deriv_widgetbook/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/deriv_widgetbook/ios/Runner/Info.plist b/packages/deriv_widgetbook/ios/Runner/Info.plist new file mode 100644 index 000000000..835054a12 --- /dev/null +++ b/packages/deriv_widgetbook/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Deriv Widgetbook + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + deriv_widgetbook + 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/packages/deriv_widgetbook/ios/Runner/Runner-Bridging-Header.h b/packages/deriv_widgetbook/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/packages/deriv_widgetbook/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/deriv_widgetbook/ios/RunnerTests/RunnerTests.swift b/packages/deriv_widgetbook/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..86a7c3b1b --- /dev/null +++ b/packages/deriv_widgetbook/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/packages/deriv_widgetbook/lib/components/date_range_picker/date_range_picker_calender_usecase.dart b/packages/deriv_widgetbook/lib/components/date_range_picker/date_range_picker_calender_usecase.dart new file mode 100644 index 000000000..d783042b7 --- /dev/null +++ b/packages/deriv_widgetbook/lib/components/date_range_picker/date_range_picker_calender_usecase.dart @@ -0,0 +1,39 @@ +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +@UseCase( + name: 'DateRangePicker calendar mode', + type: DerivDateRangePicker, +) +Widget dateRangePickerCalendarUseCase(BuildContext context) => + DerivDateRangePicker( + context: context, + currentDate: DateTime.now(), + minAllowedDate: DateTime(2021), + maxAllowedDate: DateTime(2025, 12, 31), + cancelText: 'Cancel', + confirmText: 'Confirm', + semanticLabelCalendar: 'Calendar', + semanticLabelClose: 'Close', + semanticLabelConfirm: 'Confirm', + semanticLabelEditIcon: 'Edit', + labelSelectedDateRange: 'Selected Date Range', + toolTipCalendar: 'Calendar', + toolTipConfirm: 'Confirm', + toolTipClose: 'Close', + toolTipEdit: 'Edit', + fieldEndLabelText: 'Start', + fieldStartLabelText: 'End', + initialStartDate: context.knobs.dateTime( + label: 'Start Date', + initialValue: DateTime.now(), + start: DateTime.now(), + end: DateTime(2024, 12, 11)), + initialEndDate: context.knobs.dateTime( + label: 'End Date', + initialValue: DateTime(2024, 12, 11), + start: DateTime.now(), + end: DateTime(2024, 12, 11)), + ); diff --git a/packages/deriv_widgetbook/lib/components/expandable_bottomsheet/expandable_bottomsheet_usecase.dart b/packages/deriv_widgetbook/lib/components/expandable_bottomsheet/expandable_bottomsheet_usecase.dart new file mode 100644 index 000000000..2a1a3bf25 --- /dev/null +++ b/packages/deriv_widgetbook/lib/components/expandable_bottomsheet/expandable_bottomsheet_usecase.dart @@ -0,0 +1,28 @@ +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +@UseCase( + name: 'Default', + type: ExpandableBottomSheet, +) +Widget expandableBottomSheetDefaultUseCase(BuildContext context) => const Align( + alignment: Alignment.bottomCenter, + child: ExpandableBottomSheet( + labelContractDetails: 'Test Label Contract Details', + upperContent: Padding( + padding: EdgeInsets.symmetric(vertical: 40), + child: Text( + 'Expand Me', + style: TextStyle(fontSize: 24), + ), + ), + lowerContent: Padding( + padding: EdgeInsets.symmetric(horizontal: 24), + child: Text( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + style: TextStyle(fontSize: 24), + ), + ), + ), + ); diff --git a/packages/deriv_widgetbook/lib/components/grouped_list_view/grouped_list_view_usecase.dart b/packages/deriv_widgetbook/lib/components/grouped_list_view/grouped_list_view_usecase.dart new file mode 100644 index 000000000..b95185be1 --- /dev/null +++ b/packages/deriv_widgetbook/lib/components/grouped_list_view/grouped_list_view_usecase.dart @@ -0,0 +1,48 @@ +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +@UseCase( + name: 'Grouped List View', + type: GroupedListView, +) +Widget groupedListViewUseCase(BuildContext context) => + GroupedListView( + items: listItems, + groupBy: (dynamic element) => element['group'], + groupBuilder: (String value) => Container( + padding: const EdgeInsets.all(8), + child: Text(value), + ), + itemBuilder: (dynamic element) => Container( + padding: const EdgeInsets.all(8), + child: Text(element['name']), + ), + hasStickyHeader: true, + ); + +final List> listItems = >[ + {'name': 'Item 02', 'group': 'Group 01'}, + {'name': 'Item 03', 'group': 'Group 02'}, + {'name': 'Item 04', 'group': 'Group 02'}, + {'name': 'Item 05', 'group': 'Group 02'}, + {'name': 'Item 06', 'group': 'Group 03'}, + {'name': 'Item 07', 'group': 'Group 02'}, + {'name': 'Item 08', 'group': 'Group 02'}, + {'name': 'Item 09', 'group': 'Group 03'}, + {'name': 'Item 10', 'group': 'Group 02'}, + {'name': 'Item 11', 'group': 'Group 03'}, + {'name': 'Item 12', 'group': 'Group 01'}, + {'name': 'Item 13', 'group': 'Group 01'}, + {'name': 'Item 14', 'group': 'Group 02'}, + {'name': 'Item 15', 'group': 'Group 02'}, + {'name': 'Item 16', 'group': 'Group 02'}, + {'name': 'Item 17', 'group': 'Group 03'}, + {'name': 'Item 18', 'group': 'Group 02'}, + {'name': 'Item 19', 'group': 'Group 03'}, + {'name': 'Item 20', 'group': 'Group 02'}, + {'name': 'Item 21', 'group': 'Group 03'}, + {'name': 'Item 22', 'group': 'Group 01'}, + {'name': 'Item 23', 'group': 'Group 01'}, + {'name': 'Item 24', 'group': 'Group 02'}, +]; diff --git a/packages/deriv_widgetbook/lib/components/language_selector/language_bottomsheet_usecase.dart b/packages/deriv_widgetbook/lib/components/language_selector/language_bottomsheet_usecase.dart new file mode 100644 index 000000000..9d1602f81 --- /dev/null +++ b/packages/deriv_widgetbook/lib/components/language_selector/language_bottomsheet_usecase.dart @@ -0,0 +1,39 @@ +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +@UseCase( + name: 'Language Selector Bottom Sheet', + type: LanguageItemList, +) +Widget expandableBottomSheetDefaultUseCase(BuildContext context) => Align( + alignment: Alignment.bottomCenter, + child: ExpandableBottomSheet( + labelContractDetails: 'Select language', + title: 'Language', + upperContent: LanguageItemList( + selectedItem: LanguageModel( + name: 'English', + code: 'en', + flag: 'assets/icons/ic_flag_en.png', + ), + items: [ + LanguageModel( + name: 'English', + code: 'en', + flag: 'assets/icons/ic_flag_en.png', + ), + LanguageModel( + name: 'Indonesia', + code: 'id', + flag: 'assets/icons/ic_flag_id.png', + ), + LanguageModel( + name: 'Chinese', + code: 'zh', + flag: 'assets/icons/ic_flag_zh.png', + ), + ], + onLanguageSelected: (LanguageModel item) {}), + ), + ); diff --git a/packages/deriv_widgetbook/lib/components/language_selector/language_selector_usecase.dart b/packages/deriv_widgetbook/lib/components/language_selector/language_selector_usecase.dart new file mode 100644 index 000000000..ddfb92356 --- /dev/null +++ b/packages/deriv_widgetbook/lib/components/language_selector/language_selector_usecase.dart @@ -0,0 +1,16 @@ +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +@UseCase( + name: 'Language Selector', + type: LanguageSelectorWidget, +) +Widget expandableBottomSheetDefaultUseCase(BuildContext context) => + LanguageSelectorWidget( + languageItem: LanguageModel( + name: 'English', + code: 'en', + flag: 'assets/icons/ic_flag_en.png', + ), + onPressed: () async {}); diff --git a/packages/deriv_widgetbook/lib/components/numpad/numpad_with_exchanger_usecase.dart b/packages/deriv_widgetbook/lib/components/numpad/numpad_with_exchanger_usecase.dart new file mode 100644 index 000000000..94bf6a575 --- /dev/null +++ b/packages/deriv_widgetbook/lib/components/numpad/numpad_with_exchanger_usecase.dart @@ -0,0 +1,81 @@ +import 'dart:async'; + +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +@UseCase( + name: 'With Currency Exchanger', + type: NumberPad, +) +Widget numpadWithCurrencyExchangerUseCase(BuildContext context) { + final StreamController controller = + StreamController.broadcast(); + return Align( + alignment: Alignment.bottomCenter, + child: NumberPad.withCurrencyExchanger( + onClose: (NumberPadWidgetType type, NumberPadCloseType closeType, + NumberPadData result) {}, + title: 'Amount', + exchangeRatesStream: controller.stream, + initialExchangeRate: ExchangeRateModel( + baseCurrency: 'BTC', + targetCurrency: 'USD', + exchangeRate: 42800, + ), + primaryCurrency: CurrencyDetail(0.123, 'BTC'), + label: NumberPadLabel( + onValidate: (String value) { + if (value.isEmpty) { + return NumpadValidationText( + enableActionButton: false, + text: RichText( + text: TextSpan( + text: 'Please enter an amount', + style: context.theme.textStyle( + textStyle: TextStyles.captionBold, + color: context.theme.colors.blue, + ), + ), + ), + ); + } + if (double.parse(value) > 50) { + return NumpadValidationText( + enableActionButton: false, + text: RichText( + textAlign: TextAlign.center, + text: TextSpan( + text: + 'You have reached the daily transfer limit of [50,000.00] USD between your USD Wallet and Deriv X.', + style: context.theme.textStyle( + textStyle: TextStyles.captionBold, + color: context.theme.colors.blue, + ), + ), + ), + ); + } else { + return NumpadValidationText( + text: RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'This looks good! Hehe', + style: context.theme.textStyle( + textStyle: TextStyles.captionBold, + color: context.theme.colors.disabled, + ), + ), + ], + ), + ), + ); + } + }, + actionOK: 'OK', + ), + ), + ); +} diff --git a/packages/deriv_widgetbook/lib/components/numpad/numpad_without_exchanger_usecase.dart b/packages/deriv_widgetbook/lib/components/numpad/numpad_without_exchanger_usecase.dart new file mode 100644 index 000000000..b4801bdc5 --- /dev/null +++ b/packages/deriv_widgetbook/lib/components/numpad/numpad_without_exchanger_usecase.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:intl/intl.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +@UseCase( + name: 'Without Currency Exchanger', + type: NumberPad, +) +Widget numpadUseCase(BuildContext context) => Align( + alignment: Alignment.bottomCenter, + child: NumberPad( + numberPadType: NumberPadWidgetType.singleInput, + currency: 'USD', + firstInputTitle: 'Amount', + formatter: NumberFormat.decimalPattern(), + firstInputInitialValue: 20, + firstInputMaximumValue: 100, + firstInputMinimumValue: 10, + label: NumberPadLabel( + semanticNumberPadBottomSheetHandle: + 'semanticNumberPadBottomSheetHandle', + warnValueCantBeLessThan: + (Object input, Object minValue, Object currency) => + '$input can\'t be less than $minValue $currency', + warnValueCantBeGreaterThan: + (Object input, Object maxValue, Object currency) => + '$input can\'t be greater than $maxValue $currency', + warnDoubleInputValueCantBeLessThan: (Object input, Object minValue, + Object currency) => + 'Invalid $input. $input can\'t be less than $minValue $currency', + warnDoubleInputValueCantBeGreaterThan: (Object input, Object maxValue, + Object currency) => + 'Invalid $input. $input can\'t be greater than $maxValue $currency', + warnValueShouldBeInRange: (Object input, Object minValue, + Object currency, Object maxValue) => + '$input between $minValue $currency and $maxValue $currency', + actionOK: 'OK', + ), + ), + ); diff --git a/packages/deriv_widgetbook/lib/integrations/restore_page_integration.dart b/packages/deriv_widgetbook/lib/integrations/restore_page_integration.dart new file mode 100644 index 000000000..504985a21 --- /dev/null +++ b/packages/deriv_widgetbook/lib/integrations/restore_page_integration.dart @@ -0,0 +1,60 @@ +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:widgetbook/widgetbook.dart'; + +class RestoreLastPageIntegration extends WidgetbookIntegration { + RestoreLastPageIntegration({ + required SharedPreferences preferences, + }) : _db = preferences; + + final SharedPreferences _db; + + /// The last state (URL) that was visited. Used for initial restoration + String get lastState => _db.getString('lastState') ?? '/'; + + /// The last path (Component + Story part of the URL) + String? _lastPath; + + /// The last params without the args + Map? _lastParams; + + @override + void onChange(WidgetbookState state) { + // On page changed + if (_lastPath != null && state.path != null && _lastPath != state.path) { + // Restore last arg state + if (_db.containsKey(state.path!)) { + state.queryParams = { + ...state.queryParams, + 'args': _db.getString(state.path!)!, + }; + } + + // Maintain non-args (ie theme, textScaleFactor, etc.) + if (_lastParams != null) { + state.queryParams = { + ..._lastParams!, + if (state.queryParams.containsKey('args')) + 'args': state.queryParams['args']!, + }; + } + } + + // Save current arg state + if (state.path != null) { + if (state.queryParams.containsKey('args')) { + _db.setString( + state.path!, + state.queryParams['args']!, + ); + } else { + _db.remove(state.path!); + } + } + + _lastPath = state.path; + _lastParams = {...state.queryParams}..remove('args'); + + // The part that enables initial state restoration + _db.setString('lastState', state.uri.toString()); + } +} diff --git a/packages/deriv_widgetbook/lib/main.dart b/packages/deriv_widgetbook/lib/main.dart new file mode 100644 index 000000000..3711554b3 --- /dev/null +++ b/packages/deriv_widgetbook/lib/main.dart @@ -0,0 +1,83 @@ +import 'package:deriv_theme/deriv_theme.dart'; + +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:deriv_widgetbook/integrations/restore_page_integration.dart'; +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; +import 'main.directories.g.dart'; + +@App() +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + final SharedPreferences preferences = await SharedPreferences.getInstance(); + runApp(MyApp( + preferences: preferences, + )); +} + +@App() +class MyApp extends StatelessWidget { + const MyApp({required this.preferences, super.key}); + + final SharedPreferences preferences; + + @override + Widget build(BuildContext context) => Widgetbook.material( + directories: directories, + addons: [ + DeviceFrameAddon( + devices: Devices.all, + initialDevice: Devices.android.bigPhone, + ), + ThemeAddon( + themes: >[ + const WidgetbookTheme( + name: 'Dark', + data: ThemeMode.dark, + ), + ], + themeBuilder: + (BuildContext context, ThemeMode themeMode, Widget child) => + DerivThemeProvider.builder( + initialTheme: themeMode, + builder: (BuildContext context) { + WidgetsBinding.instance.addPostFrameCallback((_) { + DerivThemeProvider.changeThemeMode(context, themeMode); + }); + return MaterialApp( + supportedLocales: DateRangeLocalizations.supportedLocales, + localizationsDelegates: const >[ + DefaultWidgetsLocalizations.delegate, + DefaultMaterialLocalizations.delegate, + DateRangeLocalizations.delegate, + ], + theme: context.themeData, + home: Scaffold( + backgroundColor: context.theme.colors.primary, + body: SafeArea( + child: child, + )), + ); + }, + ), + ), + AlignmentAddon(), + LocalizationAddon( + locales: DateRangeLocalizations.supportedLocales, + localizationsDelegates: >[ + DefaultWidgetsLocalizations.delegate, + DefaultMaterialLocalizations.delegate, + DateRangeLocalizations.delegate, + ]), + TextScaleAddon(scales: [1, 2, 3]), + InspectorAddon(), + ], + integrations: [ + RestoreLastPageIntegration(preferences: preferences), + ], + ); +} diff --git a/packages/deriv_widgetbook/lib/main.directories.g.dart b/packages/deriv_widgetbook/lib/main.directories.g.dart new file mode 100644 index 000000000..4ca596c4a --- /dev/null +++ b/packages/deriv_widgetbook/lib/main.directories.g.dart @@ -0,0 +1,278 @@ +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_import, prefer_relative_imports, directives_ordering + +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// AppGenerator +// ************************************************************************** + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:deriv_widgetbook/components/date_range_picker/date_range_picker_calender_usecase.dart' + as _i2; +import 'package:deriv_widgetbook/components/expandable_bottomsheet/expandable_bottomsheet_usecase.dart' + as _i3; +import 'package:deriv_widgetbook/components/grouped_list_view/grouped_list_view_usecase.dart' + as _i4; +import 'package:deriv_widgetbook/components/language_selector/language_bottomsheet_usecase.dart' + as _i5; +import 'package:deriv_widgetbook/components/language_selector/language_selector_usecase.dart' + as _i6; +import 'package:deriv_widgetbook/components/numpad/numpad_with_exchanger_usecase.dart' + as _i7; +import 'package:deriv_widgetbook/components/numpad/numpad_without_exchanger_usecase.dart' + as _i8; +import 'package:deriv_widgetbook/theme/colors_usecases.dart' as _i9; +import 'package:deriv_widgetbook/widgets/buttons/primary_button_usecases.dart' + as _i16; +import 'package:deriv_widgetbook/widgets/buttons/secondary_button_usecases.dart' + as _i17; +import 'package:deriv_widgetbook/widgets/checkbox/checkbox_usecases.dart' + as _i12; +import 'package:deriv_widgetbook/widgets/dialogs/custom_alert_dailog_usecases.dart' + as _i11; +import 'package:deriv_widgetbook/widgets/dialogs/popup_alert_dialog_usecases.dart' + as _i15; +import 'package:deriv_widgetbook/widgets/full_screen_message/full_screen_message_usecases.dart' + as _i13; +import 'package:deriv_widgetbook/widgets/loading_indicator/loading_indicator_usecases.dart' + as _i14; +import 'package:deriv_widgetbook/widgets/text_fields/base_text_field_usecases.dart' + as _i10; +import 'package:widgetbook/widgetbook.dart' as _i1; + +final directories = <_i1.WidgetbookNode>[ + _i1.WidgetbookFolder( + name: 'components', + children: [ + _i1.WidgetbookFolder( + name: 'date_range_picker', + children: [ + _i1.WidgetbookFolder( + name: 'widgets', + children: [ + _i1.WidgetbookLeafComponent( + name: 'DerivDateRangePicker', + useCase: _i1.WidgetbookUseCase( + name: 'DateRangePicker calendar mode', + builder: _i2.dateRangePickerCalendarUseCase, + ), + ) + ], + ) + ], + ), + _i1.WidgetbookFolder( + name: 'expandable_bottom_sheet', + children: [ + _i1.WidgetbookFolder( + name: 'widgets', + children: [ + _i1.WidgetbookLeafComponent( + name: 'ExpandableBottomSheet', + useCase: _i1.WidgetbookUseCase( + name: 'Default', + builder: _i3.expandableBottomSheetDefaultUseCase, + ), + ) + ], + ) + ], + ), + _i1.WidgetbookFolder( + name: 'grouped_list_view', + children: [ + _i1.WidgetbookFolder( + name: 'widgets', + children: [ + _i1.WidgetbookLeafComponent( + name: 'GroupedListView', + useCase: _i1.WidgetbookUseCase( + name: 'Grouped List View', + builder: _i4.groupedListViewUseCase, + ), + ) + ], + ) + ], + ), + _i1.WidgetbookFolder( + name: 'language_selector', + children: [ + _i1.WidgetbookFolder( + name: 'widgets', + children: [ + _i1.WidgetbookLeafComponent( + name: 'LanguageItemList', + useCase: _i1.WidgetbookUseCase( + name: 'Language Selector', + builder: _i5.expandableBottomSheetDefaultUseCase, + ), + ), + _i1.WidgetbookLeafComponent( + name: 'LanguageSelectorWidget', + useCase: _i1.WidgetbookUseCase( + name: 'Language Selector', + builder: _i6.expandableBottomSheetDefaultUseCase, + ), + ), + ], + ) + ], + ), + _i1.WidgetbookFolder( + name: 'numpad', + children: [ + _i1.WidgetbookFolder( + name: 'widgets', + children: [ + _i1.WidgetbookComponent( + name: 'NumberPad', + useCases: [ + _i1.WidgetbookUseCase( + name: 'With Currency Exchanger', + builder: _i7.numpadWithCurrencyExchangerUseCase, + ), + _i1.WidgetbookUseCase( + name: 'Without Currency Exchanger', + builder: _i8.numpadUseCase, + ), + ], + ) + ], + ) + ], + ), + ], + ), + _i1.WidgetbookFolder( + name: 'theme', + children: [ + _i1.WidgetbookLeafComponent( + name: 'ColorLabel', + useCase: _i1.WidgetbookUseCase( + name: 'General Colors', + builder: _i9.buildColorUseCase, + ), + ) + ], + ), + _i1.WidgetbookFolder( + name: 'widgets', + children: [ + _i1.WidgetbookComponent( + name: 'BaseTextField', + useCases: [ + _i1.WidgetbookUseCase( + name: 'Disabled', + builder: _i10.baseTextFieldDisabledUseCase, + ), + _i1.WidgetbookUseCase( + name: 'Enabled', + builder: _i10.baseTextFieldEnabledUseCase, + ), + _i1.WidgetbookUseCase( + name: 'Error', + builder: _i10.baseTextFieldErrorUseCase, + ), + ], + ), + _i1.WidgetbookComponent( + name: 'CustomAlertDialog', + useCases: [ + _i1.WidgetbookUseCase( + name: 'No button', + builder: _i11.customAlertDialogNoButtonUseCase, + ), + _i1.WidgetbookUseCase( + name: 'One button', + builder: _i11.customAlertDialogOneButtonUseCase, + ), + _i1.WidgetbookUseCase( + name: 'Two buttons', + builder: _i11.customAlertDialogUseCase, + ), + ], + ), + _i1.WidgetbookComponent( + name: 'CustomCheckbox', + useCases: [ + _i1.WidgetbookUseCase( + name: 'checked', + builder: _i12.checkboxCheckedUsecase, + ), + _i1.WidgetbookUseCase( + name: 'unchecked', + builder: _i12.checkboxUncheckedUsecase, + ), + ], + ), + _i1.WidgetbookComponent( + name: 'FullscreenMessage', + useCases: [ + _i1.WidgetbookUseCase( + name: 'No button', + builder: _i13.fullScreenMessageUseCase, + ), + _i1.WidgetbookUseCase( + name: 'With button', + builder: _i13.fullScreenMessageWithButtonUseCase, + ), + ], + ), + _i1.WidgetbookComponent( + name: 'LoadingIndicator', + useCases: [ + _i1.WidgetbookUseCase( + name: 'Circular', + builder: _i14.loadingIndicatorUseCase, + ), + _i1.WidgetbookUseCase( + name: 'Linear', + builder: _i14.linearLoadingIndicatorUseCase, + ), + ], + ), + _i1.WidgetbookComponent( + name: 'PopupAlertDialog', + useCases: [ + _i1.WidgetbookUseCase( + name: 'With checkbox', + builder: _i15.popupAlertDialogUseCase, + ), + _i1.WidgetbookUseCase( + name: 'Without checkbox', + builder: _i15.popupAlertDialogWithoutCheckboxUseCase, + ), + ], + ), + _i1.WidgetbookComponent( + name: 'PrimaryButton', + useCases: [ + _i1.WidgetbookUseCase( + name: 'Disabled', + builder: _i16.primaryButtonDisabledUseCase, + ), + _i1.WidgetbookUseCase( + name: 'Enabled', + builder: _i16.primaryButtonEnabledUseCase, + ), + ], + ), + _i1.WidgetbookComponent( + name: 'SecondaryButton', + useCases: [ + _i1.WidgetbookUseCase( + name: 'Disabled', + builder: _i17.secondaryButtonDisabledUseCase, + ), + _i1.WidgetbookUseCase( + name: 'Enabled', + builder: _i17.secondaryButtonEnabledUseCase, + ), + ], + ), + ], + ), +]; diff --git a/packages/deriv_widgetbook/lib/theme/colors_usecases.dart b/packages/deriv_widgetbook/lib/theme/colors_usecases.dart new file mode 100644 index 000000000..6f92aeacb --- /dev/null +++ b/packages/deriv_widgetbook/lib/theme/colors_usecases.dart @@ -0,0 +1,112 @@ +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; +import 'group.dart'; + +@UseCase( + name: 'General Colors', + type: ColorLabel, +) +Widget buildColorUseCase(BuildContext context) => WidgetbookGroup( + label: 'General Colors', + children: [ + ColorLabel( + label: 'Primary', + color: context.theme.colors.primary, + ), + ColorLabel( + label: 'Secondary', + color: context.theme.colors.secondary, + ), + ColorLabel( + label: 'Coral', + color: context.theme.colors.coral, + ), + ColorLabel( + label: 'Blue', + color: context.theme.colors.blue, + ), + ColorLabel( + label: 'Orange', + color: context.theme.colors.orange, + ), + ColorLabel( + label: 'Night', + color: context.theme.colors.night, + ), + ColorLabel( + label: 'Prominent', + color: context.theme.colors.prominent, + ), + ColorLabel( + label: 'General', + color: context.theme.colors.general, + ), + ColorLabel( + label: 'LessProminent', + color: context.theme.colors.lessProminent, + ), + ColorLabel( + label: 'Disabled', + color: context.theme.colors.disabled, + ), + ColorLabel( + label: 'Active', + color: context.theme.colors.active, + ), + ColorLabel( + label: 'Hover', + color: context.theme.colors.hover, + ), + ColorLabel( + label: 'Information', + color: context.theme.colors.information, + ), + ColorLabel( + label: 'Success', + color: context.theme.colors.success, + ), + ColorLabel( + label: 'Warning', + color: context.theme.colors.warning, + ), + ColorLabel( + label: 'Danger', + color: context.theme.colors.danger, + ), + ], + ); + +class ColorLabel extends StatelessWidget { + const ColorLabel({ + required this.label, + required this.color, + super.key, + }); + + final String label; + final Color color; + + @override + Widget build(BuildContext context) => Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + children: [ + Container( + width: 24, + height: 24, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: Colors.white, + width: 2, + ), + ), + ), + const SizedBox(width: 16), + Text(label), + ], + ), + ); +} diff --git a/packages/deriv_widgetbook/lib/theme/group.dart b/packages/deriv_widgetbook/lib/theme/group.dart new file mode 100644 index 000000000..429e737e0 --- /dev/null +++ b/packages/deriv_widgetbook/lib/theme/group.dart @@ -0,0 +1,32 @@ +import 'package:flutter/widgets.dart'; + +class WidgetbookGroup extends StatelessWidget { + const WidgetbookGroup({ + required this.label, + required this.children, + super.key, + }); + + final String label; + final List children; + + @override + Widget build(BuildContext context) => Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label), + const SizedBox( + height: 16, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: children, + ), + ), + ], + ), + ); +} diff --git a/packages/deriv_widgetbook/lib/widgets/buttons/primary_button_usecases.dart b/packages/deriv_widgetbook/lib/widgets/buttons/primary_button_usecases.dart new file mode 100644 index 000000000..95484b17a --- /dev/null +++ b/packages/deriv_widgetbook/lib/widgets/buttons/primary_button_usecases.dart @@ -0,0 +1,43 @@ +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +@UseCase( + name: 'Enabled', + type: PrimaryButton, +) +Widget primaryButtonEnabledUseCase(BuildContext context) => PrimaryButton( + onPressed: () {}, + child: Text( + context.knobs.string( + label: 'Button Text', + initialValue: 'Text', + ), + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent, + ), + ), + ); + +@UseCase( + name: 'Disabled', + type: PrimaryButton, +) +Widget primaryButtonDisabledUseCase(BuildContext context) => PrimaryButton( + onPressed: () {}, + isEnabled: false, + child: Text( + context.knobs.string( + label: 'Button Text', + initialValue: 'Text', + ), + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent + .withOpacity(getOpacity(isEnabled: false)), + ), + ), + ); diff --git a/packages/deriv_widgetbook/lib/widgets/buttons/secondary_button_usecases.dart b/packages/deriv_widgetbook/lib/widgets/buttons/secondary_button_usecases.dart new file mode 100644 index 000000000..99f0dc025 --- /dev/null +++ b/packages/deriv_widgetbook/lib/widgets/buttons/secondary_button_usecases.dart @@ -0,0 +1,43 @@ +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +@UseCase( + name: 'Enabled', + type: SecondaryButton, +) +Widget secondaryButtonEnabledUseCase(BuildContext context) => SecondaryButton( + onPressed: () {}, + child: Text( + context.knobs.string( + label: 'Button Text', + initialValue: 'Text', + ), + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent, + ), + ), + ); + +@UseCase( + name: 'Disabled', + type: SecondaryButton, +) +Widget secondaryButtonDisabledUseCase(BuildContext context) => SecondaryButton( + onPressed: () {}, + isEnabled: false, + child: Text( + context.knobs.string( + label: 'Button Text', + initialValue: 'Text', + ), + style: context.theme.textStyle( + textStyle: TextStyles.body2, + color: context.theme.colors.prominent + .withOpacity(getOpacity(isEnabled: false)), + ), + ), + ); diff --git a/packages/deriv_widgetbook/lib/widgets/checkbox/checkbox_usecases.dart b/packages/deriv_widgetbook/lib/widgets/checkbox/checkbox_usecases.dart new file mode 100644 index 000000000..d62d71add --- /dev/null +++ b/packages/deriv_widgetbook/lib/widgets/checkbox/checkbox_usecases.dart @@ -0,0 +1,30 @@ +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +@UseCase( + name: 'checked', + type: CustomCheckbox, +) +Widget checkboxCheckedUsecase(BuildContext context) => CustomCheckbox( + padding: const EdgeInsets.all( + ThemeProvider.margin24, + ), + message: context.knobs + .string(label: 'checkbox message', initialValue: 'Checkbox'), + value: true, + ); + +@UseCase( + name: 'unchecked', + type: CustomCheckbox, +) +Widget checkboxUncheckedUsecase(BuildContext context) => CustomCheckbox( + padding: const EdgeInsets.all( + ThemeProvider.margin24, + ), + message: context.knobs + .string(label: 'checkbox message', initialValue: 'Checkbox'), + ); diff --git a/packages/deriv_widgetbook/lib/widgets/dialogs/custom_alert_dailog_usecases.dart b/packages/deriv_widgetbook/lib/widgets/dialogs/custom_alert_dailog_usecases.dart new file mode 100644 index 000000000..37460b578 --- /dev/null +++ b/packages/deriv_widgetbook/lib/widgets/dialogs/custom_alert_dailog_usecases.dart @@ -0,0 +1,103 @@ +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +@UseCase( + name: 'Two buttons', + type: CustomAlertDialog, +) +Widget customAlertDialogUseCase(BuildContext context) => CustomAlertDialog( + title: Text( + context.knobs.string( + label: 'Title', + initialValue: 'Title', + ), + style: context.theme.textStyle( + textStyle: TextStyles.title, + color: context.theme.colors.prominent, + ), + ), + content: Text( + context.knobs.string( + label: 'Content', + initialValue: 'This is the default content.', + ), + style: context.theme.textStyle( + textStyle: TextStyles.body1, + color: context.theme.colors.prominent, + ), + ), + negativeActionLabel: context.knobs.string( + label: 'Negative Action Label', + initialValue: 'Cancel', + ), + positiveActionLabel: context.knobs.string( + label: 'Positive Action Label', + initialValue: 'Confirm', + ), + onNegativeActionPressed: () {}, + onPositiveActionPressed: () {}, + ); + +@UseCase( + name: 'One button', + type: CustomAlertDialog, +) +Widget customAlertDialogOneButtonUseCase(BuildContext context) => + CustomAlertDialog( + title: Text( + context.knobs.string( + label: 'Title', + initialValue: 'Title', + ), + style: context.theme.textStyle( + textStyle: TextStyles.title, + color: context.theme.colors.prominent, + ), + ), + content: Text( + context.knobs.string( + label: 'Content', + initialValue: 'This is the default content.', + ), + style: context.theme.textStyle( + textStyle: TextStyles.body1, + color: context.theme.colors.prominent, + ), + ), + positiveActionLabel: context.knobs.string( + label: 'Positive Action Label', + initialValue: 'Confirm', + ), + onPositiveActionPressed: () {}, + ); + +@UseCase( + name: 'No button', + type: CustomAlertDialog, +) +Widget customAlertDialogNoButtonUseCase(BuildContext context) => + CustomAlertDialog( + title: Text( + context.knobs.string( + label: 'Title', + initialValue: 'Title', + ), + style: context.theme.textStyle( + textStyle: TextStyles.title, + color: context.theme.colors.prominent, + ), + ), + content: Text( + context.knobs.string( + label: 'Content', + initialValue: 'This is the default content.', + ), + style: context.theme.textStyle( + textStyle: TextStyles.body1, + color: context.theme.colors.prominent, + ), + ), + ); diff --git a/packages/deriv_widgetbook/lib/widgets/dialogs/popup_alert_dialog_usecases.dart b/packages/deriv_widgetbook/lib/widgets/dialogs/popup_alert_dialog_usecases.dart new file mode 100644 index 000000000..5b575794a --- /dev/null +++ b/packages/deriv_widgetbook/lib/widgets/dialogs/popup_alert_dialog_usecases.dart @@ -0,0 +1,79 @@ +import 'package:deriv_theme/deriv_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +@UseCase( + name: 'With checkbox', + type: PopupAlertDialog, +) +Widget popupAlertDialogUseCase(BuildContext context) => PopupAlertDialog( + title: context.knobs.string( + label: 'Title', + initialValue: 'Title', + ), + content: Text( + context.knobs.string( + label: 'Content', + initialValue: 'This is the default content.', + ), + style: context.theme.textStyle( + textStyle: TextStyles.body1, + color: context.theme.colors.general, + ), + ), + checkBoxValue: true, + checkboxMessage: context.knobs.string( + label: 'Checkbox Message', + initialValue: 'This is the default checkbox message.', + ), + isCheckboxVisible: true, + negativeButtonLabel: context.knobs.string( + label: 'Negative Button Label', + initialValue: 'Cancel', + ), + positiveButtonLabel: context.knobs.string( + label: 'Positive Action Label', + initialValue: 'Confirm', + ), + onPositiveActionPressed: () {}, + onNegativeActionPressed: () {}, + ); + +@UseCase( + name: 'Without checkbox', + type: PopupAlertDialog, +) +Widget popupAlertDialogWithoutCheckboxUseCase(BuildContext context) => + PopupAlertDialog( + title: context.knobs.string( + label: 'Title', + initialValue: 'Title', + ), + content: Text( + context.knobs.string( + label: 'Content', + initialValue: 'This is the default content.', + ), + style: context.theme.textStyle( + textStyle: TextStyles.body1, + color: context.theme.colors.general, + ), + ), + checkBoxValue: false, + checkboxMessage: context.knobs.string( + label: 'Checkbox Message', + initialValue: 'This is the default checkbox message.', + ), + negativeButtonLabel: context.knobs.string( + label: 'Negative Button Label', + initialValue: 'Cancel', + ), + positiveButtonLabel: context.knobs.string( + label: 'Positive Action Label', + initialValue: 'Confirm', + ), + onPositiveActionPressed: () {}, + onNegativeActionPressed: () {}, + ); diff --git a/packages/deriv_widgetbook/lib/widgets/full_screen_message/full_screen_message_usecases.dart b/packages/deriv_widgetbook/lib/widgets/full_screen_message/full_screen_message_usecases.dart new file mode 100644 index 000000000..6f0a59e88 --- /dev/null +++ b/packages/deriv_widgetbook/lib/widgets/full_screen_message/full_screen_message_usecases.dart @@ -0,0 +1,46 @@ +import 'package:deriv_ui/models/custom_icon_data_model.dart'; +import 'package:flutter/material.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +@UseCase( + name: 'No button', + type: FullscreenMessage, +) +Widget fullScreenMessageUseCase(BuildContext context) => FullscreenMessage( + title: context.knobs.string( + label: 'Title', + initialValue: 'Title', + ), + iconData: CustomIconData( + icon: '/icons/ic_no_connection.svg', + ), + description: context.knobs.string( + label: 'Description', + initialValue: 'This is the default content.', + ), + ); + +@UseCase( + name: 'With button', + type: FullscreenMessage, +) +Widget fullScreenMessageWithButtonUseCase(BuildContext context) => + FullscreenMessage( + title: context.knobs.string( + label: 'Title', + initialValue: 'Title', + ), + description: context.knobs.string( + label: 'Description', + initialValue: 'This is the default content.', + ), + iconData: CustomIconData( + icon: '/icons/ic_no_connection.svg', + ), + actionLabel: context.knobs.string( + label: 'Action Label', + initialValue: 'Confirm', + ), + ); diff --git a/packages/deriv_widgetbook/lib/widgets/loading_indicator/loading_indicator_usecases.dart b/packages/deriv_widgetbook/lib/widgets/loading_indicator/loading_indicator_usecases.dart new file mode 100644 index 000000000..4f0cb95f4 --- /dev/null +++ b/packages/deriv_widgetbook/lib/widgets/loading_indicator/loading_indicator_usecases.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +@UseCase( + name: 'Circular', + type: LoadingIndicator, +) +Widget loadingIndicatorUseCase(BuildContext context) => + const LoadingIndicator(); + +@UseCase( + name: 'Linear', + type: LoadingIndicator, +) +Widget linearLoadingIndicatorUseCase(BuildContext context) => + const LoadingIndicator( + isCircular: false, + ); diff --git a/packages/deriv_widgetbook/lib/widgets/text_fields/base_text_field_usecases.dart b/packages/deriv_widgetbook/lib/widgets/text_fields/base_text_field_usecases.dart new file mode 100644 index 000000000..787d6dfd7 --- /dev/null +++ b/packages/deriv_widgetbook/lib/widgets/text_fields/base_text_field_usecases.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:deriv_ui/deriv_ui.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart'; + +/// Created to preserve state while changing knobs. +final GlobalKey> enabledTextFieldKey = GlobalKey(); +final GlobalKey> errorTextFieldKey = GlobalKey(); + +@UseCase( + name: 'Enabled', + type: BaseTextField, +) +Widget baseTextFieldEnabledUseCase(BuildContext context) => BaseTextField( + key: enabledTextFieldKey, + autofocus: true, + onChanged: (String value) {}, + labelText: 'Label', + ); + +@UseCase( + name: 'Disabled', + type: BaseTextField, +) +Widget baseTextFieldDisabledUseCase(BuildContext context) => BaseTextField( + onChanged: (String value) {}, + labelText: 'Label', + enabled: false, + ); + +@UseCase( + name: 'Error', + type: BaseTextField, +) +Widget baseTextFieldErrorUseCase(BuildContext context) => BaseTextField( + key: errorTextFieldKey, + autofocus: true, + onChanged: (String value) {}, + labelText: 'Enter something for error', + validator: (String? p0) => 'This field is required', + ); diff --git a/packages/deriv_widgetbook/pubspec.yaml b/packages/deriv_widgetbook/pubspec.yaml new file mode 100644 index 000000000..1cd435718 --- /dev/null +++ b/packages/deriv_widgetbook/pubspec.yaml @@ -0,0 +1,39 @@ +name: deriv_widgetbook +description: Storybook for Deriv UI widgets and components. +publish_to: "none" +version: 0.0.2+35 + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + + widgetbook_annotation: ^3.1.0 + widgetbook: ^3.7.1 + deriv_ui: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_ui + ref: deriv_ui-v0.1.1 + deriv_theme: + git: + url: git@github.com:regentmarkets/flutter-deriv-packages.git + path: packages/deriv_theme + ref: deriv_theme-v2.8.0 + intl: ^0.19.0 + shared_preferences: ^2.2.2 + +dev_dependencies: + flutter_test: + sdk: flutter + + flutter_lints: ^3.0.1 + widgetbook_generator: ^3.7.0 + build_runner: ^2.4.8 + +flutter: + uses-material-design: true + assets: + - assets/icons/ diff --git a/packages/deriv_widgetbook/web/favicon.png b/packages/deriv_widgetbook/web/favicon.png new file mode 100644 index 000000000..8aaa46ac1 Binary files /dev/null and b/packages/deriv_widgetbook/web/favicon.png differ diff --git a/packages/deriv_widgetbook/web/icons/Icon-192.png b/packages/deriv_widgetbook/web/icons/Icon-192.png new file mode 100644 index 000000000..b749bfef0 Binary files /dev/null and b/packages/deriv_widgetbook/web/icons/Icon-192.png differ diff --git a/packages/deriv_widgetbook/web/icons/Icon-512.png b/packages/deriv_widgetbook/web/icons/Icon-512.png new file mode 100644 index 000000000..88cfd48df Binary files /dev/null and b/packages/deriv_widgetbook/web/icons/Icon-512.png differ diff --git a/packages/deriv_widgetbook/web/icons/Icon-maskable-192.png b/packages/deriv_widgetbook/web/icons/Icon-maskable-192.png new file mode 100644 index 000000000..eb9b4d76e Binary files /dev/null and b/packages/deriv_widgetbook/web/icons/Icon-maskable-192.png differ diff --git a/packages/deriv_widgetbook/web/icons/Icon-maskable-512.png b/packages/deriv_widgetbook/web/icons/Icon-maskable-512.png new file mode 100644 index 000000000..d69c56691 Binary files /dev/null and b/packages/deriv_widgetbook/web/icons/Icon-maskable-512.png differ diff --git a/packages/deriv_widgetbook/web/index.html b/packages/deriv_widgetbook/web/index.html new file mode 100644 index 000000000..618ba9e75 --- /dev/null +++ b/packages/deriv_widgetbook/web/index.html @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + Deriv Widgetbook + + + + + + + + + + + + diff --git a/packages/deriv_widgetbook/web/manifest.json b/packages/deriv_widgetbook/web/manifest.json new file mode 100644 index 000000000..df4130b59 --- /dev/null +++ b/packages/deriv_widgetbook/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "deriv_widgetbook", + "short_name": "deriv_widgetbook", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "Storybook for Deriv Widgets", + "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/packages/update_checker/CHANGELOG.md b/packages/update_checker/CHANGELOG.md index 7037dec39..fb903a5f7 100644 --- a/packages/update_checker/CHANGELOG.md +++ b/packages/update_checker/CHANGELOG.md @@ -1,3 +1,51 @@ +## 3.1.1 + + - **FIX**(update_checker): [DRGO-1343] handle "failed to fetch and activate remote config" error ([#871](https://github.com/regentmarkets/flutter-deriv-packages/issues/871)). ([06c68591](https://github.com/regentmarkets/flutter-deriv-packages/commit/06c68591aaa9146789d173f220761efa1eab0d50)) + +## 3.1.0 + + - **FEAT**(analytics): Add indicator events ([#846](https://github.com/regentmarkets/flutter-deriv-packages/issues/846)). ([1d30ddc9](https://github.com/regentmarkets/flutter-deriv-packages/commit/1d30ddc9c7510e280cc90fc7e1308b945d5758ed)) + +## 3.0.0 + +> Note: This release has breaking changes. + + - **BREAKING** **FIX**(deriv_app_performance): [DRGO-1247] Ramin/update dependencies ([#862](https://github.com/regentmarkets/flutter-deriv-packages/issues/862)). ([b0e7120b](https://github.com/regentmarkets/flutter-deriv-packages/commit/b0e7120bd1afc0b3244e14d0c251525005ee67c5)) + +## 2.0.0 + +> Note: This release has breaking changes. + + - **BREAKING** **FIX**(deriv_auth): [DRGO-1247] update some depedencies ([#858](https://github.com/regentmarkets/flutter-deriv-packages/issues/858)). ([6a511b39](https://github.com/regentmarkets/flutter-deriv-packages/commit/6a511b39e91b95747fe594f40d6214b0da39d2e2)) + +## 1.5.2 + + - **REFACTOR**(deriv_passkeys): Add call back to call after pass key flow finished ([#831](https://github.com/regentmarkets/flutter-deriv-packages/issues/831)). ([444e963e](https://github.com/regentmarkets/flutter-deriv-packages/commit/444e963e949334ae81b170c73c1a35afad7a1e0e)) + +## 1.5.1 + + - **REFACTOR**(deriv_localizations): Crowdin Localization Generated ([#686](https://github.com/regentmarkets/flutter-deriv-packages/issues/686)). ([a0a6df21](https://github.com/regentmarkets/flutter-deriv-packages/commit/a0a6df21cbc6681b923ec3e060752de20ddad32b)) + +## 1.5.0 + + - **FEAT**(deriv_ui): [DERG 2450] Added Timeline Widget to Deriv UI ([#631](https://github.com/regentmarkets/flutter-deriv-packages/issues/631)). ([e34d78b3](https://github.com/regentmarkets/flutter-deriv-packages/commit/e34d78b303358cb5f91abab14a2a042ce3650b0f)) + +## 1.4.0 + + - **FEAT**(update_checker): add the ability to change the key from the app side ([#628](https://github.com/regentmarkets/flutter-deriv-packages/issues/628)). ([b18609a0](https://github.com/regentmarkets/flutter-deriv-packages/commit/b18609a00533aaab6d6962eb89f323e2f560df8b)) + +## 1.3.0 + + - **FEAT**(deriv_logger): add ability to print prettified logs in console and UI ([#608](https://github.com/regentmarkets/flutter-deriv-packages/issues/608)). ([5a91c24b](https://github.com/regentmarkets/flutter-deriv-packages/commit/5a91c24bde607ff37940edf18f8dfac67d3fc4fa)) + +## 1.2.2 + + - **FIX**(update_checker): fix optional update issue in remote config [#475](https://github.com/regentmarkets/flutter-deriv-packages/issues/475). ([ac0dc26b](https://github.com/regentmarkets/flutter-deriv-packages/commit/ac0dc26b46b478248ea81d1dba6b7c6844b88995)) + +## 1.2.1 + + - **REFACTOR**(update_checker): increase package info plus version ([#407](https://github.com/regentmarkets/flutter-deriv-packages/issues/407)). ([5147c12b](https://github.com/regentmarkets/flutter-deriv-packages/commit/5147c12bd6b49aed82dd4e3036f63de79816edba)) + ## [1.1.0] - Migrated to Null Safety diff --git a/packages/update_checker/example/lib/update_bloc_page.dart b/packages/update_checker/example/lib/update_bloc_page.dart index 9afd733fd..761cc61a6 100644 --- a/packages/update_checker/example/lib/update_bloc_page.dart +++ b/packages/update_checker/example/lib/update_bloc_page.dart @@ -9,7 +9,8 @@ class UpdateBlocPage extends StatefulWidget { } class _UpdateBlocPageState extends State { - final UpdateBloc updateBloc = UpdateBloc(); + final UpdateBloc updateBloc = + UpdateBloc(firebaseRepository: FirebaseRemoteConfigRepository()); @override Widget build(BuildContext context) => Scaffold( diff --git a/packages/update_checker/example/lib/update_checker_page.dart b/packages/update_checker/example/lib/update_checker_page.dart index 1440f52a5..672a432a0 100644 --- a/packages/update_checker/example/lib/update_checker_page.dart +++ b/packages/update_checker/example/lib/update_checker_page.dart @@ -10,6 +10,7 @@ class UpdateCheckerPage extends StatelessWidget { ), body: Builder( builder: (BuildContext context) => UpdateChecker( + firebaseRepository: FirebaseRemoteConfigRepository(), onNotAvailable: () => _showSnackBar( context, 'Update not available', diff --git a/packages/update_checker/example/pubspec.yaml b/packages/update_checker/example/pubspec.yaml index f3ff98718..945263605 100644 --- a/packages/update_checker/example/pubspec.yaml +++ b/packages/update_checker/example/pubspec.yaml @@ -10,8 +10,8 @@ environment: flutter: "3.10.2" dependencies: - firebase_core: ^2.9.0 - firebase_database: ^10.1.0 + firebase_core: ^3.5.0 + firebase_database: ^11.1.4 flutter: sdk: flutter flutter_bloc: ^8.1.2 diff --git a/packages/update_checker/lib/src/bloc/update_bloc.dart b/packages/update_checker/lib/src/bloc/update_bloc.dart index 84f3544a1..3811fc1a9 100644 --- a/packages/update_checker/lib/src/bloc/update_bloc.dart +++ b/packages/update_checker/lib/src/bloc/update_bloc.dart @@ -1,13 +1,16 @@ import 'dart:convert'; +import 'dart:io'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; +import 'package:update_checker/src/repositories/base_firebase.dart'; import '../models/models.dart'; import '../repositories/repositories.dart'; import '../utils/utils.dart'; part 'update_event.dart'; + part 'update_state.dart'; /// UpdateBloc is responsible for fetching the update availability from the @@ -17,7 +20,7 @@ class UpdateBloc extends Bloc { /// availability from the Firebase Database and `PackageInfoRepository` to /// check the app version with the update to determine the update availability UpdateBloc({ - this.firebaseDatabaseRepository = const FirebaseDatabaseRepository(), + required this.firebaseRepository, this.packageInfoRepository = const PackageInfoRepository(), }) : super(UpdateInitialState()) { on( @@ -27,7 +30,7 @@ class UpdateBloc extends Bloc { } /// Firebase database repository for fetching the update information. - final FirebaseDatabaseRepository firebaseDatabaseRepository; + final BaseFirebase firebaseRepository; /// Package info repository for fetching the app build number. final PackageInfoRepository packageInfoRepository; @@ -36,7 +39,10 @@ class UpdateBloc extends Bloc { UpdateFetchEvent event, Emitter emit) async { if (state is! UpdateInProgressState) { emit(UpdateInProgressState()); - final UpdateInfo? info = await _getUpdateInfo(); + final UpdateInfo? info = + (firebaseRepository is FirebaseRemoteConfigRepository) + ? await _getUpdateInfoFromRemoteConfig() + : await _getUpdateInfoFromDatabase(); if (info != null) { emit(UpdateAvailableState(info)); } else { @@ -45,8 +51,58 @@ class UpdateBloc extends Bloc { } } - Future _getUpdateInfo() async { - final dynamic rawData = await firebaseDatabaseRepository.fetchUpdateData(); + String? _platformName() { + if (Platform.isAndroid || Platform.isIOS) { + return Platform.operatingSystem; + } else { + return null; + } + } + + Future _getUpdateInfoFromRemoteConfig() async { + final String rawData = await firebaseRepository.fetchUpdateData(); + + // checks if failed to fetch data from Firebase Database and return + if (rawData.isEmpty) { + return null; + } + + // checks if failed to get app build number and return + final num appBuildNumber = await packageInfoRepository.getAppBuildNumber(); + if (appBuildNumber <= 0) { + return null; + } + + final Map mapValues = json.decode(rawData); + + final String? platformName = _platformName(); + + if (platformName == null) { + return null; + } + + final num minVersion = mapValues[platformName]['version']['minimum']; + final num latestVersion = mapValues[platformName]['version']['latest']; + + final bool isMandatory = appBuildNumber < minVersion; + + final bool isOptional = + appBuildNumber < latestVersion && appBuildNumber >= minVersion; + + // checks if no update available and return + if (!isMandatory && !isOptional) { + return null; + } + + return _createUpdate( + mapValues[platformName], + isOptional, + isOptional ? latestVersion : minVersion, + ); + } + + Future _getUpdateInfoFromDatabase() async { + final dynamic rawData = await firebaseRepository.fetchUpdateData(); // checks if failed to fetch data from Firebase Database and return if (rawData == null) { @@ -82,16 +138,22 @@ class UpdateBloc extends Bloc { bool isOptional, num buildNumber, ) { - final String? rawChangelogs = rawUpdateInfo['changelogs']?.toString(); - final Map? changelogs = rawChangelogs != null - ? json.decode( - rawChangelogs.toString().substring(1, rawChangelogs.length - 1), - ) - : null; + final Map? changelogs; + if (firebaseRepository is FirebaseRemoteConfigRepository) { + changelogs = rawUpdateInfo['changelogs']; + } else { + final String? rawChangelogs = rawUpdateInfo['changelogs']?.toString(); + changelogs = rawChangelogs != null + ? json.decode( + rawChangelogs.toString().substring(1, rawChangelogs.length - 1), + ) + : null; + } return UpdateInfo( buildNumber: buildNumber, url: rawUpdateInfo['url'], + huaweiUrl: rawUpdateInfo['huawei_url'], changelog: decodeBase64(rawUpdateInfo['changelog'] ?? ''), changelogs: changelogs, isOptional: isOptional, diff --git a/packages/update_checker/lib/src/models/update_info.dart b/packages/update_checker/lib/src/models/update_info.dart index 927135868..cd4b6f2b4 100644 --- a/packages/update_checker/lib/src/models/update_info.dart +++ b/packages/update_checker/lib/src/models/update_info.dart @@ -10,6 +10,7 @@ class UpdateInfo extends Equatable { this.changelog, this.changelogs, this.url, + this.huaweiUrl, }); /// [isOptional] determines if the update is optional or not. @@ -33,6 +34,9 @@ class UpdateInfo extends Equatable { /// [url] is alternative url for updating the app, can be empty. final String? url; + /// [huaweiUrl] is Huawei URL for Redirecting Huawei Users to AppGallery. + final String? huaweiUrl; + @override List get props => [isOptional, buildNumber, url]; @@ -41,5 +45,6 @@ class UpdateInfo extends Equatable { 'isOptional: $isOptional, ' 'buildNumber: $buildNumber, ' 'url: $url' + 'huaweiUrl: $huaweiUrl' ')'; } diff --git a/packages/update_checker/lib/src/presentation/update_checker.dart b/packages/update_checker/lib/src/presentation/update_checker.dart index 38659152b..9f9514d6c 100644 --- a/packages/update_checker/lib/src/presentation/update_checker.dart +++ b/packages/update_checker/lib/src/presentation/update_checker.dart @@ -1,5 +1,7 @@ +import 'package:firebase_remote_config/firebase_remote_config.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:update_checker/src/repositories/base_firebase.dart'; import '../../update_checker.dart'; @@ -9,6 +11,7 @@ class UpdateChecker extends StatefulWidget { /// Checks for update availability as soon as renders and will call the proper /// callback based on the UpdateState. const UpdateChecker({ + required this.firebaseRepository, this.child, this.onAvailable, this.onNotAvailable, @@ -18,6 +21,10 @@ class UpdateChecker extends StatefulWidget { /// The [child] that UpdateChecker widget will wrap. final Widget? child; + /// The [firebaseRepository] that fetch the update information + /// from the firebase system. + final BaseFirebase firebaseRepository; + /// [onAvailable] will be called when there is an update available. final Function(UpdateInfo)? onAvailable; @@ -32,11 +39,25 @@ class UpdateChecker extends StatefulWidget { } class _UpdateCheckerState extends State { - final UpdateBloc _updateBloc = UpdateBloc(); + late final UpdateBloc _updateBloc; @override void initState() { super.initState(); + // Configure fetch settings + FirebaseRemoteConfig.instance.setConfigSettings(RemoteConfigSettings( + fetchTimeout: const Duration(seconds: 10), + // Default fetch timeout + + // As google set a limit of 5 call per hour for each device, + // we set the minimumFetchInterval to 15 minutes + // to avoid Throttling error, + // check: https://firebase.google.com/docs/remote-config/get-started?platform=ios#throttling + minimumFetchInterval: + const Duration(minutes: 15), // Minimum fetch interval set to 0 + )); + + _updateBloc = UpdateBloc(firebaseRepository: widget.firebaseRepository); _updateBloc.add(UpdateFetchEvent()); } diff --git a/packages/update_checker/lib/src/repositories/base_firebase.dart b/packages/update_checker/lib/src/repositories/base_firebase.dart new file mode 100644 index 000000000..cca5598f7 --- /dev/null +++ b/packages/update_checker/lib/src/repositories/base_firebase.dart @@ -0,0 +1,6 @@ +/// Firebase base repository will help to fetch the update information from +/// the firebase system. +abstract class BaseFirebase { + /// Fetches the update information from the firebase system. + Future fetchUpdateData(); +} diff --git a/packages/update_checker/lib/src/repositories/firebase_database.dart b/packages/update_checker/lib/src/repositories/firebase_database.dart index 2838984b4..67e42aa93 100644 --- a/packages/update_checker/lib/src/repositories/firebase_database.dart +++ b/packages/update_checker/lib/src/repositories/firebase_database.dart @@ -1,14 +1,16 @@ import 'dart:io'; import 'package:firebase_database/firebase_database.dart'; +import 'package:update_checker/src/repositories/base_firebase.dart'; /// Firebase Database repository will help to fetch the update information from /// the firebase database. -class FirebaseDatabaseRepository { +class FirebaseDatabaseRepository implements BaseFirebase{ /// Initializes the Firebase Database repository const FirebaseDatabaseRepository(); /// Fetches the update information from the database. + @override Future fetchUpdateData() async { final DatabaseReference dbRef = FirebaseDatabase.instance .ref() diff --git a/packages/update_checker/lib/src/repositories/firebase_remote_config.dart b/packages/update_checker/lib/src/repositories/firebase_remote_config.dart new file mode 100644 index 000000000..48101c42e --- /dev/null +++ b/packages/update_checker/lib/src/repositories/firebase_remote_config.dart @@ -0,0 +1,33 @@ +import 'dart:developer' as dev; + +import 'package:firebase_remote_config/firebase_remote_config.dart'; +import 'package:update_checker/src/repositories/base_firebase.dart'; + +/// Firebase Remote Config repository will help to fetch the update information from +/// the firebase database. +class FirebaseRemoteConfigRepository implements BaseFirebase { + /// Initializes the Firebase Database repository + FirebaseRemoteConfigRepository( + {String versionControlKey = 'app_version_control'}) + : _versionControlKey = versionControlKey; + + late final String _versionControlKey; + + /// Fetches the update information from the database. + @override + Future fetchUpdateData() async { + final FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.instance; + + await remoteConfig.ensureInitialized(); + try { + await remoteConfig.fetchAndActivate(); + } on Exception catch (e, s) { + // Log the exception and continue + dev.log('Error fetching remote config', error: e, stackTrace: s); + } + + final RemoteConfigValue remoteConfigValue = + remoteConfig.getValue(_versionControlKey); + return remoteConfigValue.asString(); + } +} diff --git a/packages/update_checker/lib/src/repositories/repositories.dart b/packages/update_checker/lib/src/repositories/repositories.dart index 5c648fda1..58e2dc7f6 100644 --- a/packages/update_checker/lib/src/repositories/repositories.dart +++ b/packages/update_checker/lib/src/repositories/repositories.dart @@ -1,2 +1,3 @@ export 'firebase_database.dart'; export 'package_info.dart'; +export 'firebase_remote_config.dart'; diff --git a/packages/update_checker/pubspec.lock b/packages/update_checker/pubspec.lock index 0d7ce4b5e..f8825e754 100644 --- a/packages/update_checker/pubspec.lock +++ b/packages/update_checker/pubspec.lock @@ -5,26 +5,26 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: a36ec4843dc30ea6bf652bf25e3448db6c5e8bcf4aa55f063a5d1dad216d8214 + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.dev" source: hosted - version: "58.0.0" + version: "67.0.0" _flutterfire_internals: dependency: transitive description: name: _flutterfire_internals - sha256: f175bc1414e4edf8c5b83372c98eeabecf8353f39c9da423c2cfdf1f1f508788 + sha256: "5534e701a2c505fed1f0799e652dd6ae23bd4d2c4cf797220e5ced5764a7c1c2" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.3.44" analyzer: dependency: transitive description: name: analyzer - sha256: cc4242565347e98424ce9945c819c192ec0838cb9d1f6aa4a97cc96becbc5b27 + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.dev" source: hosted - version: "5.10.0" + version: "6.4.1" args: dependency: transitive description: @@ -85,10 +85,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.18.0" convert: dependency: transitive description: @@ -101,10 +101,10 @@ packages: dependency: transitive description: name: coverage - sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" + sha256: "3945034e86ea203af7a056d98e98e42a5518fff200d6e8e6647e1886b07e936e" url: "https://pub.dev" source: hosted - version: "1.6.3" + version: "1.8.0" crypto: dependency: transitive description: @@ -141,10 +141,10 @@ packages: dependency: transitive description: name: ffi - sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.3" file: dependency: transitive description: @@ -157,50 +157,74 @@ packages: dependency: transitive description: name: firebase_core - sha256: ed611fb8e67e43ecc7956f242cecca383d87cf71aace27287aa5dd4bdba4ac07 + sha256: "51dfe2fbf3a984787a2e7b8592f2f05c986bfedd6fdacea3f9e0a7beb334de96" url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "3.6.0" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: "0df0a064ab0cad7f8836291ca6f3272edd7b83ad5b3540478ee46a0849d8022b" + sha256: e30da58198a6d4b49d5bce4e852f985c32cb10db329ebef9473db2b9f09ce810 url: "https://pub.dev" source: hosted - version: "4.6.0" + version: "5.3.0" firebase_core_web: dependency: transitive description: name: firebase_core_web - sha256: "347351a8f0518f3343d79a9a0690fa67ad232fc32e2ea270677791949eac792b" + sha256: f967a7138f5d2ffb1ce15950e2a382924239eaa521150a8f144af34e68b3b3e5 url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.18.1" firebase_database: dependency: "direct main" description: name: firebase_database - sha256: d437ccb3537485cbcf92df60c493b1601e93bce579518a9874c8d7be5408231e + sha256: "64f8076912139e83f3db3d853df5bffd4c6150a71ffef393584318efd4883f7c" url: "https://pub.dev" source: hosted - version: "10.1.0" + version: "11.1.4" firebase_database_platform_interface: dependency: transitive description: name: firebase_database_platform_interface - sha256: "509351811777e8bcd643f295ae096d8e7741d9bf845467d3626e086d1ac89848" + sha256: "750e9076a41bc78ce87b95f9d0548f224e5b0679009f6236bf5cf98884db60dd" url: "https://pub.dev" source: hosted - version: "0.2.3" + version: "0.2.5+44" firebase_database_web: dependency: transitive description: name: firebase_database_web - sha256: d4a319e7923380f6adc7a46de331ce372aeafe101ae0ec684fef0540d41821bb + sha256: daf3fbde892ff5f2ea7469eff1ab22b7988fe96b637919fdec6890141eb51023 + url: "https://pub.dev" + source: hosted + version: "0.2.6+2" + firebase_remote_config: + dependency: "direct main" + description: + name: firebase_remote_config + sha256: "8985e55900060437136a11f794f430d810a95f93e4a422117c474326feedc2c6" + url: "https://pub.dev" + source: hosted + version: "5.1.3" + firebase_remote_config_platform_interface: + dependency: transitive + description: + name: firebase_remote_config_platform_interface + sha256: ecde097c286b5967b9338f0374725655e2af71e685a8567f9b4700bbeec04ab8 url: "https://pub.dev" source: hosted - version: "0.2.2" + version: "1.4.44" + firebase_remote_config_web: + dependency: transitive + description: + name: firebase_remote_config_web + sha256: e4dc0f7a63150181c800b8bd4fa9f50ce252479f1935f47e7a2845100239eb0a + url: "https://pub.dev" + source: hosted + version: "1.7.2" flutter: dependency: "direct main" description: flutter @@ -280,6 +304,30 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" logging: dependency: transitive description: @@ -292,26 +340,26 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.15.0" mime: dependency: transitive description: @@ -356,26 +404,26 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: cbff87676c352d97116af6dbea05aa28c4d65eb0f6d5677a520c11a69ca9a24d + sha256: "894f37107424311bdae3e476552229476777b8752c5a2a2369c0cb9a2d5442ef" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "8.0.3" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" + sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" plugin_platform_interface: dependency: transitive description: @@ -404,10 +452,10 @@ packages: dependency: transitive description: name: pub_semver - sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" shelf: dependency: transitive description: @@ -465,26 +513,26 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" string_scanner: dependency: transitive description: @@ -505,26 +553,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "3dac9aecf2c3991d09b9cdde4f98ded7b30804a88a0d7e4e7e1678e78d6b97f4" + sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" url: "https://pub.dev" source: hosted - version: "1.24.1" + version: "1.25.7" test_api: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.7.2" test_core: dependency: transitive description: name: test_core - sha256: "5138dbffb77b2289ecb12b81c11ba46036590b72a64a7a90d6ffb880f1a29e93" + sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.4" typed_data: dependency: transitive description: @@ -545,18 +593,26 @@ packages: dependency: transitive description: name: vm_service - sha256: e7fb6c2282f7631712b69c19d1bff82f3767eea33a2321c14fa59ad67ea391c7 + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "9.4.0" + version: "14.2.5" watcher: dependency: transitive description: name: watcher - sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + url: "https://pub.dev" + source: hosted + version: "1.1.0" web_socket_channel: dependency: transitive description: @@ -577,10 +633,10 @@ packages: dependency: transitive description: name: win32 - sha256: dd8f9344bc305ae2923e3d11a2a911d9a4e2c7dd6fe0ed10626d63211a69676e + sha256: e5c39a90447e7c81cfec14b041cdbd0d0916bd9ebbc7fe02ab69568be703b9bd url: "https://pub.dev" source: hosted - version: "4.1.3" + version: "5.6.0" yaml: dependency: transitive description: @@ -590,5 +646,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=3.0.0 <4.0.0" - flutter: ">=3.10.2" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.22.0" diff --git a/packages/update_checker/pubspec.yaml b/packages/update_checker/pubspec.yaml index 9577e8206..50c8d93f7 100644 --- a/packages/update_checker/pubspec.yaml +++ b/packages/update_checker/pubspec.yaml @@ -1,6 +1,6 @@ name: update_checker description: Check and retrieve update information from the server for the given package. -version: 1.1.0 +version: 3.1.1 homepage: https://deriv.com/ publish_to: "none" @@ -12,11 +12,12 @@ environment: dependencies: bloc: ^8.1.1 equatable: ^2.0.5 - firebase_database: ^10.1.0 + firebase_database: ^11.1.4 flutter: sdk: flutter flutter_bloc: ^8.1.2 - package_info_plus: ^3.0.3 + package_info_plus: ^8.0.2 + firebase_remote_config: ^5.1.2 dev_dependencies: bloc_test: ^9.1.1 diff --git a/packages/update_checker/test/update_checker_test.dart b/packages/update_checker/test/update_checker_test.dart index 4df3734c6..c6b7ba7c0 100644 --- a/packages/update_checker/test/update_checker_test.dart +++ b/packages/update_checker/test/update_checker_test.dart @@ -38,7 +38,7 @@ void main() { when(() => mockPackageInfoRepository.getAppBuildNumber()) .thenAnswer((_) async => buildNumber); return UpdateBloc( - firebaseDatabaseRepository: mockFirebaseDatabaseRepository, + firebaseRepository: mockFirebaseDatabaseRepository, packageInfoRepository: mockPackageInfoRepository, ); }, @@ -48,7 +48,10 @@ void main() { test( 'should emit UpdateInitialState as initial state', - () => expect(UpdateBloc().state, UpdateInitialState()), + () => expect( + UpdateBloc(firebaseRepository: mockFirebaseDatabaseRepository) + .state, + UpdateInitialState()), ); generateBlocTest( diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 000000000..e150eca8b --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,317 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + ansi_styles: + dependency: transitive + description: + name: ansi_styles + sha256: "9c656cc12b3c27b17dd982b2cc5c0cfdfbdabd7bc8f3ae5e8542d9867b47ce8a" + url: "https://pub.dev" + source: hosted + version: "0.3.2+1" + args: + dependency: transitive + description: + name: args + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + url: "https://pub.dev" + source: hosted + version: "1.3.1" + cli_launcher: + dependency: transitive + description: + name: cli_launcher + sha256: "5e7e0282b79e8642edd6510ee468ae2976d847a0a29b3916e85f5fa1bfe24005" + url: "https://pub.dev" + source: hosted + version: "0.3.1" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + url: "https://pub.dev" + source: hosted + version: "0.4.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + conventional_commit: + dependency: transitive + description: + name: conventional_commit + sha256: dec15ad1118f029c618651a4359eb9135d8b88f761aa24e4016d061cd45948f2 + url: "https://pub.dev" + source: hosted + version: "0.6.0+1" + file: + dependency: transitive + description: + name: file + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" + source: hosted + version: "6.1.4" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + http: + dependency: transitive + description: + name: http + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + melos: + dependency: "direct dev" + description: + name: melos + sha256: a45e54b72cc2444b46be9d32a590119b9ba8c4e87117f2743a73ec049542f2d3 + url: "https://pub.dev" + source: hosted + version: "3.2.0" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mustache_template: + dependency: transitive + description: + name: mustache_template + sha256: a46e26f91445bfb0b60519be280555b06792460b27b19e2b19ad5b9740df5d1c + url: "https://pub.dev" + source: hosted + version: "2.0.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + platform: + dependency: transitive + description: + name: platform + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + process: + dependency: transitive + description: + name: process + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" + source: hosted + version: "4.2.4" + prompts: + dependency: transitive + description: + name: prompts + sha256: "3773b845e85a849f01e793c4fc18a45d52d7783b4cb6c0569fad19f9d0a774a1" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pub_updater: + dependency: transitive + description: + name: pub_updater + sha256: b06600619c8c219065a548f8f7c192b3e080beff95488ed692780f48f69c0625 + url: "https://pub.dev" + source: hosted + version: "0.3.1" + pubspec: + dependency: transitive + description: + name: pubspec + sha256: f534a50a2b4d48dc3bc0ec147c8bd7c304280fff23b153f3f11803c4d49d927e + url: "https://pub.dev" + source: hosted + version: "2.3.0" + quiver: + dependency: transitive + description: + name: quiver + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "2419f20b0c8677b2d67c8ac4d1ac7372d862dc6c460cdbb052b40155408cd794" + url: "https://pub.dev" + source: hosted + version: "0.7.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + uri: + dependency: transitive + description: + name: uri + sha256: "889eea21e953187c6099802b7b4cf5219ba8f3518f604a1033064d45b1b8268a" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" + yaml_edit: + dependency: transitive + description: + name: yaml_edit + sha256: e9c1a3543d2da0db3e90270dbb1e4eebc985ee5e3ffe468d83224472b2194a5f + url: "https://pub.dev" + source: hosted + version: "2.2.1" +sdks: + dart: ">=3.0.0 <4.0.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 000000000..1e42b50bb --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,7 @@ +name: deriv_packages_workspace + +environment: + sdk: ">=2.18.0 <4.0.0" + +dev_dependencies: + melos: 3.2.0 diff --git a/scripts/readme.sh b/scripts/readme.sh new file mode 100755 index 000000000..4991fc857 --- /dev/null +++ b/scripts/readme.sh @@ -0,0 +1,20 @@ +# This script is used to update version numbers in the README.md file. +# It is used in our version.yaml file (version github action). +cd .. + +# Get the output of `melos list -l` +melos_output=$(melos list -l) + +# This gets the package name and version number from the output of `melos list -l` +function parse_melos_output() { + local melos_output="$1" + echo "$melos_output" | awk -F '->' '{print $1,$2}' +} + +# Replace current version with the new version in the README.md file. +# Looks for package name and version number in the README.md file and replaces it with the new version number. +parse_melos_output "$melos_output" | while read -r package version; do + package=$(echo $package | xargs) + version=$(echo $version | awk '{print $1}') + sed -i -E "s|($package.*)(v[0-9\.+\-]+)|\1v$version|g" README.md +done