Compare commits

...

130 Commits

Author SHA1 Message Date
Neeraj Gupta
77632b3241 Bump version 2.0.26+226 2023-11-27 18:39:22 +05:30
Neeraj Gupta
d02e02a326 Sign up: Add field for 'hear us from' info (#376) 2023-11-27 18:06:49 +05:30
github-actions[bot]
7fe314a16a New Translations (#375)
New translations via [Crowdin GH
Action](https://github.com/crowdin/github-action)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-11-27 18:04:23 +05:30
green
504fa939d2 Add CLI tool + disclaimer to Authy guide (#373)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Description
Adds a hyperlink to Neeraj's CLI tool shown in ente Discord, as well as
a disclaimer to check all accounts have been imported before deleting
Authy data
<!--- Describe your changes in detail -->

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ] 🖼️ New icon
- [ ]  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
- [x] 📝 Documentation
- [ ] 🗑️ Chore
2023-11-27 08:33:49 +05:30
Neeraj Gupta
17f5a7996a Change Password: Confirm before signing out from other devices (#374) 2023-11-26 16:27:25 +05:30
Neeraj Gupta
cf6b4f5423 Enable import for 2fas v4 (#372) 2023-11-26 14:19:33 +05:30
github-actions[bot]
ca46765760 New Translations (#371)
New translations via [Crowdin GH
Action](https://github.com/crowdin/github-action)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-11-26 13:40:47 +05:30
Neeraj Gupta
4de3287f55 Support importing 2FAS export (v3) (#370)
Tested locally.

Planning to extract the strings separately while merging common strings
from other import sections.
2023-11-26 13:38:47 +05:30
Neeraj Gupta
cdc27e061b Fix: Add missing await (#368) 2023-11-24 22:07:04 +05:30
Tanguy
78b3d239eb Add Proxmox logo (#366)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

New logo + refractor

<!--- Describe your changes in detail -->

Add Proxmox logo. Remove test icon. Refractor X icon data with altNames.

SVGs with mix-blend-mode appear to be glitchy in emulators but not on
real devices
(https://github.com/dnfield/flutter_svg/issues/922#issuecomment-1552104687).
By the way, SVGs using mix-blend-mode get pixelated. This is probably
because of `flutter_svg` library
(https://github.com/dnfield/flutter_svg/issues/985). I didn't go into
any depth, I didn't feel like digging into the code.

<!--- Put an `x` in all the boxes that apply: -->

- [x] 🖼️ New icon
- [ ]  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)
- [x] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2023-11-24 10:42:28 +05:30
github-actions[bot]
005d3b0eca New Translations (#364)
New translations via [Crowdin GH
Action](https://github.com/crowdin/github-action)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-11-23 07:20:03 +05:30
github-actions[bot]
23ba69c6a6 New Translations (#363)
New translations via [Crowdin GH
Action](https://github.com/crowdin/github-action)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-11-21 10:35:26 +05:30
Neeraj Gupta
d644ee97e1 Bump version 2.0.22+222 2023-11-20 18:15:10 +05:30
Neeraj Gupta
0afd0d63b3 Show hint that double tapping on hidden code makes it visible (#362)
Related to #331
2023-11-20 18:14:13 +05:30
Neeraj Gupta
f04d9b94d9 Add device auth for sign in to backup action (#361)
Related to #337
2023-11-20 18:08:41 +05:30
github-actions[bot]
8cd4ec30af New Translations (#360)
New translations via [Crowdin GH
Action](https://github.com/crowdin/github-action)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-11-20 17:35:33 +05:30
Neeraj Gupta
4215810cf8 Add fix for loginViaSRP (#359)
Port fixes from photos-app
2023-11-20 17:21:38 +05:30
Neeraj Gupta
1f0c2d2aa6 Fix lockscreen issue on iPAD (#358)
Tested internally on Simulator and via Test flight.
2023-11-20 15:53:46 +05:30
Zxhir
dc7782fb0c Add the Pingvin Share logo (#356)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Description

This PR adds the Pingvin Share icon. Pingvin Share is a self-hosted file
sharing platform, their GitHub is
[here](https://github.com/stonith404/pingvin-share) if you want to check
it out.

<!--- Describe your changes in detail -->

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [x] 🖼️ New icon
- [ ]  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
2023-11-18 19:51:26 +05:30
github-actions[bot]
c7ba0c5f33 New Translations (#355) 2023-11-18 19:04:56 +05:30
Neeraj Gupta
5d372607a4 Revert "Analyze qr code image (#336)" (#354)
This reverts commit 233858ad09, reversing
changes made to f97bf015f1.

Reason: media_scanner depends on MLKit that underhood also makes
connection to firebase.
2023-11-17 09:09:22 +05:30
Neeraj Gupta
b203b373b9 Bump version 2.0.18+218 (#352) 2023-11-16 19:17:09 +05:30
github-actions[bot]
a4e12a1e0c New Translations (#351)
New translations via [Crowdin GH
Action](https://github.com/crowdin/github-action)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-11-16 19:11:22 +05:30
Neeraj Gupta
233858ad09 Analyze qr code image (#336) 2023-11-16 15:40:47 +05:30
Neeraj Gupta
863d602a70 Merge branch 'main' into analyzeQrCodeImage 2023-11-16 15:30:08 +05:30
Neeraj Gupta
f97bf015f1 New Translations (#350) 2023-11-16 12:16:08 +05:30
Muhammed Ayimen
9d73a39b9d Updated: Parsing error catched and logged 2023-11-16 15:38:42 +09:00
Muhammed Ayimen
3455e7f7c3 Removed: Unwanted comments in analyze qr code screen 2023-11-16 15:34:44 +09:00
Muhammed Ayimen
e9861e5311 Uncommented: Gradle file contents 2023-11-16 13:21:02 +09:00
Muhammed Ayimen
f902c8ca4c Added: Error handling while parcing qr code data 2023-11-16 13:19:51 +09:00
Muhammed Ayimen
4180621bd9 Bug fixed: Old scanner package using in other places 2023-11-16 13:17:43 +09:00
Muhammed Ayimen
ab2eb77722 Added and removed: Disposal of scanner controller and removal of old scanning package 2023-11-16 13:17:07 +09:00
Muhammed Ayimen
09e6511d64 Removed: Old google auth import scanner 2023-11-16 13:12:03 +09:00
Crowdin Bot
5c4ec23b1a New Crowdin translations by GitHub Action 2023-11-16 00:01:16 +00:00
Muhammed Ayimen
c7a78a70c9 Bug fixed: Google auth image option click function 2023-11-15 08:19:07 +09:00
Muhammed Ayimen
a4df578665 Merge conflict: Resolved-2 2023-11-15 08:12:09 +09:00
Muhammed Ayimen
4cfbe91ad4 Merge conflict: Resolved 2023-11-15 08:08:38 +09:00
Vishnu Mohandas
ae6e910e84 New Translations (#346) 2023-11-14 21:14:36 +05:30
Crowdin Bot
533e0c413a New Crowdin translations by GitHub Action 2023-11-14 14:57:08 +00:00
Vishnu Mohandas
4ec71f6151 Bitwarden import (#348) 2023-11-14 20:26:18 +05:30
Muhammed Ayimen
adc157fe56 Fixes: Based on the review 2023-11-14 23:42:53 +09:00
Muhammed Ayimen
3b8219020a Updated: en arb file content for bitwarden data 2023-11-14 23:40:40 +09:00
Muhammed Ayimen
9b759a02a5 Completed: Import from bitwarden functionality 2023-11-14 23:06:02 +09:00
Muhammed Ayimen
bed3bd9612 Bug fixed: Using the map values from wrong jsonArray 2023-11-14 23:05:43 +09:00
Muhammed Ayimen
27c8111e63 Created: Bitward import functionality with json file selecting and extracting the data to a particular format 2023-11-14 23:05:02 +09:00
Muhammed Ayimen
4b66689e07 Created: Popup for bitwarden import option 2023-11-14 23:04:21 +09:00
Muhammed Ayimen
f22b0cde8d Added: Bitwarden option in import screen 2023-11-14 23:04:05 +09:00
Muhammed Ayimen
1705229ad9 Merge conflict: Resolved 2023-11-14 17:47:22 +09:00
Muhammed Ayimen
baa941c9dd Merge branch 'main' of https://github.com/i-aiymen/auth into analyzeQrCodeImage 2023-11-14 17:45:10 +09:00
Neeraj Gupta
55e9a7049e Lock screen fixes (#345) 2023-11-14 10:39:52 +05:30
Neeraj Gupta
6b4b69f0bb Bump version 2.0.17 2023-11-14 10:35:19 +05:30
Neeraj Gupta
572417d3aa Fix bug in setting showingLockScreen flag 2023-11-14 10:35:00 +05:30
Neeraj Gupta
90193eaed9 Fix import 2023-11-14 10:30:19 +05:30
Neeraj Gupta
d80f783013 Remove commented out code 2023-11-14 10:29:59 +05:30
Neeraj Gupta
16c36a088f bump version 2023-11-14 10:28:35 +05:30
Neeraj Gupta
c0444680c6 Extract strings for local auth 2023-11-14 10:25:02 +05:30
Neeraj Gupta
03b1accfda Add lockscreen fixes from photos 2023-11-14 10:21:03 +05:30
Muhammed Ayimen
8e379a8e43 Uncommmented: Gradle file contents 2023-11-13 20:42:30 +09:00
Muhammed Ayimen
e05bd71fe1 Extracted: All the string added in the Qr code scanning and analyzing image to en arb files 2023-11-13 20:38:49 +09:00
Muhammed Ayimen
5cc4d8cfb3 Updated: allowing only images to be selected for analyzing the Qr code 2023-11-13 20:26:30 +09:00
Vishnu Mohandas
c359775ebc Support multiple names for the same service (#344) 2023-11-13 16:42:08 +05:30
Vishnu Mohandas
80148f95af Merge branch 'main' into support_multiple_names 2023-11-13 16:33:16 +05:30
vishnukvmd
16bb23d977 Support multiple names for the same service 2023-11-13 16:32:08 +05:30
Vishnu Mohandas
0e59f80999 Update icons (#341) 2023-11-12 12:19:16 +05:30
vishnukvmd
a13d2a065e Update icon for mstdn.* 2023-11-12 10:31:28 +05:30
vishnukvmd
993bf81e43 Update simple icons 2023-11-12 10:04:50 +05:30
Muhammed Ayimen
71f3ce4120 Changed: Settings.json file 2023-11-12 13:03:16 +09:00
Muhammed Ayimen
694662d851 Uncommmented: Gradle file contents 2023-11-12 12:57:30 +09:00
Muhammed Ayimen
492748f854 Added: Reminder Popup after analyzing a Qr Code image from gallery 2023-11-12 12:56:26 +09:00
Muhammed Ayimen
c6811ffadc Updated: Different toast, based different condition 2023-11-12 12:23:24 +09:00
Muhammed Ayimen
d432f08dc6 Added: Vibration effect, when google auth Qr code scanning is successfull 2023-11-12 12:16:03 +09:00
Muhammed Ayimen
2930f22208 Added: Functionality to capture the QR codes by scanning it and checking if the QR code is associated with google auth and reject all the QR ccodes 2023-11-12 12:15:25 +09:00
Muhammed Ayimen
b52de5d9e6 Added: Popup while selecting google auth saved image option similar to normal google auth option 2023-11-12 12:05:27 +09:00
Muhammed Ayimen
fd6b030dff Removed: Unwanted permissions from manifest file 2023-11-10 07:58:28 +09:00
Muhammed Ayimen
ae07e435ae Merge branch 'analyzeQrCodeImage' of https://github.com/i-aiymen/auth into analyzeQrCodeImage 2023-11-09 08:03:47 +09:00
Muhammed Ayimen
abab8df412 Updated: Build gradle and settings.json file to initial values 2023-11-08 22:30:37 +09:00
Muhammed Ayimen
c0cc5f29e1 Re-Updated: Location of icons used in qr-code-scanner screen and added the location to the pubspec 2023-11-08 22:27:14 +09:00
Muhammed Ayimen
cd817f6df9 Re-Updated: Location of icons used in qr-code-scanner screen 2023-11-08 22:21:10 +09:00
Muhammed Ayimen
2d56c656cc Updated: Location of icons used in qr-code-scanner screen 2023-11-08 22:12:07 +09:00
Muhammed Ayimen
b33ec5e347 Removed: Unwanted permisions from manifest file 2023-11-08 22:08:53 +09:00
Muhammed Ayimen
3bdfebfcea Added: Comments for different widgets used 2023-11-08 21:47:59 +09:00
Vishnu Mohandas
02d2db92c2 New Translations (#335) 2023-11-08 18:14:12 +05:30
Muhammed Ayimen
3db1c55ce3 Function added: Qr Code Image analyzing by uploading from gallery and scanning Qr code images directly 2023-11-08 21:25:01 +09:00
Crowdin Bot
8ed5bd84d2 New Crowdin translations by GitHub Action 2023-11-08 12:01:18 +00:00
Vishnu Mohandas
7855e3925a New Translations (#328) 2023-11-06 14:11:38 +05:30
Crowdin Bot
f5125b7981 New Crowdin translations by GitHub Action 2023-11-06 00:01:05 +00:00
Vishnu Mohandas
5b11569ea9 New Translations (#325) 2023-11-05 20:33:41 +05:30
Crowdin Bot
c2dc0e6318 New Crowdin translations by GitHub Action 2023-11-05 12:01:21 +00:00
Neeraj Gupta
4ab4b51b56 Reformat code (#323) 2023-10-31 11:41:41 +05:30
vishnukvmd
25c84d5b56 Reformat code 2023-10-31 10:56:04 +05:30
Neeraj Gupta
8f7cf104ed New Translations (#322) 2023-10-31 06:40:17 +05:30
Crowdin Bot
dfc21f60a8 New Crowdin translations by GitHub Action 2023-10-31 00:01:26 +00:00
Neeraj Gupta
ed3067d9d2 New Translations (#321) 2023-10-27 11:43:24 +05:30
Crowdin Bot
459e501700 New Crowdin translations by GitHub Action 2023-10-27 00:01:16 +00:00
Neeraj Gupta
67c5452dee Fix invalid json for custom icons + bump version (#320) 2023-10-25 13:21:11 +05:30
Neeraj Gupta
b847880d41 bump version 2.0.15+215 2023-10-25 12:54:03 +05:30
Neeraj Gupta
257e4e1486 Swallow error in during icon json load 2023-10-25 12:52:49 +05:30
Neeraj Gupta
f8f13f9c8c Fix json 2023-10-25 12:51:11 +05:30
Neeraj Gupta
4bfe69bc47 Downgrade file saver (#319) 2023-10-25 10:01:44 +05:30
Neeraj Gupta
322eb273cd Downgrade file saver 2023-10-25 09:57:27 +05:30
Neeraj Gupta
6bb7db7d54 Add dev_test_icon (#318) 2023-10-25 09:57:01 +05:30
Neeraj Gupta
d077109bc8 New Translations (#317) 2023-10-25 09:43:38 +05:30
Crowdin Bot
ed8aef8f80 New Crowdin translations by GitHub Action 2023-10-25 00:01:02 +00:00
Tanguy
bb1f730e32 Add dev_test_icon
Test icon for verifying SVG behaviour in the release version
2023-10-24 19:47:35 +02:00
Neeraj Gupta
afaf26a2f2 Bump version 2.0.13+213 2023-10-24 10:57:12 +05:30
Vishnu Mohandas
d7a2597406 Change Firefox logo name to Mozilla (#312) 2023-10-24 09:11:15 +05:30
Neeraj Gupta
226aeafe76 Warn accounts are linked with all ente apps before deleting account (#314) 2023-10-24 06:44:06 +05:30
Tanguy
d4a6363793 Merge branch 'main' into my-changes 2023-10-23 19:08:35 +02:00
Neeraj Gupta
249a8016a1 New Translations (#310) 2023-10-23 20:20:32 +05:30
ashilkn
f914256510 Move new mapping to bottom 2023-10-23 19:33:51 +05:30
ashilkn
a9bc28ad02 before deleting account, warn that all ente apps use the same account and deleting the account will wipe out data from all apps 2023-10-23 19:23:31 +05:30
Crowdin Bot
4d89a0ed4a New Crowdin translations by GitHub Action 2023-10-23 12:01:17 +00:00
Tanguy
3475e3a988 Change Firefox logo name to Mozilla
Mozilla has announced the end of Firefox accounts, which will all be renamed as Mozilla accounts. "Firefox" title will continue to work by pointing to the file mozilla.svg.
2023-10-22 16:09:45 +02:00
Vishnu Mohandas
4b246174b6 Update issue templates (#306) 2023-10-21 12:10:49 +05:30
Vishnu Mohandas
aad130fdcb Create CODE_OF_CONDUCT.md (#305) 2023-10-21 12:10:34 +05:30
Vishnu Mohandas
cf4ff27e9a Update issue templates 2023-10-21 11:47:17 +05:30
Vishnu Mohandas
a25925aa6d Create CODE_OF_CONDUCT.md 2023-10-21 11:46:22 +05:30
Neeraj Gupta
7c77c03487 Fix log export for android (#302) 2023-10-20 17:46:57 +05:30
Neeraj Gupta
f549e2e268 Fix Instagram logo (#304) 2023-10-20 17:46:38 +05:30
Tanguy
4b1b27a243 Merge branch 'main' into my-changes 2023-10-20 07:55:43 +02:00
Tanguy
ea915489b8 Fix Instagram logo
Quick fix Instagram logo since Flutter is not compatible with SVG <pattern>
2023-10-20 07:52:14 +02:00
Vishnu Mohandas
e211239ed6 Bunch together app specific settings (#303) 2023-10-19 21:19:40 +05:30
vishnukvmd
a09a4e5c9b Bunch together app specific settings 2023-10-19 21:03:35 +05:30
Neeraj Gupta
7c552a1e8c minor refactor 2023-10-19 15:41:54 +05:30
Neeraj Gupta
518eb1d942 Fix log export for android 2023-10-19 15:23:51 +05:30
Neeraj Gupta
ec895546f6 Bump version to 2.0.12 2023-10-19 12:50:48 +05:30
Neeraj Gupta
f5b7894cb1 Show placeholder icons when icon is missing (#301) 2023-10-19 12:48:31 +05:30
Neeraj Gupta
3f5ef23e1b Update ui 2023-10-19 12:46:03 +05:30
Neeraj Gupta
d496adaed2 Show placeholder icons when icon is missing 2023-10-19 12:38:27 +05:30
Neeraj Gupta
016e85f350 Bump version to 2.0.11 2023-10-19 12:20:03 +05:30
59 changed files with 3575 additions and 500 deletions

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**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.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

128
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
community@ente.io.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@@ -52,7 +52,8 @@
"hex": "858585"
},
{
"title": "Firefox"
"title": "Firefox",
"slug": "mozilla"
},
{
"title": "GitHub",
@@ -94,9 +95,18 @@
"title": "La Poste",
"slug": "laposte"
},
{
"title": "Mastodon",
"altNames": ["mstdn", "fediscience", "mathstodon", "fosstodon"],
"slug": "mastodon",
"hex": "6364FF"
},
{
"title": "Microsoft"
},
{
"title": "Mozilla"
},
{
"title": "ngrok",
"hex": "858585"
@@ -123,6 +133,10 @@
"title": "Peerberry",
"hex": "03E5A5"
},
{
"title": "Pingvin Share",
"hex": "485099"
},
{
"title": "Plutus",
"hex": "DEC685"
@@ -149,6 +163,9 @@
{
"title": "Proton"
},
{
"title": "Proxmox"
},
{
"title": "Revolt",
"hex": "858585"
@@ -192,10 +209,6 @@
"title": "Twingate",
"hex": "858585"
},
{
"title": "Twitter",
"slug": "x"
},
{
"title": "Ubisoft",
"hex": "4285f4"
@@ -215,7 +228,9 @@
"title": "Wise"
},
{
"title": "X"
"title": "X",
"altNames": ["twitter"],
"slug": "x"
},
{
"title": "NextDNS"
@@ -224,6 +239,5 @@
"title": "Skiff",
"hex": "EF5A3C"
}
]
}

View File

Before

Width:  |  Height:  |  Size: 822 B

After

Width:  |  Height:  |  Size: 822 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 MiB

After

Width:  |  Height:  |  Size: 118 KiB

View File

@@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Mastodon</title><path d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 943.11 911.62"><script xmlns=""/>
<defs>
<style>
.cls-1 {
fill: #fff;
}
.cls-2 {
fill: #37474f;
}
.cls-3 {
fill: #46509e;
}
</style>
</defs>
<ellipse class="cls-3" cx="471.56" cy="454.28" rx="471.56" ry="454.28"/>
<g>
<g>
<ellipse class="cls-2" cx="471.56" cy="390.28" rx="233.66" ry="207"/>
<path class="cls-2" d="m705.22,848.95c-36.69,21.14-123.09,64.33-240.64,62.57-109.54-1.63-190.04-41.45-226.68-62.57v-454.19h467.33v454.19Z"/>
</g>
<path class="cls-1" d="m658.81,397.7v475.8c-36.98,15.7-98.93,36.54-177.98,38.04-88.67,1.69-157.75-21.73-196.2-38.04v-475.8c0-95.55,83.77-173.02,187.09-173.02s187.09,77.47,187.09,173.02Z"/>
<polygon class="cls-3" points="565.02 431.68 471.56 514.49 378.09 431.68 565.02 431.68"/>
<ellipse class="cls-2" cx="378.09" cy="369.58" rx="23.37" ry="20.7"/>
<ellipse class="cls-2" cx="565.02" cy="369.58" rx="23.37" ry="20.7"/>
<path class="cls-2" d="m658.49,400.63c0-40.04-36.59-72.45-81.78-72.45s-81.78,32.41-81.78,72.45c0,11.14,2.81,21.65,7.9,31.05h-62.54c5.1-9.4,7.9-19.91,7.9-31.05,0-40.04-36.59-72.45-81.78-72.45s-81.78,32.41-81.78,72.45l-46.73-10.35c0-114.32,104.63-207,233.66-207s233.66,92.69,233.66,207l-46.73,10.35Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,6 @@
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 388.755L126.571 249.665L0 110.571C13.9095 96.6616 33.3816 87.8523 54.7141 87.8523C77.4297 87.8523 97.3643 97.5937 111.274 112.89L181.281 189.854L235.992 249.665L181.281 309.942L111.274 386.902C97.3643 402.202 77.4297 411.94 54.7141 411.94C33.3816 411.94 13.9095 403.127 0 388.755ZM500 388.757L373.43 249.667L500 110.573C486.091 96.6633 466.619 87.8536 445.286 87.8536C422.571 87.8536 402.636 97.5956 388.726 112.892L318.719 189.856L264.008 249.667L318.719 309.943L388.726 386.904C402.636 402.204 422.571 411.942 445.286 411.942C466.619 411.942 486.091 403.128 500 388.757Z" fill="#E57000"/>
<g style="mix-blend-mode:difference">
<path fill-rule="evenodd" clip-rule="evenodd" d="M249.934 235.708L299.87 180.692L415.394 53.7358C402.7 41.0404 384.926 33 365.456 33C344.722 33 326.528 41.8913 313.83 55.8522L249.934 126.1L185.607 55.8522C172.491 41.4659 155.138 33 134.4 33C114.941 33 97.163 41.0404 84.4681 53.7358L199.996 180.692L249.934 235.708ZM249.937 263.615L299.873 318.631L415.398 445.588C402.703 458.283 384.929 466.323 365.459 466.323C344.725 466.323 326.531 457.432 313.834 443.471L249.937 373.224L185.61 443.471C172.494 457.857 155.141 466.323 134.403 466.323C114.944 466.323 97.1661 458.283 84.4713 445.588L200 318.631L249.937 263.615Z" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -38,6 +38,8 @@ PODS:
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- file_saver (0.0.1):
- Flutter
- fk_user_agent (2.0.0):
- Flutter
- Flutter (1.0.0)
@@ -110,6 +112,7 @@ DEPENDENCIES:
- connectivity (from `.symlinks/plugins/connectivity/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- file_saver (from `.symlinks/plugins/file_saver/ios`)
- fk_user_agent (from `.symlinks/plugins/fk_user_agent/ios`)
- Flutter (from `Flutter`)
- flutter_email_sender (from `.symlinks/plugins/flutter_email_sender/ios`)
@@ -153,6 +156,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/device_info_plus/ios"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
file_saver:
:path: ".symlinks/plugins/file_saver/ios"
fk_user_agent:
:path: ".symlinks/plugins/fk_user_agent/ios"
Flutter:
@@ -202,6 +207,7 @@ SPEC CHECKSUMS:
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
@@ -225,7 +231,7 @@ SPEC CHECKSUMS:
Sentry: e3203780941722a1fcfee99e351de14244c7f806
sentry_flutter: 8f0ffd53088e6a4d50c095852c5cad9e4405025c
SentryPrivate: 5e3683390f66611fc7c6215e27645873adb55d13
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f

View File

@@ -41,6 +41,8 @@ class InvalidStateError extends AssertionError {
class KeyDerivationError extends Error {}
class LoginKeyDerivationError extends Error {}
class SrpSetupNotCompleteError extends Error {}
class AuthenticatorKeyNotFound extends Error {}

1
lib/l10n/arb/app_ar.arb Normal file
View File

@@ -0,0 +1 @@
{}

View File

@@ -151,8 +151,6 @@
"yesSendFeedbackAction": "Ja, Feedback senden",
"noDeleteAccountAction": "Nein, Konto löschen",
"initiateAccountDeleteTitle": "Bitte authentifizieren Sie sich, um die Kontolöschung einzuleiten",
"confirmAccountDeleteTitle": "Sind Sie sicher, dass Sie Ihr ente Konto löschen wollen?",
"confirmAccountDeleteMessage": "Ihre hochgeladenen Daten werden in allen Anwendungen (sowohl Fotos als auch Authenticator) zur Löschung vorgesehen und Ihr Konto wird dauerhaft gelöscht.",
"sendEmail": "E-Mail senden",
"createNewAccount": "Neues Konto erstellen",
"weakStrength": "Schwach",

View File

@@ -1,5 +1,6 @@
{
"account": "Account",
"unlock": "Unlock",
"recoveryKey": "Recovery key",
"counterAppBarTitle": "Counter",
"@counterAppBarTitle": {
@@ -83,9 +84,12 @@
"importFromApp": "Import codes from {appName}",
"importGoogleAuthGuide": "Export your accounts from Google Authenticator to a QR code using the \"Transfer Accounts\" option. Then using another device, scan the QR code.\n\nTip: You can use your laptop's webcam to take a picture of the QR code.",
"importSelectJsonFile": "Select JSON file",
"importSelectAppExport": "Select {appName} export file",
"importEnteEncGuide": "Select the encrypted JSON file exported from ente",
"importRaivoGuide": "Use the \"Export OTPs to Zip archive\" option in Raivo's Settings.\n\nExtract the zip file and import the JSON file.",
"importBitwardenGuide": "Use the \"Export vault\" option within Bitwarden Tools and import the unencrypted JSON file.",
"importAegisGuide": "Use the \"Export the vault\" option in Aegis's Settings.\n\nIf your vault is encrypted, you will need to enter vault password to decrypt the vault.",
"import2FasGuide": "Use the \"Settings->Backup -Export\" option in 2FAS.\n\nIf your backup is encrypted, you will need to enter the password to decrypt the backup",
"exportCodes": "Export codes",
"importLabel": "Import",
"importInstruction": "Please select a file that contains a list of your codes in the following format",
@@ -97,6 +101,8 @@
"authToViewYourRecoveryKey": "Please authenticate to view your recovery key",
"authToChangeYourEmail": "Please authenticate to change your email",
"authToChangeYourPassword": "Please authenticate to change your password",
"authToViewSecrets": "Please authenticate to view your secrets",
"authToInitiateSignIn": "Please authenticate to initiate sign in for backup.",
"ok": "Ok",
"cancel": "Cancel",
"yes": "Yes",
@@ -126,6 +132,7 @@
"faq_q_5": "How can I enable FaceID lock in ente Auth",
"faq_a_5": "You can enable FaceID lock under Settings → Security → Lockscreen.",
"somethingWentWrongMessage": "Something went wrong, please try again",
"leaveFamily": "Leave family",
"leaveFamilyMessage": "Are you sure that you want to leave the family plan?",
"inFamilyPlanMessage": "You are on a family plan!",
@@ -157,8 +164,6 @@
"yesSendFeedbackAction": "Yes, send feedback",
"noDeleteAccountAction": "No, delete account",
"initiateAccountDeleteTitle": "Please authenticate to initiate account deletion",
"confirmAccountDeleteTitle": "Are you sure you want to delete your ente account?",
"confirmAccountDeleteMessage": "Your uploaded data, across all apps (Photos and Authenticator both), will be scheduled for deletion, and your account will be permanently deleted.",
"sendEmail": "Send email",
"createNewAccount": "Create new account",
"weakStrength": "Weak",
@@ -331,10 +336,73 @@
"offlineModeWarning": "You have chosen to proceed without backups. Please take manual backups to make sure your codes are safe.",
"showLargeIcons": "Show large icons",
"shouldHideCode": "Hide codes",
"doubleTapToViewHiddenCode" : "You can double tap on an entry to view code",
"focusOnSearchBar": "Focus search on app start",
"confirmUpdatingkey": "Are you sure you want to update the secret key?",
"minimizeAppOnCopy": "Minimize app on copy",
"editCodeAuthMessage": "Authenticate to edit code",
"deleteCodeAuthMessage": "Authenticate to delete code",
"showQRAuthMessage": "Authenticate to show QR code"
"showQRAuthMessage": "Authenticate to show QR code",
"confirmAccountDeleteTitle": "Confirm account deletion",
"confirmAccountDeleteMessage": "This account is linked to other ente apps, if you use any.\n\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted.",
"androidBiometricHint": "Verify identity",
"@androidBiometricHint": {
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
},
"androidBiometricNotRecognized": "Not recognized. Try again.",
"@androidBiometricNotRecognized": {
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
},
"androidBiometricSuccess": "Success",
"@androidBiometricSuccess": {
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
},
"androidCancelButton": "Cancel",
"@androidCancelButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
},
"androidSignInTitle": "Authentication required",
"@androidSignInTitle": {
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
},
"androidBiometricRequiredTitle": "Biometric required",
"@androidBiometricRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
},
"androidDeviceCredentialsRequiredTitle": "Device credentials required",
"@androidDeviceCredentialsRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
},
"androidDeviceCredentialsSetupDescription": "Device credentials required",
"@androidDeviceCredentialsSetupDescription": {
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
},
"goToSettings": "Go to settings",
"@goToSettings": {
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
},
"androidGoToSettingsDescription": "Biometric authentication is not set up on your device. Go to 'Settings > Security' to add biometric authentication.",
"@androidGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
},
"iOSLockOut": "Biometric authentication is disabled. Please lock and unlock your screen to enable it.",
"@iOSLockOut": {
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
},
"iOSGoToSettingsDescription": "Biometric authentication is not set up on your device. Please either enable Touch ID or Face ID on your phone.",
"@iOSGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
},
"iOSOkButton": "OK",
"@iOSOkButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
},
"noInternetConnection": "No internet connection",
"pleaseCheckYourInternetConnectionAndTryAgain": "Please check your internet connection and try again.",
"signOutFromOtherDevices": "Sign out from other devices",
"signOutOtherBody": "If you think someone might know your password, you can force all other devices using your account to sign out.",
"signOutOtherDevices": "Sign out other devices",
"doNotSignOut": "Do not sign out",
"hearUsWhereTitle": "How did you hear about Ente? (optional)",
"hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!"
}

View File

@@ -9,8 +9,13 @@
"onBoardingGetStarted": "Primeros pasos",
"setupFirstAccount": "Configura tu primera cuenta",
"importScanQrCode": "Escanear un código QR",
"qrCode": "Código QR",
"importEnterSetupKey": "Ingrese una llave de configuración",
"importAccountPageTitle": "Ingrese los detalles de la cuenta",
"secretCanNotBeEmpty": "El secreto no puede estar vacío",
"bothIssuerAndAccountCanNotBeEmpty": "El emisor y la cuenta no pueden estar vacíos",
"incorrectDetails": "Detalles incorrectos",
"pleaseVerifyDetails": "Por favor, confirma los detalles e intentar otra vez",
"codeIssuerHint": "Emisor",
"codeSecretKeyHint": "Llave Secreta",
"codeAccountHint": "Cuenta (tu@dominio.com)",
@@ -53,6 +58,9 @@
}
},
"contactSupport": "Ponerse en contacto con el equipo de soporte",
"rateUsOnStore": "Califícanos en {storeName}",
"blog": "Blog",
"merchandise": "Mercancías",
"verifyPassword": "Verificar contraseña",
"pleaseWait": "Por favor, espere...",
"generatingEncryptionKeysTitle": "Generando claves de encriptación...",
@@ -68,10 +76,24 @@
"changePassword": "Cambiar contraseña",
"data": "Datos",
"importCodes": "Importar códigos",
"importTypePlainText": "Texto sin formato",
"importTypeEnteEncrypted": "Exportación cifrada ente",
"passwordForDecryptingExport": "Contraseña para descifrar exportación",
"passwordEmptyError": "La contraseña no puede estar vacía",
"importFromApp": "Importar códigos de {appName}",
"importGoogleAuthGuide": "Exportar tus cuentas desde Google Authenticator a un código QR usando la opción \"Transferir Cuentas\". A continuación, usando otro dispositivo, escanee el código QR.\n\nConsejo: Puede usar la webcam de su portátil para tomar una foto del código QR.",
"importSelectJsonFile": "Seleccione el archivo JSON",
"importEnteEncGuide": "Seleccione el archivo JSON cifrado exportado desde ente",
"importRaivoGuide": "Utilice la opción \"Exportar códigos a un archivo de Zip\" en la configuración de Raivo.\n\nExtraiga el archivo zip e importe el archivo JSON.",
"importAegisGuide": "Utilice la opción \"Exportar la bóveda\" en ajustes de Aegis.\n\nSi tu bóveda es cifrada, necesitara entrar contraseña de bóveda para descifrar la bóveda.",
"exportCodes": "Exportar códigos",
"importLabel": "Importar",
"importInstruction": "Por favor, seleccione un archivo que contenga una lista de sus códigos en el siguiente formato",
"importCodeDelimiterInfo": "Los códigos pueden separarse por una coma o una nueva línea",
"selectFile": "Seleccionar archivo",
"emailVerificationToggle": "Verificación de correo electrónico",
"emailVerificationEnableWarning": "Si estás guardando la autenticación de dos factores en tu correo electrónico con nosotros, activar la verificación de correo electrónico podría resultar en un punto muerto. Si está bloqueado fuera de un servicio, puede que no pueda iniciar sesión en el otro.",
"authToChangeEmailVerificationSetting": "Por favor, autentifíquese para cambiar su correo electrónico",
"authToViewYourRecoveryKey": "Por favor, autentifíquese para ver su clave de recuperación",
"authToChangeYourEmail": "Por favor, autentifíquese para cambiar su correo electrónico",
"authToChangeYourPassword": "Por favor, autentifíquese para cambiar su contraseña",
@@ -81,6 +103,7 @@
"no": "No",
"email": "Correo electrónico",
"support": "Soporte",
"general": "General",
"settings": "Configuración",
"copied": "Copiado",
"pleaseTryAgain": "Por favor, inténtalo nuevamente",
@@ -90,6 +113,18 @@
"enterYourPasswordHint": "Ingrese su contraseña",
"forgotPassword": "Olvidé mi contraseña",
"oops": "Ups",
"suggestFeatures": "Sugerir funcionalidades",
"faq": "Preguntas Frecuentes",
"faq_q_1": "¿Cuán seguro es ente Auth?",
"faq_a_1": "Todos los códigos que copia de seguridad vía ente se almacenan cifrados de extremo a extremo. Esto significa que solo usted puede acceder a sus códigos. Nuestras aplicaciones son de código abierto y nuestra criptografía ha sido auditada externamente.",
"faq_q_2": "¿Puedo acceder a mis códigos en el escritorio?",
"faq_a_2": "Puede acceder a tus códigos en la web en auth.ente.io.",
"faq_q_3": "¿Cómo puedo borrar códigos?",
"faq_a_3": "Puede eliminar un código deslizando a la izquierda en ese elemento.",
"faq_q_4": "¿Cómo puedo apoyar este proyecto?",
"faq_a_4": "Puedes apoyar el desarrollo de este proyecto suscribiéndote a nuestra app de Fotos en ente.io.",
"faq_q_5": "¿Cómo puedo habilitar bloqueo FaceID en ente Auth",
"faq_a_5": "Puede activar el bloqueo FaceID en Ajustes → Seguridad → Pantalla de bloqueo.",
"somethingWentWrongMessage": "Algo ha ido mal, por favor, prueba otra vez",
"leaveFamily": "Dejar plan familiar",
"leaveFamilyMessage": "¿Está seguro de que desea abandonar el plan familiar?",
@@ -122,8 +157,6 @@
"yesSendFeedbackAction": "Sí, enviar comentarios",
"noDeleteAccountAction": "No, eliminar cuenta",
"initiateAccountDeleteTitle": "Por favor, autentifíquese para iniciar la eliminación de la cuenta",
"confirmAccountDeleteTitle": "¿Estás seguro de que quieres eliminar tu cuenta de ente?",
"confirmAccountDeleteMessage": "Sus datos subidos a través de todas las aplicaciones (fotos y autenticador), se programarán para su eliminación y su cuenta se eliminará permanentemente.",
"sendEmail": "Enviar correo electrónico",
"createNewAccount": "Crear nueva cuenta",
"weakStrength": "Poco segura",
@@ -140,6 +173,7 @@
"authToChangeLockscreenSetting": "Por favor autentifíquese para cambiar la configuración de bloqueo de pantalla",
"lockScreenEnablePreSteps": "Para activar la pantalla de bloqueo, por favor configure la contraseña del dispositivo o el bloqueo de pantalla en los ajustes de sistema.",
"viewActiveSessions": "Ver sesiones activas",
"authToViewYourActiveSessions": "Por favor, autentifíquese para ver sus sesiones activas",
"searchHint": "Buscar...",
"search": "Buscar",
"sorryUnableToGenCode": "Lo sentimos, no se puede generar un código para {issuerName}",
@@ -149,12 +183,14 @@
"enterDetailsManually": "Ingrese los detalles manualmente",
"edit": "Editar",
"copiedToClipboard": "Copiado al portapapeles",
"copiedNextToClipboard": "Copiado el siguiente código al portapapeles",
"error": "Error",
"recoveryKeyCopiedToClipboard": "Llave de recuperación copiada al portapapeles",
"recoveryKeyOnForgotPassword": "Si olvida su contraseña, la única forma de recuperar sus datos es con esta clave.",
"recoveryKeySaveDescription": "Nosotros no almacenamos esta clave, por favor guarde dicha clave de 24 palabras en un lugar seguro.",
"doThisLater": "Hacer esto más tarde",
"saveKey": "Guardar Clave",
"back": "Atrás",
"createAccount": "Crear cuenta",
"passwordStrength": "Fortaleza de la contraseña: {passwordStrengthValue}",
"@passwordStrength": {
@@ -177,6 +213,9 @@
"changePasswordTitle": "Cambiar contraseña",
"resetPasswordTitle": "Restablecer contraseña",
"encryptionKeys": "Claves de cifrado",
"passwordWarning": "No almacenamos esta contraseña, así que si la olvidas, <underline>no podemos descifrar tus datos</underline>",
"enterPasswordToEncrypt": "Introduzca una contraseña que podamos usar para cifrar sus datos",
"enterNewPasswordToEncrypt": "Introduzca una nueva contraseña que podamos usar para cifrar sus datos",
"passwordChangedSuccessfully": "Contraseña cambiada correctamente",
"generatingEncryptionKeys": "Generando claves de encriptación...",
"continueLabel": "Continuar",
@@ -215,6 +254,10 @@
"privacy": "Privacidad",
"terms": "Términos",
"checkForUpdates": "Comprobar actualizaciones",
"downloadUpdate": "Descargar",
"criticalUpdateAvailable": "Actualización crítica disponible",
"updateAvailable": "Actualizacion disponible",
"update": "Actualizar",
"checking": "Comprobando...",
"youAreOnTheLatestVersion": "Está usando la versión más reciente",
"warning": "Atención",
@@ -224,10 +267,74 @@
"description": "Text for the button to confirm the user understands the warning"
},
"authToExportCodes": "Por favor, autentifíquese para exportar sus códigos",
"importSuccessTitle": "¡Hurra!",
"importSuccessDesc": "¡Has importado {count} códigos!",
"@importSuccessDesc": {
"placeholders": {
"count": {
"description": "The number of codes imported",
"type": "int",
"example": "1"
}
}
},
"sorry": "Lo sentimos",
"importFailureDesc": "No se pudo analizar el archivo seleccionado.\n¡Por favor escriba a support@ente.io si necesita ayuda!",
"pendingSyncs": "Atención",
"pendingSyncsWarningBody": "Algunos de sus códigos no han sido respaldados.\n\nPor favor, asegúrese de tener una copia de seguridad de estos códigos antes de cerrar la sesión.",
"checkInboxAndSpamFolder": "Por favor revisa tu bandeja de entrada (y spam) para completar la verificación",
"tapToEnterCode": "Toca para introducir el código",
"resendEmail": "Reenviar correo electrónico",
"weHaveSendEmailTo": "Hemos enviado un correo a <green>{email}</green>",
"@weHaveSendEmailTo": {
"description": "Text to indicate that we have sent a mail to the user",
"placeholders": {
"email": {
"description": "The email address of the user",
"type": "String",
"example": "example@ente.io"
}
}
},
"activeSessions": "Sesiones activas",
"somethingWentWrongPleaseTryAgain": "Algo ha ido mal, por favor, prueba otra vez",
"thisWillLogYouOutOfThisDevice": "¡Esto cerrará la sesión de este dispositivo!",
"thisWillLogYouOutOfTheFollowingDevice": "Esto cerrará la sesión del siguiente dispositivo:",
"terminateSession": "¿Terminar sesión?",
"terminate": "Terminar",
"thisDevice": "Este dispositivo",
"toResetVerifyEmail": "Para restablecer su contraseña, por favor verifique su correo electrónico primero.",
"thisEmailIsAlreadyInUse": "Este correo electrónico ya está en uso",
"verificationFailedPleaseTryAgain": "Verificación fallida, por favor inténtalo de nuevo",
"yourVerificationCodeHasExpired": "Tu código de verificación ha expirado",
"incorrectCode": "Código incorrecto",
"sorryTheCodeYouveEnteredIsIncorrect": "Lo sentimos, el código que ha introducido es incorrecto",
"emailChangedTo": "Correo electrónico cambiado a {newEmail}",
"authenticationFailedPleaseTryAgain": "Error de autenticación, por favor inténtalo de nuevo",
"authenticationSuccessful": "¡Autenticación exitosa!",
"twofactorAuthenticationSuccessfullyReset": "Autenticación de doble factor restablecida con éxito",
"incorrectRecoveryKey": "Clave de recuperación incorrecta",
"theRecoveryKeyYouEnteredIsIncorrect": "La clave de recuperación introducida es incorrecta",
"enterPassword": "Introduzca la contraseña",
"selectExportFormat": "Seleccionar formato para exportar",
"exportDialogDesc": "Las exportaciones cifradas estarán protegidas por una contraseña de su elección.",
"encrypted": "Cifrado",
"plainText": "Texto sin formato",
"passwordToEncryptExport": "Contraseña para cifrar la exportación",
"export": "Exportar",
"useOffline": "Usar sin copias de seguridad",
"signInToBackup": "Inicia sesión para hacer copia de tus códigos",
"singIn": "Iniciar sesión",
"sigInBackupReminder": "Por favor, exporte sus códigos para asegurarse de que tiene una copia de seguridad de la que puede restaurar.",
"offlineModeWarning": "Ha elegido proceder sin copia de seguridad. Por favor, tome copias de seguridad manuales para asegurarse de que sus códigos están seguros.",
"showLargeIcons": "Mostrar iconos grandes",
"shouldHideCode": "Ocultar códigos",
"focusOnSearchBar": "Enfocar búsqueda al iniciar la aplicación",
"confirmUpdatingkey": "¿Estás seguro de que deseas actualizar la clave secreto?",
"minimizeAppOnCopy": "Minimizar aplicación al copiar",
"editCodeAuthMessage": "Autenticar para editar código",
"deleteCodeAuthMessage": "Autenticar para borrar código",
"showQRAuthMessage": "Autenticar para mostrar código QR"
"showQRAuthMessage": "Autenticar para mostrar código QR",
"confirmAccountDeleteTitle": "Confirmar eliminación de la cuenta",
"confirmAccountDeleteMessage": "Esta cuenta está vinculada a otras aplicaciones de ente, si utiliza alguna.\n\nSe programará la eliminación de los datos que cargue en todas las aplicaciones de ente y su cuenta se eliminará permanentemente."
}

View File

@@ -121,8 +121,6 @@
"yesSendFeedbackAction": "بله، ارسال بازخورد",
"noDeleteAccountAction": "خیر، حساب کاربری را حذف کن",
"initiateAccountDeleteTitle": "لطفا جهت شروع فرآیند حذف حساب کاربری، اعتبارسنجی کنید",
"confirmAccountDeleteTitle": "آیا از حذف حساب کاربری خود اطمینان دارید؟",
"confirmAccountDeleteMessage": "داده‌های آپلود شده شما، در همه برنامه‌ها(هر دو برنامه عکس‌ها و احراز هویت)، طبق زمانبندی و حساب کاربری شما برای همیشه حذف خواهد شد.",
"sendEmail": "ارسال ایمیل",
"createNewAccount": "ایجاد حساب کاربری جدید",
"weakStrength": "ضعیف",

View File

@@ -110,8 +110,6 @@
"yesSendFeedbackAction": "Kyllä, lähetä palautetta",
"noDeleteAccountAction": "En, poista tili",
"initiateAccountDeleteTitle": "Ole hyvä ja tee todennus käynnistääksesi tilisi poistoprosessin",
"confirmAccountDeleteTitle": "Haluatko varmasti poistaa Ente-tilisi?",
"confirmAccountDeleteMessage": "Lataamasi tiedot kaikkien sovellusten kesken (molemmat, sekä kuvat ja todenteet) ajastetaan poistettavaksi ja tilisi poistetaan pysyvästi.",
"sendEmail": "Lähetä sähköpostia",
"createNewAccount": "Luo uusi tili",
"weakStrength": "Heikko salasana",

View File

@@ -157,8 +157,6 @@
"yesSendFeedbackAction": "Oui, envoyer un commentaire",
"noDeleteAccountAction": "Non, supprimer le compte",
"initiateAccountDeleteTitle": "Veuillez vous authentifier pour débuter la suppression du compte",
"confirmAccountDeleteTitle": "Êtes-vous sûr de vouloir supprimer votre compte ente ?",
"confirmAccountDeleteMessage": "Vos données, à travers toutes les applications (Photos et Authenticator), seront programmées pour être supprimées et votre compte sera définitivement supprimé.",
"sendEmail": "Envoyer un e-mail",
"createNewAccount": "Créer un nouveau compte",
"weakStrength": "Faible",
@@ -336,5 +334,7 @@
"minimizeAppOnCopy": "Réduire l'application après la copie",
"editCodeAuthMessage": "Authentification requise pour modifier le code",
"deleteCodeAuthMessage": "Authentification requise pour supprimer le code",
"showQRAuthMessage": "Authentification requise pour afficher le code QR"
"showQRAuthMessage": "Authentification requise pour afficher le code QR",
"confirmAccountDeleteTitle": "Confirmer la suppression du compte",
"confirmAccountDeleteMessage": "Ce compte peut être lié à d'autres applications ente.\n\nVos données seront bientôt effacées de toutes les applications et votre compte sera définitivement supprimé."
}

View File

@@ -157,8 +157,6 @@
"yesSendFeedbackAction": "כן, שלח משוב",
"noDeleteAccountAction": "לא, מחק את החשבון",
"initiateAccountDeleteTitle": "אנא אמת על מנת להתחיל את מחיקת החשבון שלך",
"confirmAccountDeleteTitle": "האם אתה בטוח שברצונך למחוק את חשבונך?",
"confirmAccountDeleteMessage": "המידע שלך שהועלה, ברחבי כל האפליקציות (גם ב-Photos וב-Authenticator), יהיה מועמד למחיקה, וגם המחשבון שלך ימחק לצמיתות.",
"sendEmail": "שלח אימייל",
"createNewAccount": "צור חשבון חדש",
"weakStrength": "חלש",

View File

@@ -157,8 +157,6 @@
"yesSendFeedbackAction": "Sì, invia un feedback",
"noDeleteAccountAction": "No, elimina l'account",
"initiateAccountDeleteTitle": "Si prega di autenticarsi per avviare l'eliminazione dell'account",
"confirmAccountDeleteTitle": "Sei sicuro di voler eliminare il tuo account?",
"confirmAccountDeleteMessage": "I tuoi dati caricati, in tutte le app (sia foto che autenticatore), verranno pianificati per la cancellazione, e il tuo account sarà eliminato in modo permanente.",
"sendEmail": "Invia email",
"createNewAccount": "Crea un nuovo account",
"weakStrength": "Debole",
@@ -336,5 +334,7 @@
"minimizeAppOnCopy": "Riduci a icona l'app dopo la copia",
"editCodeAuthMessage": "Autenticarsi per modificare il codice",
"deleteCodeAuthMessage": "Autenticarsi per cancellare il codice",
"showQRAuthMessage": "Autenticarsi per mostrare il codice QR"
"showQRAuthMessage": "Autenticarsi per mostrare il codice QR",
"confirmAccountDeleteTitle": "Conferma l'eliminazione dell'account",
"confirmAccountDeleteMessage": "Questo account è collegato ad altre app di ente, se ne utilizzi.\n\nI tuoi dati caricati, su tutte le app di ente, saranno pianificati per la cancellazione e il tuo account verrà eliminato definitivamente."
}

View File

@@ -1,32 +1,37 @@
{
"account": "アカウント",
"recoveryKey": "回復キー",
"counterAppBarTitle": "カウンタ",
"counterAppBarTitle": "カウンタ",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"
},
"onBoardingBody": "2要素認証コードの保護",
"onBoardingBody": "2要素認証コードを安全にバックアップ",
"onBoardingGetStarted": "さあ、はじめよう",
"setupFirstAccount": "最初のアカウントを設定しましょう",
"importScanQrCode": "QRコードをスキャン",
"importEnterSetupKey": "セットアップキーを入力",
"importScanQrCode": "QR コードを読み取り",
"qrCode": "QR コード",
"importEnterSetupKey": "手動で入力",
"importAccountPageTitle": "アカウントの詳細を入力",
"secretCanNotBeEmpty": "秘密鍵は空欄にできません",
"bothIssuerAndAccountCanNotBeEmpty": "発行者とアカウントの両方を空欄にはできません",
"incorrectDetails": "不正な詳細",
"pleaseVerifyDetails": "詳細を確認し、もう一度お試しください",
"codeIssuerHint": "発行者",
"codeSecretKeyHint": "シークレットキー",
"codeSecretKeyHint": "秘密鍵",
"codeAccountHint": "アカウント (you@domain.com)",
"accountKeyType": "キーの種類",
"sessionExpired": "セッションの有効期間超過",
"accountKeyType": "の種類",
"sessionExpired": "セッションが失効しました",
"@sessionExpired": {
"description": "Title of the dialog when the users current session is invalid/expired"
},
"pleaseLoginAgain": "再度ログインしてください",
"loggingOut": "ログアウト中...",
"timeBasedKeyType": "時間ベース (TOTP)",
"counterBasedKeyType": "カウンタベース (HOTP)",
"counterBasedKeyType": "カウンタベース (HOTP)",
"saveAction": "保存",
"nextTotpTitle": "次へ",
"deleteCodeTitle": "コードを削除しますか?",
"deleteCodeMessage": "このコードを削除してもよろしいですか? このアクションは元に戻せません。",
"deleteCodeTitle": "コードを削除しますか",
"deleteCodeMessage": "本当にこのコードを削除してもよろしいですか?この決定は元に戻せません。",
"viewLogsAction": "ログの表示",
"sendLogsDescription": "問題のデバッグに役立つログが送信されます。機密情報が記録されないように予防措置を講じていますが、共有する前にこれらのログを確認することをお勧めします。",
"preparingLogsTitle": "ログを準備中...",
@@ -40,7 +45,7 @@
}
},
"copyEmailAction": "メールをコピー",
"exportLogsAction": "ログエクスポート",
"exportLogsAction": "ログエクスポート",
"reportABug": "バグを報告",
"crashAndErrorReporting": "クラッシュとエラーの報告",
"reportBug": "バグを報告",
@@ -53,25 +58,41 @@
}
},
"contactSupport": "サポートに問い合わせ",
"rateUsOnStore": "{storeName} で評価",
"blog": "ブログ",
"merchandise": "グッズ",
"verifyPassword": "パスワードを確認",
"pleaseWait": "お待ちください...",
"generatingEncryptionKeysTitle": "暗号化キーを生成中...",
"recreatePassword": "パスワードを再設定",
"recreatePasswordMessage": "現在のデバイスは、パスワードを確認するのに十分ではありません。全てのデバイスで利用できるように再生成する必要があります。\n\nリカバリーキーを使用してログインし、パスワードを再生成してください (ご希望の場合は再度同じパスワードを使用できます)",
"generatingEncryptionKeysTitle": "暗号化を生成中...",
"recreatePassword": "パスワードを再作成",
"recreatePasswordMessage": "現在のデバイスは、パスワードを確認するのに十分ではありません。全てのデバイスで利用できるように再生成する必要があります。\n\n回復キーを使用してログインし、パスワードを再生成してください (ご希望の場合は再度同じパスワードを使用できます)",
"useRecoveryKey": "回復キーを使用",
"incorrectPasswordTitle": "パスワードが正しくありません",
"welcomeBack": "おかえりなさい!",
"welcomeBack": "おかえりなさい",
"madeWithLoveAtPrefix": "made with ❤️ at ",
"supportDevs": "プロジェクト支援のために <bold-green>ente</bold-green> に登録",
"supportDiscount": "クーポンコード \"AUTH\" 使用すると、初年度が10%オフになります。",
"supportDiscount": "クーポンコード \"AUTH\" 使用初年度が 10% オフに",
"changeEmail": "メールアドレスを変更",
"changePassword": "パスワードを変更",
"data": "データ",
"importCodes": "コードをインポート",
"importTypePlainText": "プレーンテキスト",
"importTypeEnteEncrypted": "ente 暗号化エクスポート",
"passwordForDecryptingExport": "復号化用パスワード",
"passwordEmptyError": "パスワードは空欄にできません",
"importFromApp": "{appName} からコードをインポート",
"importGoogleAuthGuide": "Google Authenticator の \"アカウントを転送\" オプションを使用してあなたのアカウントを QR コードにエクスポートしてください。その後、他のデバイスで QR コードを読み取ってください。\n\nヒント: ノートパソコンのウェブカメラを使用して QR コードを撮影することができます。",
"importSelectJsonFile": "JSON ファイルを選択",
"importEnteEncGuide": "ente からエクスポートされた暗号化 JSON ファイルを選択",
"importRaivoGuide": "Raivo の設定の \"OTP を zip アーカイブにエクスポート\" を使用してください。\n\nzip ファイルを解凍し JSON ファイルをインポートしてください。",
"importAegisGuide": "Aegis の設定の \"保管庫をエクスポート\" を使用してください。\n\n保管庫が暗号化されている場合、保管庫を復号するためにパスワードの入力が必要になります。",
"exportCodes": "コードをエクスポート",
"importInstruction": "以下のフォーマットで、コードのリストを含むファイルを選択してください",
"importLabel": "インポート",
"importInstruction": "以下の形式のコードのリストを含むファイルを選択してください",
"importCodeDelimiterInfo": "コードはカンマまたは改行で区切ることができます",
"selectFile": "ファイルを選択",
"emailVerificationToggle": "メール認証",
"authToChangeEmailVerificationSetting": "メール認証を変更するためには認証が必要です",
"authToViewYourRecoveryKey": "回復キーを表示するためには認証が必要です",
"authToChangeYourEmail": "メールアドレスを変更するためには認証が必要です",
"authToChangeYourPassword": "パスワードを変更するためには認証が必要です",
@@ -79,8 +100,9 @@
"cancel": "キャンセル",
"yes": "はい",
"no": "いいえ",
"email": "Eメール",
"email": "E メール",
"support": "サポート",
"general": "一般",
"settings": "設定",
"copied": "コピーしました",
"pleaseTryAgain": "再度お試しください",
@@ -90,6 +112,12 @@
"enterYourPasswordHint": "パスワードを入力してください",
"forgotPassword": "パスワードを忘れた場合",
"oops": "おっと",
"suggestFeatures": "機能を提案",
"faq": "FAQ",
"faq_q_1": "ente Auth はどのくらい安全ですか?",
"faq_a_1": "ente でバックアップされたすべてのコードはエンドツーエンドで暗号化されて保管されます。これはあなただけがコードにアクセスできることを意味します。私たちのアプリはオープンソースであり、私たちの暗号は外部有識者によって検証済みです。",
"faq_q_2": "パソコンから私のコードにアクセスできますか?",
"faq_a_2": "auth.ente.io で Web からコードにアクセス可能です。",
"somethingWentWrongMessage": "問題が発生しました、再試行してください",
"leaveFamily": "ファミリーから退会",
"leaveFamilyMessage": "本当にファミリープランを退会しますか?",
@@ -122,8 +150,6 @@
"yesSendFeedbackAction": "はい、フィードバックを送信します",
"noDeleteAccountAction": "いいえ、アカウントを削除します",
"initiateAccountDeleteTitle": "アカウント削除を開始するには認証してください",
"confirmAccountDeleteTitle": "本当に ente アカウントを削除してよろしいですか?",
"confirmAccountDeleteMessage": "アップロードされたデータは、すべてのアプリ(写真と認証アプリの両方) で削除がスケジュールされます。アカウントは完全に削除されます。",
"sendEmail": "メール送信",
"createNewAccount": "新規アカウント作成",
"weakStrength": "脆弱",

92
lib/l10n/arb/app_ka.arb Normal file
View File

@@ -0,0 +1,92 @@
{
"account": "ანგარიში",
"unlock": "განბლოკვა",
"recoveryKey": "აღდგენის კოდი",
"counterAppBarTitle": "მრიცხველზე დაფუძნებული",
"@counterAppBarTitle": {
"description": "Text shown in the AppBar of the Counter Page"
},
"onBoardingBody": "შექმენით თქვენი ორმხრივი აუთენთიფიკაციის კოდების სარეზერვო ასლი უსაფრთხოდ",
"onBoardingGetStarted": "დაწყება",
"setupFirstAccount": "დააყენეთ თქვენი პირველი ანგარიში",
"importScanQrCode": "QR კოდის დასკანერება",
"qrCode": "QR კოდი",
"importEnterSetupKey": "შეიყვანეთ დაყენების კოდი",
"importAccountPageTitle": "შეიყვანეთ ანგარიშის დეტალები",
"secretCanNotBeEmpty": "გასაღების ველი არ შეიძლება ცარიელი იყოს",
"bothIssuerAndAccountCanNotBeEmpty": "ანგარიშის მომწოდებლისა და ანგარიშის ველი არ შეიძლება ცარიელი იყოს",
"incorrectDetails": "არასწორი მონაცემები",
"pleaseVerifyDetails": "გთხოვთ, გადაამოწმოთ დეტალები და სცადოთ ხელახლა",
"codeIssuerHint": "მომწოდებელი",
"codeSecretKeyHint": "გასაღები",
"codeAccountHint": "ანგარიში (you@domain.com)",
"accountKeyType": "გასაღების ტიპი",
"sessionExpired": "სესიის დრო ამოიწურა",
"@sessionExpired": {
"description": "Title of the dialog when the users current session is invalid/expired"
},
"pleaseLoginAgain": "გთხოვთ, გაიაროთ ავტორიზაცია ხელახლა",
"loggingOut": "მიმდინარეობს გამოსვლა...",
"timeBasedKeyType": "დროზე დაფუძნებული (TOTP)",
"counterBasedKeyType": "მრიცხველზე დაფუძნებული (HOTP)",
"saveAction": "შენახვა",
"nextTotpTitle": "შემდეგი",
"deleteCodeTitle": "გსურთ კოდის წაშლა?",
"deleteCodeMessage": "დარწმუნებული ხართ რომ გსურთ ამ კოდის წაშლა? ამ მოქმედების გაუქმება შეუძლებელია.",
"viewLogsAction": "აღრიცხვის ფაილების ნახვა",
"sendLogsDescription": "თქვენი პრობლემის აღმოსაფხვრელად, ეს ქმედება გააგზავნის აღრიცხვის ფაილებს. მიუხედავად იმისა, რომ ჩვენ ვიღებთ უსაფრთხოების ზომებს, რათა სენსიტიური ინფორმაცია არ მოხვდეს აღრიცხვის ფაილებში, გაგზავნამდე, გირჩევთ, გადახედოთ აღრიცხვის ფაილებს.",
"preparingLogsTitle": "მიმდინარეობს აღრიცხვის ფაილების მზადება...",
"emailLogsTitle": "აღრიცხვის ფაილების ელექტრონული ფოსტით გაგზავნა",
"emailLogsMessage": "გთხოვთ, გამოაგზავნოთ აღრიცხვის ფაილები {email}-ზე",
"@emailLogsMessage": {
"placeholders": {
"email": {
"type": "String"
}
}
},
"copyEmailAction": "ელექტრონული ფოსტის დაკოპირება",
"exportLogsAction": "აღრიცხვის ფაილების ექსპორტირება",
"reportABug": "პრობლემის შესახებ შეტყობინება",
"crashAndErrorReporting": "აპლიკაციის ხარვეზის & პრობლემის შეტყობინება",
"reportBug": "პრობლემის შეტყობინება",
"emailUsMessage": "გთხოვთ, მოგვწეროთ ელექტრონულ ფოსტაზე {email}",
"@emailUsMessage": {
"placeholders": {
"email": {
"type": "String"
}
}
},
"contactSupport": "მხარდაჭერის გუნდთან დაკავშირება",
"rateUsOnStore": "შეგვაფასეთ {storeName}-ზე",
"blog": "ბლოგი",
"merchandise": "მერჩანტი",
"verifyPassword": "პაროლის დასტური",
"pleaseWait": "გთხოვთ, დაელოდოთ...",
"generatingEncryptionKeysTitle": "მიმდინარეობს დაშიფრვის გასაღებების გენერირება...",
"recreatePassword": "პაროლის ხელახლა შექმნა",
"incorrectPasswordTitle": "არასწორი პაროლი",
"welcomeBack": "კეთილი იყოს თქვენი დაბრუნება!",
"madeWithLoveAtPrefix": "შეიქმნა ❤️ ",
"changeEmail": "ელექტრონული ფოსტის შეცვლა",
"changePassword": "პაროლის შეცვლა",
"data": "მონაცემები",
"importCodes": "კოდების იმპორტირება",
"importTypePlainText": "სტანდარტული ტექსტი",
"importTypeEnteEncrypted": "ente დაშიფრული ექსპორტი",
"passwordForDecryptingExport": "ექსპორტის გაშიფრვის პაროლი",
"passwordEmptyError": "პაროლის ველი არ შეიძლება იყოს ცარიელი",
"emailVerificationToggle": "ელექტრონული ფოსტის ვერიფიკაცია",
"cancel": "გაუქმება",
"yes": "დიახ",
"no": "არა",
"email": "ელექტრონული ფოსტა",
"support": "მხარდაჭერა",
"general": "ზოგადი",
"settings": "პარამეტრები",
"copied": "დაკოპირებულია",
"pleaseTryAgain": "გთხოვთ, სცადოთ ხელახლა",
"existingUser": "არსებული მომხმარებელი",
"delete": "წაშლა"
}

View File

@@ -92,6 +92,7 @@
"importCodeDelimiterInfo": "De codes mogen gescheiden worden door een komma of een nieuwe regel",
"selectFile": "Bestand selecteren",
"emailVerificationToggle": "E-mailverificatie",
"emailVerificationEnableWarning": "Als u de 2FA van uw e-mail bij ons opslaat, kan de verificatie van die e-mail resulteren in een riskante impasse. Als u bent uitgesloten van een van beide diensten, kunt u zich mogelijk niet meer aanmelden bij de andere.",
"authToChangeEmailVerificationSetting": "Gelieve te verifiëren om de e-mailverificatie te wijzigen",
"authToViewYourRecoveryKey": "Graag verifiëren om uw herstelsleutel te bekijken",
"authToChangeYourEmail": "Graag verifiëren om je e-mailadres te wijzigen",
@@ -156,8 +157,6 @@
"yesSendFeedbackAction": "Ja, geef feedback",
"noDeleteAccountAction": "Nee, verwijder account",
"initiateAccountDeleteTitle": "Gelieve te verifiëren om het account te verwijderen",
"confirmAccountDeleteTitle": "Weet je zeker dat je je ente account wil verwijderen?",
"confirmAccountDeleteMessage": "Uw geüploade gegevens, in alle apps (Photos en Authenticator), worden ingepland voor verwijdering en uw account zal permanent worden verwijderd.",
"sendEmail": "E-mail versturen",
"createNewAccount": "Nieuw account aanmaken",
"weakStrength": "Zwak",
@@ -329,8 +328,13 @@
"sigInBackupReminder": "Exporteer de codes zodat je een back-up hebt waarvandaan je kan herstellen.",
"offlineModeWarning": "Je hebt ervoor gekozen om verder te gaan zonder back-ups. Neem handmatige back-ups om ervoor te zorgen dat jouw codes veilig zijn.",
"showLargeIcons": "Grote iconen",
"shouldHideCode": "Verberg codes",
"focusOnSearchBar": "Focus zoekveld na starten app",
"confirmUpdatingkey": "Weet u zeker dat u de geheime sleutel wilt bijwerken?",
"minimizeAppOnCopy": "Na kopiëren app minimaliseren",
"editCodeAuthMessage": "Authenticeren om code te bewerken",
"deleteCodeAuthMessage": "Authenticeren om code te verwijderen",
"showQRAuthMessage": "Authenticeren om QR-code te tonen"
"showQRAuthMessage": "Authenticeren om QR-code te tonen",
"confirmAccountDeleteTitle": "Account verwijderen bevestigen",
"confirmAccountDeleteMessage": "Dit account is gekoppeld aan andere ente apps, als je er gebruik van maakt.\n\nJe geüploade gegevens worden in alle ente apps gepland voor verwijdering, en je account wordt permanent verwijderd voor alle ente diensten."
}

View File

@@ -9,8 +9,13 @@
"onBoardingGetStarted": "Wprowadzenie",
"setupFirstAccount": "Skonfiguruj pierwsze konto",
"importScanQrCode": "Zeskanuj kod QR",
"qrCode": "Kod QR",
"importEnterSetupKey": "Wprowadź klucz",
"importAccountPageTitle": "Wprowadź dane konta",
"secretCanNotBeEmpty": "Sekret nie może być pusty",
"bothIssuerAndAccountCanNotBeEmpty": "Pola wydawca i konto nie mogą być puste",
"incorrectDetails": "Nieprawidłowe szczegóły",
"pleaseVerifyDetails": "Proszę zweryfikować szczegóły i spróbuj ponownie",
"codeIssuerHint": "Wydawca",
"codeSecretKeyHint": "Tajny klucz",
"codeAccountHint": "Konto (ty@domena.com)",
@@ -53,6 +58,9 @@
}
},
"contactSupport": "Skontaktuj się z pomocą techniczną",
"rateUsOnStore": "Oceń nas na {storeName}",
"blog": "Blog",
"merchandise": "Sklep",
"verifyPassword": "Zweryfikuj hasło",
"pleaseWait": "Proszę czekać...",
"generatingEncryptionKeysTitle": "Generowanie kluczy szyfrujących...",
@@ -68,10 +76,24 @@
"changePassword": "Zmień hasło",
"data": "Dane",
"importCodes": "Importuj kody",
"importTypePlainText": "Zwykły tekst",
"importTypeEnteEncrypted": "Zaszyfrowany eksport ente",
"passwordForDecryptingExport": "Hasło do odszyfrowania eksportu",
"passwordEmptyError": "Pole hasło nie może być puste",
"importFromApp": "Importuj kody z {appName}",
"importGoogleAuthGuide": "Wyeksportuj twoje konta z Google Authenticator do kodu QR używając opcji \"Przenieś konta\". Potem używając innego urządzenia, zeskanuj kod QR.",
"importSelectJsonFile": "Wybierz plik JSON",
"importEnteEncGuide": "Wybierz zaszyfrowany plik JSON wyeksportowany z ente",
"importRaivoGuide": "Użyj opcji \"Eksportuj OTP do archiwum ZIP\" w Ustawieniach Raivo.\n\nWyodrębnij plik zip i zaimportuj plik JSON.",
"importAegisGuide": "Użyj opcji \"Eksportuj sejf\" w ustawieniach Aegis.\n\nJeśli twój sejf jest zaszyfrowany, musisz wprowadzić hasło sejfu, aby odszyfrować sejf.",
"exportCodes": "Eksportuj kody",
"importLabel": "Importuj",
"importInstruction": "Wybierz plik, który zawiera listę twoich kodów w następującym formacie",
"importCodeDelimiterInfo": "Kody mogą być oddzielone przecinkiem lub nową linią",
"selectFile": "Wybierz plik",
"emailVerificationToggle": "Weryfikacja e-mail",
"emailVerificationEnableWarning": "Jeśli przechowujesz uwierzytelnianie dwuskładnikowe do twojego e-mailu z nami, włączenie weryfikacji adresu e-mail może spowodować impas. Jeśli jesteś zablokowany z jednej usługi, możesz nie być w stanie zalogować się do drugiej.",
"authToChangeEmailVerificationSetting": "Proszę uwierzytelnić, aby zmienić weryfikację e-mail",
"authToViewYourRecoveryKey": "Proszę uwierzytelnić, aby wyświetlić swój klucz odzyskiwania",
"authToChangeYourEmail": "Proszę uwierzytelnić, aby zmienić swój adres e-mail",
"authToChangeYourPassword": "Proszę uwierzytelnić, aby zmienić hasło",
@@ -81,6 +103,7 @@
"no": "Nie",
"email": "Email",
"support": "Wsparcie techniczne",
"general": "Ogólne",
"settings": "Ustawienia",
"copied": "Skopiowano",
"pleaseTryAgain": "Proszę spróbować ponownie",
@@ -90,6 +113,18 @@
"enterYourPasswordHint": "Wprowadź swoje hasło",
"forgotPassword": "Nie pamiętam hasła",
"oops": "Ups",
"suggestFeatures": "Zaproponuj funkcje",
"faq": "Najczęściej zadawane pytania (FAQ)",
"faq_q_1": "Jak bezpieczny jest ente Auth?",
"faq_a_1": "Wszystkie kody, których tworzysz kopię zapasową za pomocą ente są przechowywane zaszyfrowane end-to-end. Oznacza to, że tylko Ty możesz uzyskać dostęp do swoich kodów. Nasze aplikacje są otwarto-źródłowe, a nasza kryptografia została poddana sprawdzeniu z zewnątrz.",
"faq_q_2": "Czy mogę uzyskać dostęp do moich kodów na komputerze?",
"faq_a_2": "Możesz uzyskać dostęp do swoich kodów na stronie auth.ente.io.",
"faq_q_3": "Jak mogę usunąć kody?",
"faq_a_3": "Możesz usunąć kod, przesuwając go w lewo.",
"faq_q_4": "Jak mogę wesprzeć ten projekt?",
"faq_a_4": "Możesz wspierać rozwój tego projektu, subskrybując do naszej aplikacji Zdjęcia na ente.io.",
"faq_q_5": "Jak mogę włączyć blokadę FaceID w ente Auth?",
"faq_a_5": "Możesz włączyć blokadę FaceID w Ustawienia → Bezpieczeństwo→ Ekran blokady.",
"somethingWentWrongMessage": "Coś poszło nie tak. Proszę, spróbuj ponownie",
"leaveFamily": "Opuść rodzinę",
"leaveFamilyMessage": "Czy jesteś pewien/pewna, że chcesz opuścić plan rodzinny?",
@@ -122,8 +157,6 @@
"yesSendFeedbackAction": "Tak, wyślij opinię",
"noDeleteAccountAction": "Nie, usuń moje konto",
"initiateAccountDeleteTitle": "Proszę uwierzytelnić, aby zainicjować usuwanie konta",
"confirmAccountDeleteTitle": "Na pewno chcesz usunąć swoje konto ente?",
"confirmAccountDeleteMessage": "Twoje przesłane dane, we wszystkich aplikacjach (zarówno Zdjęcia, jak i Authenticator), zostaną zaplanowane do usunięcia, a Twoje konto zostanie trwale usunięte.",
"sendEmail": "Wyślij e-mail",
"createNewAccount": "Utwórz nowe konto",
"weakStrength": "Słabe",
@@ -150,12 +183,14 @@
"enterDetailsManually": "Wprowadź dane ręcznie",
"edit": "Edytuj",
"copiedToClipboard": "Skopiowano do schowka",
"copiedNextToClipboard": "Skopiowano następny kod do schowka",
"error": "Błąd",
"recoveryKeyCopiedToClipboard": "Klucz odzyskiwania został skopiowany do schowka",
"recoveryKeyOnForgotPassword": "Jeśli zapomnisz hasła, jedynym sposobem na odzyskanie danych jest ten klucz.",
"recoveryKeySaveDescription": "Nie przechowujemy tego klucza, proszę zachować ten 24 wyrazowy klucz w bezpiecznym miejscu.",
"doThisLater": "Zrób To Później",
"saveKey": "Zapisz klucz",
"back": "Wstecz",
"createAccount": "Utwórz konto",
"passwordStrength": "Siła hasła: {passwordStrengthValue}",
"@passwordStrength": {
@@ -219,6 +254,10 @@
"privacy": "Prywatność",
"terms": "Warunki",
"checkForUpdates": "Sprawdź czy są dostępne nowe aktualizacje",
"downloadUpdate": "Pobierz",
"criticalUpdateAvailable": "Dostępna jest krytyczna aktualizacja",
"updateAvailable": "Dostępna jest aktualizacja",
"update": "Aktualizuj",
"checking": "Sprawdzanie...",
"youAreOnTheLatestVersion": "Używasz najnowszej wersji",
"warning": "Ostrzeżenie",
@@ -264,7 +303,38 @@
"terminateSession": "Zakończyć sesję?",
"terminate": "Zakończ",
"thisDevice": "To urządzenie",
"toResetVerifyEmail": "Aby zresetować hasło, najpierw zweryfikuj swój e-mail.",
"thisEmailIsAlreadyInUse": "Ten adres e-mail już jest zajęty",
"verificationFailedPleaseTryAgain": "Weryfikacja nie powiodła się, spróbuj ponownie",
"yourVerificationCodeHasExpired": "Twój kod weryfikacyjny wygasł",
"incorrectCode": "Nieprawidłowy kod",
"sorryTheCodeYouveEnteredIsIncorrect": "Niestety, wprowadzony kod jest nieprawidłowy",
"emailChangedTo": "Adres e-mail został zmieniony na {newEmail}",
"authenticationFailedPleaseTryAgain": "Uwierzytelnianie nie powiodło się, proszę spróbować ponownie",
"authenticationSuccessful": "Uwierzytelnianie powiodło się!",
"twofactorAuthenticationSuccessfullyReset": "Pomyślnie zresetowano uwierzytelnianie dwuskładnikowe",
"incorrectRecoveryKey": "Nieprawidłowy klucz odzyskiwania",
"theRecoveryKeyYouEnteredIsIncorrect": "Wprowadzony klucz odzyskiwania jest nieprawidłowy",
"enterPassword": "Wprowadź hasło",
"selectExportFormat": "Wybierz format eksportu",
"exportDialogDesc": "Zaszyfrowane eksporty będą chronione przez wybrane przez Ciebie hasło.",
"encrypted": "Zaszyfrowane",
"plainText": "Zwykły tekst",
"passwordToEncryptExport": "Hasło do odszyfrowania eksportu",
"export": "Eksportuj",
"useOffline": "Używaj bez kopii zapasowych",
"signInToBackup": "Zaloguj się, aby wykonać kopię zapasową swoich kodów",
"singIn": "Zaloguj się",
"sigInBackupReminder": "Proszę wyeksportować swoje kody, aby upewnić się, że masz kopię zapasową, z której możesz przywrócić swoje kody.",
"offlineModeWarning": "Wybrałeś kontynuację bez kopii zapasowych. Proszę wykonywać ręczne kopie zapasowe, aby upewnić się, że Twoje kody są bezpieczne.",
"showLargeIcons": "Pokaż duże ikony",
"shouldHideCode": "Ukryj kody",
"focusOnSearchBar": "Uaktywnij wyszukiwanie przy uruchamianiu aplikacji",
"confirmUpdatingkey": "Czy na pewno chcesz zaktualizować tajny klucz?",
"minimizeAppOnCopy": "Minimalizuj aplikację przy kopiowaniu",
"editCodeAuthMessage": "Uwierzytelnij, aby edytować kod",
"deleteCodeAuthMessage": "Uwierzytelnij, aby usunąć kod",
"showQRAuthMessage": "Uwierzytelnij, aby pokazać kod QR"
"showQRAuthMessage": "Uwierzytelnij, aby pokazać kod QR",
"confirmAccountDeleteTitle": "Potwierdź usunięcie konta",
"confirmAccountDeleteMessage": "To konto jest połączone z innymi aplikacjami ente, jeśli ich używasz.\n\nTwoje przesłane dane, we wszystkich aplikacjach ente, zostaną zaplanowane do usunięcia, a Twoje konto zostanie trwale usunięte."
}

View File

@@ -120,8 +120,6 @@
"yesSendFeedbackAction": "Sim, enviar feedback",
"noDeleteAccountAction": "Não, excluir conta",
"initiateAccountDeleteTitle": "Por favor, autentique-se para iniciar a exclusão de conta",
"confirmAccountDeleteTitle": "Tem certeza que deseja excluir sua conta ente?",
"confirmAccountDeleteMessage": "Seus dados enviados, através de todos os aplicativos (Photos e Authenticator), serão agendados para exclusão, e sua conta será permanentemente deletada.",
"sendEmail": "Enviar e-mail",
"createNewAccount": "Criar nova conta",
"weakStrength": "Fraca",

View File

@@ -1,5 +1,6 @@
{
"account": "Аккаунт",
"unlock": "Разблокировать",
"recoveryKey": "Ключ восстановления",
"counterAppBarTitle": "Счетчик",
"@counterAppBarTitle": {
@@ -85,6 +86,7 @@
"importSelectJsonFile": "Выбрать JSON-файл",
"importEnteEncGuide": "Выберите зашифрованный JSON-файл, экспортированный из ente",
"importRaivoGuide": "Используйте опцию «Export OTPs to Zip archive» в настройках Raivo.\n\nРаспакуйте zip-архив и импортируйте JSON-файл.",
"importBitwardenGuide": "Используйте опцию \"Экспортировать хранилище\" в Bitwarden Tools и импортируйте незашифрованный JSON файл.",
"importAegisGuide": "Используйте опцию «Экспортировать хранилище» в настройках Aegis.\n\nЕсли ваше хранилище зашифровано, то для его расшифровки потребуется ввести пароль хранилища.",
"exportCodes": "Экспортировать коды",
"importLabel": "Импорт",
@@ -92,16 +94,19 @@
"importCodeDelimiterInfo": "Коды могут быть разделены запятой или новой строкой",
"selectFile": "Выбрать файл",
"emailVerificationToggle": "Подтверждение электронной почты",
"emailVerificationEnableWarning": "Если вы храните у нас двухфакторную аутентификацию в своей электронной почте, включение проверки электронной почты может привести к тупиковой ситуации. Если у вас заблокирован доступ к одной службе, возможно, вы не сможете войти в другую.",
"authToChangeEmailVerificationSetting": "Авторизуйтесь, чтобы изменить подтверждение электронной почты",
"authToViewYourRecoveryKey": "Пожалуйста, авторизуйтесь для просмотра вашего ключа восстановления",
"authToChangeYourEmail": "Пожалуйста, авторизуйтесь, чтобы изменить адрес электронной почты",
"authToChangeYourPassword": "Пожалуйста, авторизуйтесь, чтобы изменить пароль",
"authToViewSecrets": "Пожалуйста, авторизуйтесь для просмотра ваших секретов",
"ok": "Ок",
"cancel": "Отменить",
"yes": "Да",
"no": "Нет",
"email": "Электронная почта",
"support": "Поддержка",
"general": "Общие",
"settings": "Настройки",
"copied": "Скопировано",
"pleaseTryAgain": "Пожалуйста, попробуйте ещё раз",
@@ -155,8 +160,6 @@
"yesSendFeedbackAction": "Да, отправить отзыв",
"noDeleteAccountAction": "Нет, удалить аккаунт",
"initiateAccountDeleteTitle": "Пожалуйста, авторизуйтесь, чтобы начать удаление аккаунта",
"confirmAccountDeleteTitle": "Вы уверены, что хотите удалить ваш аккаунт ente?",
"confirmAccountDeleteMessage": "Ваши загруженные данные во всех приложениях (как фотографии, так и средство аутентификации) будут запланированы к удалению, а ваш аккаунт будет удален безвозвратно.",
"sendEmail": "Отправить электронное письмо",
"createNewAccount": "Создать новый аккаунт",
"weakStrength": "Слабый",
@@ -183,6 +186,7 @@
"enterDetailsManually": "Ввести детали вручную",
"edit": "Редактировать",
"copiedToClipboard": "Скопировано",
"copiedNextToClipboard": "Следующий код скопирован в буфер обмена",
"error": "Ошибка",
"recoveryKeyCopiedToClipboard": "Ключ восстановления скопирован в буфер обмена",
"recoveryKeyOnForgotPassword": "Если вы забыли свой пароль, то восстановить данные можно только с помощью этого ключа.",
@@ -253,6 +257,10 @@
"privacy": "Конфиденциальность",
"terms": "Условия использования",
"checkForUpdates": "Проверить наличие обновлений",
"downloadUpdate": "Скачать",
"criticalUpdateAvailable": "Доступно критическое обновление",
"updateAvailable": "Доступно обновление",
"update": "Обновить",
"checking": "Проверка...",
"youAreOnTheLatestVersion": "Вы используете последнюю версию",
"warning": "Предупреждение",
@@ -317,7 +325,73 @@
"plainText": "Обычный текст",
"passwordToEncryptExport": "Пароль для шифрования экспорта",
"export": "Экспорт",
"useOffline": "Использовать без резервных копий",
"signInToBackup": "Войдите в систему, чтобы создать резервную копию своих кодов",
"singIn": "Войти",
"sigInBackupReminder": "Экспортируйте свои коды, чтобы убедиться, что у вас есть резервная копия, из которой можно восстановить.",
"offlineModeWarning": "Вы решили продолжить без резервных копий. Пожалуйста, создайте резервные копии вручную, чтобы убедиться, что ваши коды в безопасности.",
"showLargeIcons": "Показать большие значки",
"shouldHideCode": "Скрыть коды",
"focusOnSearchBar": "Фокусировать поиск при запуске приложения",
"confirmUpdatingkey": "Вы уверены, что хотите обновить секретный ключ?",
"minimizeAppOnCopy": "Свернуть приложение при копировании",
"editCodeAuthMessage": "Аутентификация для редактирования кода",
"deleteCodeAuthMessage": "Аутентификация для удаления кода",
"showQRAuthMessage": "Аутентификация для отображения QR-кода"
"showQRAuthMessage": "Аутентификация для отображения QR-кода",
"confirmAccountDeleteTitle": "Подтвердить удаление аккаунта",
"confirmAccountDeleteMessage": "Эта учетная запись связана с другими приложениями ente, если вы ими пользуетесь.\n\nЗагруженные вами данные во всех приложениях ente будут запланированы к удалению, а ваша учетная запись будет удалена без возможности восстановления.",
"androidBiometricHint": "Подтвердите личность",
"@androidBiometricHint": {
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
},
"androidBiometricNotRecognized": "Не распознано. Попробуйте еще раз.",
"@androidBiometricNotRecognized": {
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
},
"androidBiometricSuccess": "Успех",
"@androidBiometricSuccess": {
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
},
"androidCancelButton": "Отменить",
"@androidCancelButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
},
"androidSignInTitle": "Требуется аутентификация",
"@androidSignInTitle": {
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
},
"androidBiometricRequiredTitle": "Требуется биометрия",
"@androidBiometricRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
},
"androidDeviceCredentialsRequiredTitle": "Требуются учетные данные устройства",
"@androidDeviceCredentialsRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
},
"androidDeviceCredentialsSetupDescription": "Требуются учетные данные устройства",
"@androidDeviceCredentialsSetupDescription": {
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
},
"goToSettings": "Перейдите к настройкам",
"@goToSettings": {
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
},
"androidGoToSettingsDescription": "Биометрическая аутентификация не настроена на вашем устройстве. Перейдите в \"Настройки > Безопасность\", чтобы добавить биометрическую аутентификацию.",
"@androidGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
},
"iOSLockOut": "Биометрическая аутентификация отключена. Пожалуйста, заблокируйте и разблокируйте экран, чтобы включить ее.",
"@iOSLockOut": {
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
},
"iOSGoToSettingsDescription": "Биометрическая аутентификация не настроена на вашем устройстве. Пожалуйста, включите Touch ID или Face ID на вашем телефоне.",
"@iOSGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
},
"iOSOkButton": "ОК",
"@iOSOkButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
},
"noInternetConnection": "Нет подключения к Интернету",
"pleaseCheckYourInternetConnectionAndTryAgain": "Проверьте подключение к Интернету и повторите попытку."
}

View File

@@ -1,5 +1,6 @@
{
"account": "Hesabım",
"unlock": "Kilidi aç",
"recoveryKey": "Kurtarma Anahtarı",
"counterAppBarTitle": "Sayaç",
"@counterAppBarTitle": {
@@ -83,6 +84,7 @@
"importSelectJsonFile": "JSON dosyasını seçin",
"importEnteEncGuide": "Ente'den dışa aktarılan şifrelenmiş JSON dosyasını seçin",
"importRaivoGuide": "Raivo'nun ayarlarında \"OTP'leri Zip arşivine aktar\" seçeneğini kullanın.\n\nZip dosyasını çıkarın ve JSON dosyasını içe aktarın.",
"importBitwardenGuide": "Bitwarden Tools içindeki \"Kasayı dışa aktar\" seçeneğini kullanın ve şifrelenmemiş JSON dosyasını içe aktarın.",
"importAegisGuide": "Aegis'in Ayarlarında \"Kasayı dışa aktar\" seçeneğini kullanın.\n\nKasanız şifrelenmişse, kasanın şifresini çözmek için kasa parolasını girmeniz gerekecektir.",
"exportCodes": "Kodu dışa aktar",
"importLabel": "İçe aktar",
@@ -90,16 +92,19 @@
"importCodeDelimiterInfo": "Kodlar, virgülle ya da yeni bir satırla ayrılabilir",
"selectFile": "Dosya seç",
"emailVerificationToggle": "E-posta doğrulama",
"emailVerificationEnableWarning": "E-postanız için 2FA'yı bizde saklıyorsanız, e-posta doğrulamasını açmak bir kilitlenmeye neden olabilir. Bir hizmetin dışında kalırsanız, diğerine giriş yapamayabilirsiniz.",
"authToChangeEmailVerificationSetting": "E-posta doğrulamasını değiştirmek için lütfen kimlik doğrulaması yapın",
"authToViewYourRecoveryKey": "Kurtarma anahtarınızı görmek için lütfen kimliğinizi doğrulayın",
"authToChangeYourEmail": "Epostanızı değiştirmek için lütfen kimliğinizi doğrulayın",
"authToChangeYourPassword": "Şifrenizi değiştirmek için lütfen kimliğinizi doğrulayın",
"authToInitiateSignIn": "Yedekleme için giriş yapmayı başlatmak üzere lütfen kimlik doğrulaması yapın.",
"ok": "Tamam",
"cancel": "İptal Et",
"yes": "Evet",
"no": "Hayır",
"email": "E-Posta",
"support": "Destek",
"general": "Genel",
"settings": "Ayarlar",
"copied": "Kopyalandı",
"pleaseTryAgain": "Lütfen tekrar deneyin",
@@ -153,8 +158,6 @@
"yesSendFeedbackAction": "Evet, geri bildirimi gönder",
"noDeleteAccountAction": "Hayır, hesabı sil",
"initiateAccountDeleteTitle": "Hesap silme işlemini yapabilmek için lütfen kimliğinizi doğrulayın",
"confirmAccountDeleteTitle": "ente hesabınızı silmek istediğinizden emin misiniz?",
"confirmAccountDeleteMessage": "Tüm uygulamalarda (Fotoğraflar ve Kimlik doğrulayıcı) kaydedilen verileriniz zamanlı silinmeye ayarlanacak ve hesabınız kalıcı olarak silinecektir.",
"sendEmail": "E-posta gönder",
"createNewAccount": "Yeni hesap oluşturun",
"weakStrength": "Zayıf",
@@ -181,6 +184,7 @@
"enterDetailsManually": "Bilgileri elle girin",
"edit": "Düzenle",
"copiedToClipboard": "Panoya kopyalandı",
"copiedNextToClipboard": "Sonraki kod panoya kopyalandı",
"error": "Hata",
"recoveryKeyCopiedToClipboard": "Kurtarma anahtarı panoya kopyalandı",
"recoveryKeyOnForgotPassword": "Eğer şifrenizi unutursanız, verilerinizi kurtarabileceğiniz tek yol bu anahtardır.",
@@ -251,6 +255,10 @@
"privacy": "Gizlilik",
"terms": "Şartlar",
"checkForUpdates": "Güncellemeleri denetleyin",
"downloadUpdate": "İndir",
"criticalUpdateAvailable": "Kritik güncelleme mevcut",
"updateAvailable": "Güncelleme mevcut",
"update": "Güncelle",
"checking": "Denetleniyor...",
"youAreOnTheLatestVersion": "En son sürümdesiniz",
"warning": "Uyarı",
@@ -315,7 +323,69 @@
"plainText": "Düz metin",
"passwordToEncryptExport": "Dışa aktarımı şifrelemek için parola",
"export": "Dışa aktar",
"useOffline": "Yedekleme olmadan kullan",
"signInToBackup": "Kodlarınızı yedeklemek için giriş yapın",
"singIn": "Giriş yap",
"sigInBackupReminder": "Geri yükleyebileceğiniz bir yedeğiniz olduğundan emin olmak için lütfen kodlarınızı dışa aktarın.",
"offlineModeWarning": "Yedekleme yapmadan devam etmeyi seçtiniz. Kodlarınızın güvende olduğundan emin olmak için lütfen manuel yedekleme yapın.",
"showLargeIcons": "Büyük simgeler göster",
"shouldHideCode": "Kodları gizle",
"doubleTapToViewHiddenCode": "Kodu görüntülemek için bir girdiye çift dokunabilirsiniz",
"focusOnSearchBar": "Uygulama başladığında arama bölümüne odaklan",
"minimizeAppOnCopy": "Kopyalarken uygulamayı küçült",
"editCodeAuthMessage": "Kodu düzenlemek için doğrulama yapın",
"deleteCodeAuthMessage": "Kodu silmek için doğrulama yapın",
"showQRAuthMessage": "QR kodunu göstermek için doğrulama yapın"
"showQRAuthMessage": "QR kodunu göstermek için doğrulama yapın",
"confirmAccountDeleteTitle": "Hesap silme işlemini onayla",
"confirmAccountDeleteMessage": "Bu hesap, eğer kullanıyorsanız, diğer ente uygulamalarıyla bağlantılıdır.\n\nTüm ente uygulamalarında yüklediğiniz veriler silinmek üzere programlanacak ve hesabınız kalıcı olarak silinecektir.",
"androidBiometricHint": "Kimliği doğrula",
"@androidBiometricHint": {
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
},
"androidBiometricNotRecognized": "Tanınmadı. Tekrar deneyin.",
"@androidBiometricNotRecognized": {
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
},
"androidBiometricSuccess": "Başarılı",
"@androidBiometricSuccess": {
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
},
"androidCancelButton": "İptal et",
"@androidCancelButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
},
"androidSignInTitle": "Kimlik doğrulaması gerekli",
"@androidSignInTitle": {
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
},
"androidBiometricRequiredTitle": "Biyometrik gerekli",
"@androidBiometricRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
},
"androidDeviceCredentialsRequiredTitle": "Cihaz kimlik bilgileri gerekli",
"@androidDeviceCredentialsRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
},
"goToSettings": "Ayarlara git",
"@goToSettings": {
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
},
"androidGoToSettingsDescription": "Biyometrik kimlik doğrulama cihazınızda ayarlanmamış. Biyometrik kimlik doğrulama eklemek için 'Ayarlar > Güvenlik' bölümüne gidin.",
"@androidGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
},
"iOSLockOut": "Biyometrik kimlik doğrulama devre dışı. Etkinleştirmek için lütfen ekranınızı kilitleyin ve kilidini açın.",
"@iOSLockOut": {
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
},
"iOSGoToSettingsDescription": "Cihazınızda biyometrik kimlik doğrulama ayarlanmamış. Lütfen telefonunuzda Touch ID veya Face ID'yi etkinleştirin.",
"@iOSGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
},
"iOSOkButton": "Tamam",
"@iOSOkButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
},
"noInternetConnection": "İnternet bağlantısı yok",
"pleaseCheckYourInternetConnectionAndTryAgain": "Lütfen internet bağlantınızı kontrol edin ve yeniden deneyin."
}

View File

@@ -126,8 +126,6 @@
"yesSendFeedbackAction": "Có, gửi phản hồi",
"noDeleteAccountAction": "Không, xóa tài khoản",
"initiateAccountDeleteTitle": "Vui lòng xác thực để bắt đầu xóa tài khoản",
"confirmAccountDeleteTitle": "Bạn có chắc rằng bạn muốn xóa tài khoản ente của bạn không?",
"confirmAccountDeleteMessage": "Dữ liệu đã tải lên của bạn, trên tất cả các ứng dụng (cả Ảnh và Authenticator), sẽ được lên lịch xóa và tài khoản của bạn sẽ bị xóa vĩnh viễn.",
"sendEmail": "Gửi email",
"createNewAccount": "Tạo tài khoản mới",
"weakStrength": "Yếu",

View File

@@ -1,5 +1,6 @@
{
"account": "账户",
"unlock": "解锁",
"recoveryKey": "恢复密钥",
"counterAppBarTitle": "计数器",
"@counterAppBarTitle": {
@@ -83,9 +84,12 @@
"importFromApp": "从 {appName} 导入代码",
"importGoogleAuthGuide": "使用“转移帐户”选项将您的帐户从 Google 身份验证器导出到二维码。然后使用另一台设备扫描二维码。\n\n提示您可以使用笔记本电脑的网络摄像头拍摄二维码的照片。",
"importSelectJsonFile": "选择 JSON 文件",
"importSelectAppExport": "选择 {appName} 的导出文件",
"importEnteEncGuide": "选择从ente导出的加密JSON文件",
"importRaivoGuide": "使用 Raivo 设置中的“将 OTP 导出到 Zip 存档”选项。\n\n解压 zip 文件并导入 JSON 文件。",
"importBitwardenGuide": "使用 Bitwarden 工具中的“导出保管库”选项并导入未加密的 JSON 文件。",
"importAegisGuide": "在Aegis的设置中使用\"导出密码库\"选项。\n\n如果您的密码库已加密您需要输入密码才能解密密码库。",
"import2FasGuide": "使用 2FAS 中的“设置 -> 备份 - 导出”选项。\n\n如果您的备份已被加密则需要输入密码才能解密备份",
"exportCodes": "导出代码",
"importLabel": "导入",
"importInstruction": "请以以下格式选择包含代码列表的文件",
@@ -97,6 +101,8 @@
"authToViewYourRecoveryKey": "请验证以查看您的恢复密钥",
"authToChangeYourEmail": "请验证以更改您的电子邮件",
"authToChangeYourPassword": "请验证以更改密码",
"authToViewSecrets": "请进行身份验证以查看您的秘密",
"authToInitiateSignIn": "请进行身份验证以启动登录进行备份。",
"ok": "好的",
"cancel": "取消",
"yes": "是",
@@ -157,8 +163,6 @@
"yesSendFeedbackAction": "是,发送反馈",
"noDeleteAccountAction": "不,删除账户",
"initiateAccountDeleteTitle": "请进行身份验证以启动账户删除",
"confirmAccountDeleteTitle": "确定要删除您的ente账户吗",
"confirmAccountDeleteMessage": "您上传的数据,跨越所有应用程序(Photos 和 Authenticator 两者),将被排定删除,您的账户将被永久删除。",
"sendEmail": "发送电子邮件",
"createNewAccount": "创建新账号",
"weakStrength": "弱",
@@ -331,10 +335,71 @@
"offlineModeWarning": "您已选择在不进行备份的情况下继续操作。请手动备份以确保您的代码安全。",
"showLargeIcons": "显示大图标",
"shouldHideCode": "隐藏代码",
"doubleTapToViewHiddenCode": "您可以双击条目来查看代码",
"focusOnSearchBar": "应用启动后聚焦搜索",
"confirmUpdatingkey": "您确定要更新此密钥吗?",
"minimizeAppOnCopy": "复制时最小化应用",
"editCodeAuthMessage": "编辑代码需要身份验证",
"deleteCodeAuthMessage": "删除代码需要身份验证",
"showQRAuthMessage": "显示QR码需要身份验证"
"showQRAuthMessage": "显示QR码需要身份验证",
"confirmAccountDeleteTitle": "确认删除账户",
"confirmAccountDeleteMessage": "该账户已链接到其他ente旗下的应用程序如果您使用任何相关的应用程序。\n\n您在所有ente旗下应用程序中上传的数据都将被安排删除并且您的账户将被永久删除。",
"androidBiometricHint": "验证身份",
"@androidBiometricHint": {
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
},
"androidBiometricNotRecognized": "未能识别。再试一次。",
"@androidBiometricNotRecognized": {
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
},
"androidBiometricSuccess": "成功",
"@androidBiometricSuccess": {
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
},
"androidCancelButton": "取消",
"@androidCancelButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
},
"androidSignInTitle": "需要进行身份验证",
"@androidSignInTitle": {
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
},
"androidBiometricRequiredTitle": "需要进行生物识别认证",
"@androidBiometricRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
},
"androidDeviceCredentialsRequiredTitle": "需要设备凭据",
"@androidDeviceCredentialsRequiredTitle": {
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
},
"androidDeviceCredentialsSetupDescription": "需要设备凭据",
"@androidDeviceCredentialsSetupDescription": {
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
},
"goToSettings": "前往设置",
"@goToSettings": {
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
},
"androidGoToSettingsDescription": "您的设备上未设置生物识别身份验证。转到“设置 > 安全”以添加生物识别身份验证。",
"@androidGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
},
"iOSLockOut": "生物识别身份验证已禁用。请锁定再解锁您的屏幕以启用它。",
"@iOSLockOut": {
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
},
"iOSGoToSettingsDescription": "您的设备上未设置生物识别身份验证。请在您的手机上启用 触控 ID 或 面容 ID。",
"@iOSGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
},
"iOSOkButton": "好的",
"@iOSOkButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
},
"noInternetConnection": "无互联网连接",
"pleaseCheckYourInternetConnectionAndTryAgain": "请检查您的互联网连接,然后重试。",
"signOutFromOtherDevices": "从其他设备退出登录",
"signOutOtherBody": "如果你认为有人可能知道你的密码,你可以强制所有使用你账户的其他设备退出登录。",
"signOutOtherDevices": "登出其他设备",
"doNotSignOut": "不要退登"
}

View File

@@ -1,5 +1,3 @@
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/ui/tools/app_lock.dart';
import 'package:ente_auth/utils/auth_util.dart';
@@ -19,7 +17,7 @@ class LocalAuthenticationService {
) async {
if (await _isLocalAuthSupportedOnDevice()) {
AppLock.of(context)!.setEnabled(false);
final result = await requestAuthentication(infoMessage);
final result = await requestAuthentication(context, infoMessage);
AppLock.of(context)!.setEnabled(
Configuration.instance.shouldShowLockScreen(),
);
@@ -43,6 +41,7 @@ class LocalAuthenticationService {
if (await LocalAuthentication().isDeviceSupported()) {
AppLock.of(context)!.disable();
final result = await requestAuthentication(
context,
infoMessage,
);
if (result) {

View File

@@ -1,5 +1,3 @@
import 'dart:io';
import 'package:ente_auth/core/constants.dart';
@@ -94,7 +92,7 @@ class UpdateService {
// Note: in auth, currently we don't have a way to identify if the
// app was installed from play store, f-droid or github based on pkg name
if (Platform.isAndroid) {
if(flavor == "playstore") {
if (flavor == "playstore") {
return const Tuple2(
"Play Store",
"market://details??id=io.ente.auth",
@@ -120,8 +118,7 @@ class UpdateService {
// Fall back if we fail to open play-store market app on android
if (Platform.isAndroid && url.startsWith("market://")) {
launchUrlString(
"https://play.google.com/store/apps/details?id=io"
".ente.auth",
"https://play.google.com/store/apps/details?id=io.ente.auth",
mode: LaunchMode.externalApplication,
).ignore();
}
@@ -129,7 +126,8 @@ class UpdateService {
}
bool isIndependent() {
return flavor == "independent" || _packageInfo.packageName.endsWith("independent");
return flavor == "independent" ||
_packageInfo.packageName.endsWith("independent");
}
}

View File

@@ -24,13 +24,12 @@ import 'package:ente_auth/ui/account/ott_verification_page.dart';
import 'package:ente_auth/ui/account/password_entry_page.dart';
import 'package:ente_auth/ui/account/password_reentry_page.dart';
import 'package:ente_auth/ui/account/recovery_page.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/ui/common/progress_dialog.dart';
import 'package:ente_auth/ui/home_page.dart';
import 'package:ente_auth/ui/two_factor_authentication_page.dart';
import 'package:ente_auth/ui/two_factor_recovery_page.dart';
import 'package:ente_auth/utils/crypto_util.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/email_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
import "package:flutter/foundation.dart";
import 'package:flutter/material.dart';
@@ -46,9 +45,10 @@ import "package:uuid/uuid.dart";
class UserService {
static const keyHasEnabledTwoFactor = "has_enabled_two_factor";
static const keyUserDetails = "user_details";
static const kReferralSource = "referral_source";
static const kCanDisableEmailMFA = "can_disable_email_mfa";
static const kIsEmailMFAEnabled = "is_email_mfa_enabled";
final SRP6GroupParameters kDefaultSrpGroup = SRP6StandardGroups.rfc5054_4096;
final SRP6GroupParameters kDefaultSrpGroup = SRP6StandardGroups.rfc5054_4096;
final _dio = Network.instance.getDio();
final _enteDio = Network.instance.enteDio;
final _logger = Logger((UserService).toString());
@@ -68,12 +68,12 @@ class UserService {
}
Future<void> sendOtt(
BuildContext context,
String email, {
bool isChangeEmail = false,
bool isCreateAccountScreen = false,
bool isResetPasswordScreen = false,
}) async {
BuildContext context,
String email, {
bool isChangeEmail = false,
bool isCreateAccountScreen = false,
bool isResetPasswordScreen = false,
}) async {
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
await dialog.show();
try {
@@ -122,17 +122,16 @@ class UserService {
}
Future<void> sendFeedback(
BuildContext context,
String feedback, {
String type = "SubCancellation",
}) async {
BuildContext context,
String feedback, {
String type = "SubCancellation",
}) async {
await _dio.post(
_config.getHttpEndpoint() + "/anonymous/feedback",
data: {"feedback": feedback, "type": "type"},
);
}
Future<UserDetails> getUserDetailsV2({
bool memoryCount = false,
bool shouldCache = true,
@@ -146,9 +145,15 @@ class UserService {
);
final userDetails = UserDetails.fromMap(response.data);
if (shouldCache) {
if(userDetails.profileData != null) {
_preferences.setBool(kIsEmailMFAEnabled, userDetails.profileData!.isEmailMFAEnabled);
_preferences.setBool(kCanDisableEmailMFA, userDetails.profileData!.canDisableEmailMFA);
if (userDetails.profileData != null) {
_preferences.setBool(
kIsEmailMFAEnabled,
userDetails.profileData!.isEmailMFAEnabled,
);
_preferences.setBool(
kCanDisableEmailMFA,
userDetails.profileData!.canDisableEmailMFA,
);
}
// handle email change from different client
if (userDetails.email != _config.getEmail()) {
@@ -156,7 +161,7 @@ class UserService {
}
}
return userDetails;
} catch(e) {
} catch (e) {
_logger.warning("Failed to fetch", e);
rethrow;
}
@@ -210,15 +215,15 @@ class UserService {
//to close and only then to show the error dialog.
Future.delayed(
const Duration(milliseconds: 150),
() => showGenericErrorDialog(context: context),
() => showGenericErrorDialog(context: context),
);
rethrow;
}
}
Future<DeleteChallengeResponse?> getDeleteChallenge(
BuildContext context,
) async {
BuildContext context,
) async {
try {
final response = await _enteDio.get("/users/delete-challenge");
if (response.statusCode == 200) {
@@ -237,8 +242,9 @@ class UserService {
}
Future<void> deleteAccount(
BuildContext context,
String challengeResponse,) async {
BuildContext context,
String challengeResponse,
) async {
try {
final response = await _enteDio.delete(
"/users/delete",
@@ -258,18 +264,24 @@ class UserService {
}
}
Future<void> verifyEmail(BuildContext context, String ott, {bool
isResettingPasswordScreen = false,})
async {
Future<void> verifyEmail(
BuildContext context,
String ott, {
bool isResettingPasswordScreen = false,
}) async {
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
await dialog.show();
final verifyData = {
"email": _config.getEmail(),
"ott": ott,
};
if (!_config.isLoggedIn()) {
verifyData["source"] = 'auth:' + _getRefSource();
}
try {
final response = await _dio.post(
_config.getHttpEndpoint() + "/users/verify-email",
data: {
"email": _config.getEmail(),
"ott": ott,
},
data: verifyData,
);
await dialog.hide();
if (response.statusCode == 200) {
@@ -280,14 +292,15 @@ class UserService {
} else {
await _saveConfiguration(response);
if (Configuration.instance.getEncryptedToken() != null) {
if(isResettingPasswordScreen) {
if (isResettingPasswordScreen) {
page = const RecoveryPage();
} else {
page = const PasswordReentryPage();
}
} else {
page = const PasswordEntryPage(mode: PasswordEntryMode.set,);
page = const PasswordEntryPage(
mode: PasswordEntryMode.set,
);
}
}
Navigator.of(context).pushAndRemoveUntil(
@@ -296,7 +309,7 @@ class UserService {
return page;
},
),
(route) => route.isFirst,
(route) => route.isFirst,
);
} else {
// should never reach here
@@ -336,10 +349,10 @@ class UserService {
}
Future<void> changeEmail(
BuildContext context,
String email,
String ott,
) async {
BuildContext context,
String email,
String ott,
) async {
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
await dialog.show();
try {
@@ -431,9 +444,10 @@ class UserService {
}
Future<void> registerOrUpdateSrp(
Uint8List loginKey, {
SetKeysRequest? setKeysRequest,
}) async {
Uint8List loginKey, {
SetKeysRequest? setKeysRequest,
bool logOutOtherDevices = false,
}) async {
try {
final String username = const Uuid().v4().toString();
final SecureRandom random = _getSecureRandom();
@@ -466,14 +480,14 @@ class UserService {
);
if (response.statusCode == 200) {
final SetupSRPResponse setupSRPResponse =
SetupSRPResponse.fromJson(response.data);
SetupSRPResponse.fromJson(response.data);
final serverB =
SRP6Util.decodeBigInt(base64Decode(setupSRPResponse.srpB));
SRP6Util.decodeBigInt(base64Decode(setupSRPResponse.srpB));
// ignore: need to calculate secret to get M1, unused_local_variable
final clientS = client.calculateSecret(serverB);
final clientM = client.calculateClientEvidenceMessage();
late Response srpCompleteResponse;
if(setKeysRequest == null) {
if (setKeysRequest == null) {
srpCompleteResponse = await _enteDio.post(
"/users/srp/complete",
data: {
@@ -488,14 +502,15 @@ class UserService {
'setupID': setupSRPResponse.setupID,
'srpM1': base64Encode(SRP6Util.encodeBigInt(clientM!)),
'updatedKeyAttr': setKeysRequest.toMap(),
'logOutOtherDevices': logOutOtherDevices,
},
);
}
} else {
throw Exception("register-srp action failed");
}
} catch (e,s) {
_logger.severe("failed to register srp" ,e,s);
} catch (e, s) {
_logger.severe("failed to register srp", e, s);
rethrow;
}
}
@@ -512,133 +527,97 @@ class UserService {
}
Future<void> verifyEmailViaPassword(
BuildContext context,
SrpAttributes srpAttributes,
String userPassword,
) async {
final dialog = createProgressDialog(
context,
context.l10n.pleaseWait,
isDismissible: true,
);
await dialog.show();
BuildContext context,
SrpAttributes srpAttributes,
String userPassword,
ProgressDialog dialog,
) async {
late Uint8List keyEncryptionKey;
try {
keyEncryptionKey = await CryptoUtil.deriveKey(
utf8.encode(userPassword) as Uint8List,
CryptoUtil.base642bin(srpAttributes.kekSalt),
srpAttributes.memLimit,
srpAttributes.opsLimit,
);
final loginKey = await CryptoUtil.deriveLoginKey(keyEncryptionKey);
final Uint8List identity = Uint8List.fromList(
utf8.encode(srpAttributes.srpUserID),
);
final Uint8List salt = base64Decode(srpAttributes.srpSalt);
final Uint8List password = loginKey;
final SecureRandom random = _getSecureRandom();
_logger.finest('Start deriving key');
keyEncryptionKey = await CryptoUtil.deriveKey(
utf8.encode(userPassword) as Uint8List,
CryptoUtil.base642bin(srpAttributes.kekSalt),
srpAttributes.memLimit,
srpAttributes.opsLimit,
);
_logger.finest('keyDerivation done, derive LoginKey');
final loginKey = await CryptoUtil.deriveLoginKey(keyEncryptionKey);
final Uint8List identity = Uint8List.fromList(
utf8.encode(srpAttributes.srpUserID),
);
_logger.finest('longinKey derivation done');
final Uint8List salt = base64Decode(srpAttributes.srpSalt);
final Uint8List password = loginKey;
final SecureRandom random = _getSecureRandom();
final client = SRP6Client(
group: kDefaultSrpGroup,
digest: Digest('SHA-256'),
random: random,
);
final client = SRP6Client(
group: kDefaultSrpGroup,
digest: Digest('SHA-256'),
random: random,
);
final A = client.generateClientCredentials(salt, identity, password);
final createSessionResponse = await _dio.post(
_config.getHttpEndpoint() + "/users/srp/create-session",
data: {
"srpUserID": srpAttributes.srpUserID,
"srpA": base64Encode(SRP6Util.encodeBigInt(A!)),
},
);
final String sessionID = createSessionResponse.data["sessionID"];
final String srpB = createSessionResponse.data["srpB"];
final A = client.generateClientCredentials(salt, identity, password);
final createSessionResponse = await _dio.post(
_config.getHttpEndpoint() + "/users/srp/create-session",
data: {
"srpUserID": srpAttributes.srpUserID,
"srpA": base64Encode(SRP6Util.encodeBigInt(A!)),
},
);
final String sessionID = createSessionResponse.data["sessionID"];
final String srpB = createSessionResponse.data["srpB"];
final serverB = SRP6Util.decodeBigInt(base64Decode(srpB));
// ignore: need to calculate secret to get M1, unused_local_variable
final clientS = client.calculateSecret(serverB);
final clientM = client.calculateClientEvidenceMessage();
final response = await _dio.post(
_config.getHttpEndpoint() + "/users/srp/verify-session",
data: {
"sessionID": sessionID,
"srpUserID": srpAttributes.srpUserID,
"srpM1": base64Encode(SRP6Util.encodeBigInt(clientM!)),
},
);
if (response.statusCode == 200) {
Widget page;
final String twoFASessionID = response.data["twoFactorSessionID"];
Configuration.instance.setVolatilePassword(userPassword);
if (twoFASessionID.isNotEmpty) {
page = TwoFactorAuthenticationPage(twoFASessionID);
} else {
await _saveConfiguration(response);
if (Configuration.instance.getEncryptedToken() != null) {
await Configuration.instance.decryptSecretsAndGetKeyEncKey(
userPassword,
Configuration.instance.getKeyAttributes()!,
keyEncryptionKey: keyEncryptionKey,
);
page = const HomePage();
} else {
throw Exception("unexpected response during email verification");
}
}
await dialog.hide();
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
return page;
},
),
(route) => route.isFirst,
);
final serverB = SRP6Util.decodeBigInt(base64Decode(srpB));
// ignore: need to calculate secret to get M1, unused_local_variable
final clientS = client.calculateSecret(serverB);
final clientM = client.calculateClientEvidenceMessage();
final response = await _dio.post(
_config.getHttpEndpoint() + "/users/srp/verify-session",
data: {
"sessionID": sessionID,
"srpUserID": srpAttributes.srpUserID,
"srpM1": base64Encode(SRP6Util.encodeBigInt(clientM!)),
},
);
if (response.statusCode == 200) {
Widget page;
final String twoFASessionID = response.data["twoFactorSessionID"];
Configuration.instance.setVolatilePassword(userPassword);
if (twoFASessionID.isNotEmpty) {
page = TwoFactorAuthenticationPage(twoFASessionID);
} else {
// should never reach here
throw Exception("unexpected response during email verification");
}
} on DioError catch (e, s) {
await dialog.hide();
if (e.response != null && e.response!.statusCode == 401) {
final dialogChoice = await showChoiceDialog(
context,
title: context.l10n.incorrectPasswordTitle,
body: context.l10n.pleaseTryAgain,
firstButtonLabel: context.l10n.contactSupport,
secondButtonLabel: context.l10n.ok,
);
if (dialogChoice!.action == ButtonAction.first) {
await sendLogs(
context,
context.l10n.contactSupport,
"support@ente.io",
postShare: () {},
await _saveConfiguration(response);
if (Configuration.instance.getEncryptedToken() != null) {
await Configuration.instance.decryptSecretsAndGetKeyEncKey(
userPassword,
Configuration.instance.getKeyAttributes()!,
keyEncryptionKey: keyEncryptionKey,
);
page = const HomePage();
} else {
throw Exception("unexpected response during email verification");
}
} else {
_logger.fine('failed to verify password', e, s);
await showErrorDialog(
context,
context.l10n.oops,
context.l10n.verificationFailedPleaseTryAgain,
);
}
} catch (e, s) {
_logger.fine('failed to verify password', e, s);
await dialog.hide();
await showErrorDialog(
context,
context.l10n.oops,
context.l10n.verificationFailedPleaseTryAgain,
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
return page;
},
),
(route) => route.isFirst,
);
} else {
// should never reach here
throw Exception("unexpected response during email verification");
}
}
Future<void> updateKeyAttributes(KeyAttributes keyAttributes, Uint8List
loginKey,)
async {
Future<void> updateKeyAttributes(
KeyAttributes keyAttributes,
Uint8List loginKey, {
required bool logoutOtherDevices,
}) async {
try {
final setKeyRequest = SetKeysRequest(
kekSalt: keyAttributes.kekSalt,
@@ -647,11 +626,11 @@ class UserService {
memLimit: keyAttributes.memLimit,
opsLimit: keyAttributes.opsLimit,
);
await registerOrUpdateSrp(loginKey, setKeysRequest: setKeyRequest);
// await _enteDio.put(
// "/users/keys",
// data: setKeyRequest.toMap(),
// );
await registerOrUpdateSrp(
loginKey,
setKeysRequest: setKeyRequest,
logOutOtherDevices: logoutOtherDevices,
);
await _config.setKeyAttributes(keyAttributes);
} catch (e) {
_logger.severe(e);
@@ -679,10 +658,10 @@ class UserService {
}
Future<void> verifyTwoFactor(
BuildContext context,
String sessionID,
String code,
) async {
BuildContext context,
String sessionID,
String code,
) async {
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
await dialog.show();
try {
@@ -703,7 +682,7 @@ class UserService {
return const PasswordReentryPage();
},
),
(route) => route.isFirst,
(route) => route.isFirst,
);
}
} on DioError catch (e) {
@@ -717,7 +696,7 @@ class UserService {
return const LoginPage();
},
),
(route) => route.isFirst,
(route) => route.isFirst,
);
} else {
showErrorDialog(
@@ -758,7 +737,7 @@ class UserService {
);
},
),
(route) => route.isFirst,
(route) => route.isFirst,
);
}
} on DioError catch (e) {
@@ -771,7 +750,7 @@ class UserService {
return const LoginPage();
},
),
(route) => route.isFirst,
(route) => route.isFirst,
);
} else {
showErrorDialog(
@@ -793,12 +772,12 @@ class UserService {
}
Future<void> removeTwoFactor(
BuildContext context,
String sessionID,
String recoveryKey,
String encryptedSecret,
String secretDecryptionNonce,
) async {
BuildContext context,
String sessionID,
String recoveryKey,
String encryptedSecret,
String secretDecryptionNonce,
) async {
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
await dialog.show();
String secret;
@@ -847,7 +826,7 @@ class UserService {
return const PasswordReentryPage();
},
),
(route) => route.isFirst,
(route) => route.isFirst,
);
}
} on DioError catch (e) {
@@ -860,7 +839,7 @@ class UserService {
return const LoginPage();
},
),
(route) => route.isFirst,
(route) => route.isFirst,
);
} else {
showErrorDialog(
@@ -881,13 +860,6 @@ class UserService {
}
}
Future<void> _saveConfiguration(Response response) async {
await Configuration.instance.setUserID(response.data["id"]);
if (response.data["encryptedToken"] != null) {
@@ -904,6 +876,7 @@ class UserService {
bool? canDisableEmailMFA() {
return _preferences.getBool(kCanDisableEmailMFA);
}
bool hasEmailMFAEnabled() {
return _preferences.getBool(kIsEmailMFAEnabled) ?? true;
}
@@ -918,9 +891,16 @@ class UserService {
);
_preferences.setBool(kIsEmailMFAEnabled, isEnabled);
} catch (e) {
_logger.severe("Failed to update email mfa",e);
_logger.severe("Failed to update email mfa", e);
rethrow;
}
}
}
Future<void> setRefSource(String refSource) async {
await _preferences.setString(kReferralSource, refSource);
}
String _getRefSource() {
return _preferences.getString(kReferralSource) ?? "";
}
}

View File

@@ -47,7 +47,7 @@ class EnteColorScheme {
final Color warning800;
final Color caution500;
final List<Color> avatarColors;
const EnteColorScheme(
this.backgroundBase,
this.backgroundElevated,
@@ -69,7 +69,8 @@ class EnteColorScheme {
this.strokeFainter,
this.blurStrokeBase,
this.blurStrokeFaint,
this.blurStrokePressed, {
this.blurStrokePressed,
this.avatarColors, {
this.primaryGreen = _primaryGreen,
this.primary700 = _primary700,
this.primary500 = _primary500,
@@ -105,6 +106,7 @@ const EnteColorScheme lightScheme = EnteColorScheme(
blurStrokeBaseLight,
blurStrokeFaintLight,
blurStrokePressedLight,
avatarLight,
);
const EnteColorScheme darkScheme = EnteColorScheme(
@@ -129,6 +131,7 @@ const EnteColorScheme darkScheme = EnteColorScheme(
blurStrokeBaseDark,
blurStrokeFaintDark,
blurStrokePressedDark,
avatarDark,
);
// Background Colors
@@ -204,3 +207,55 @@ const Color warning500 = Color.fromRGBO(255, 101, 101, 1);
const Color _warning400 = Color.fromRGBO(255, 111, 111, 1);
const Color _caution500 = Color.fromRGBO(255, 194, 71, 1);
const List<Color> avatarLight = [
Color.fromRGBO(118, 84, 154, 1),
Color.fromRGBO(223, 120, 97, 1),
Color.fromRGBO(148, 180, 159, 1),
Color.fromRGBO(135, 162, 251, 1),
Color.fromRGBO(198, 137, 198, 1),
Color.fromRGBO(198, 137, 198, 1),
Color.fromRGBO(50, 82, 136, 1),
Color.fromRGBO(133, 180, 224, 1),
Color.fromRGBO(193, 163, 163, 1),
Color.fromRGBO(193, 163, 163, 1),
Color.fromRGBO(66, 97, 101, 1),
Color.fromRGBO(66, 97, 101, 1),
Color.fromRGBO(66, 97, 101, 1),
Color.fromRGBO(221, 157, 226, 1),
Color.fromRGBO(130, 171, 139, 1),
Color.fromRGBO(155, 187, 232, 1),
Color.fromRGBO(143, 190, 190, 1),
Color.fromRGBO(138, 195, 161, 1),
Color.fromRGBO(168, 176, 242, 1),
Color.fromRGBO(176, 198, 149, 1),
Color.fromRGBO(233, 154, 173, 1),
Color.fromRGBO(209, 132, 132, 1),
Color.fromRGBO(120, 181, 167, 1),
];
const List<Color> avatarDark = [
Color.fromRGBO(118, 84, 154, 1),
Color.fromRGBO(223, 120, 97, 1),
Color.fromRGBO(148, 180, 159, 1),
Color.fromRGBO(135, 162, 251, 1),
Color.fromRGBO(198, 137, 198, 1),
Color.fromRGBO(147, 125, 194, 1),
Color.fromRGBO(50, 82, 136, 1),
Color.fromRGBO(133, 180, 224, 1),
Color.fromRGBO(193, 163, 163, 1),
Color.fromRGBO(225, 160, 89, 1),
Color.fromRGBO(66, 97, 101, 1),
Color.fromRGBO(107, 119, 178, 1),
Color.fromRGBO(149, 127, 239, 1),
Color.fromRGBO(221, 157, 226, 1),
Color.fromRGBO(130, 171, 139, 1),
Color.fromRGBO(155, 187, 232, 1),
Color.fromRGBO(143, 190, 190, 1),
Color.fromRGBO(138, 195, 161, 1),
Color.fromRGBO(168, 176, 242, 1),
Color.fromRGBO(176, 198, 149, 1),
Color.fromRGBO(233, 154, 173, 1),
Color.fromRGBO(209, 132, 132, 1),
Color.fromRGBO(120, 181, 167, 1),
];

View File

@@ -3,8 +3,10 @@ import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/ente_theme_data.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/theme/ente_theme.dart';
import 'package:ente_auth/ui/common/dynamic_fab.dart';
import 'package:ente_auth/ui/common/web_page.dart';
import 'package:ente_auth/utils/toast_util.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:password_strength/password_strength.dart';
@@ -30,6 +32,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
String? _email;
String? _password;
String _cnfPassword = '';
String _referralSource = '';
double _passwordStrength = 0.0;
bool _emailIsValid = false;
bool _hasAgreedToTOS = true;
@@ -104,6 +107,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
onPressedFunction: () {
_config.setVolatilePassword(_passwordController1.text);
UserService.instance.setEmail(_email!);
UserService.instance.setRefSource(_referralSource);
UserService.instance
.sendOtt(context, _email!, isCreateAccountScreen: true);
FocusScope.of(context).unfocus();
@@ -325,6 +329,51 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
),
),
const SizedBox(height: 4),
Padding(
padding:
const EdgeInsets.symmetric(vertical: 0, horizontal: 20),
child: Text(
context.l10n.hearUsWhereTitle,
style: getEnteTextTheme(context).smallFaint,
),
),
const SizedBox(height: 4),
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
child: TextFormField(
style: Theme.of(context).textTheme.titleMedium,
decoration: InputDecoration(
fillColor: null,
filled: true,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 14,
),
border: UnderlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6),
),
suffixIcon: InkWell(
onTap: () {
showToast(
context,
context.l10n.hearUsExplanation,
);
},
child: Icon(
Icons.info_outline_rounded,
color: getEnteColorScheme(context).strokeMuted,
),
),
),
onChanged: (value) {
_referralSource = value.trim();
},
autocorrect: false,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.next,
),
),
const Divider(thickness: 1),
const SizedBox(height: 12),
_getAgreement(),

View File

@@ -1,11 +1,14 @@
import "package:dio/dio.dart";
import 'package:ente_auth/core/configuration.dart';
import "package:ente_auth/core/errors.dart";
import "package:ente_auth/l10n/l10n.dart";
import "package:ente_auth/models/api/user/srp.dart";
import "package:ente_auth/services/user_service.dart";
import "package:ente_auth/theme/ente_theme.dart";
import 'package:ente_auth/ui/common/dynamic_fab.dart';
import "package:ente_auth/ui/components/buttons/button_widget.dart";
import "package:ente_auth/utils/dialog_util.dart";
import "package:ente_auth/utils/email_util.dart";
import 'package:flutter/material.dart';
import "package:logging/logging.dart";
@@ -16,14 +19,16 @@ import "package:logging/logging.dart";
// volatile password.
class LoginPasswordVerificationPage extends StatefulWidget {
final SrpAttributes srpAttributes;
const LoginPasswordVerificationPage({Key? key, required this.srpAttributes}) : super(key: key);
const LoginPasswordVerificationPage({Key? key, required this.srpAttributes})
: super(key: key);
@override
State<LoginPasswordVerificationPage> createState() => _LoginPasswordVerificationPageState();
State<LoginPasswordVerificationPage> createState() =>
_LoginPasswordVerificationPageState();
}
class _LoginPasswordVerificationPageState extends
State<LoginPasswordVerificationPage> {
class _LoginPasswordVerificationPageState
extends State<LoginPasswordVerificationPage> {
final _logger = Logger((_LoginPasswordVerificationPageState).toString());
final _passwordController = TextEditingController();
final FocusNode _passwordFocusNode = FocusNode();
@@ -74,9 +79,7 @@ State<LoginPasswordVerificationPage> {
buttonText: context.l10n.logInLabel,
onPressedFunction: () async {
FocusScope.of(context).unfocus();
await UserService.instance.verifyEmailViaPassword(context, widget
.srpAttributes,
_passwordController.text,);
await verifyPassword(context, _passwordController.text);
},
),
floatingActionButtonLocation: fabLocation(),
@@ -84,6 +87,106 @@ State<LoginPasswordVerificationPage> {
);
}
Future<void> verifyPassword(BuildContext context, String password) async {
final dialog = createProgressDialog(
context,
context.l10n.pleaseWait,
isDismissible: true,
);
await dialog.show();
try {
await UserService.instance.verifyEmailViaPassword(
context,
widget.srpAttributes,
password,
dialog,
);
} on DioError catch (e, s) {
await dialog.hide();
if (e.response != null && e.response!.statusCode == 401) {
_logger.severe('server reject, failed verify SRP login', e, s);
await _showContactSupportDialog(
context,
context.l10n.incorrectPasswordTitle,
context.l10n.pleaseTryAgain,
);
} else {
_logger.severe('API failure during SRP login', e, s);
if (e.type == DioErrorType.other) {
await _showContactSupportDialog(
context,
context.l10n.noInternetConnection,
context.l10n.pleaseCheckYourInternetConnectionAndTryAgain,
);
} else {
await _showContactSupportDialog(
context,
context.l10n.oops,
context.l10n.verificationFailedPleaseTryAgain,
);
}
}
} catch (e, s) {
_logger.info('error during loginViaPassword', e);
await dialog.hide();
if (e is LoginKeyDerivationError) {
_logger.severe('loginKey derivation error', e, s);
// LoginKey err, perform regular login via ott verification
await UserService.instance.sendOtt(
context,
email!,
isCreateAccountScreen: true,
);
return;
} else if (e is KeyDerivationError) {
// device is not powerful enough to perform derive key
final dialogChoice = await showChoiceDialog(
context,
title: context.l10n.recreatePasswordTitle,
body: context.l10n.recreatePasswordBody,
firstButtonLabel: context.l10n.useRecoveryKey,
);
if (dialogChoice!.action == ButtonAction.first) {
await UserService.instance.sendOtt(
context,
email!,
isResetPasswordScreen: true,
);
}
return;
} else {
_logger.severe('unexpected error while verifying password', e, s);
await _showContactSupportDialog(
context,
context.l10n.oops,
context.l10n.verificationFailedPleaseTryAgain,
);
}
}
}
Future<void> _showContactSupportDialog(
BuildContext context,
String title,
String message,
) async {
final dialogChoice = await showChoiceDialog(
context,
title: title,
body: message,
firstButtonLabel: context.l10n.contactSupport,
secondButtonLabel: context.l10n.ok,
);
if (dialogChoice!.action == ButtonAction.first) {
await sendLogs(
context,
context.l10n.contactSupport,
"auth@ente.io",
postShare: () {},
);
}
}
Widget _getBody() {
return Column(
children: [
@@ -92,17 +195,22 @@ State<LoginPasswordVerificationPage> {
child: ListView(
children: [
Padding(
padding:
const EdgeInsets.only(top: 30, left: 20, right: 20),
padding: const EdgeInsets.only(top: 30, left: 20, right: 20),
child: Text(
context.l10n.enterPassword,
style: Theme.of(context).textTheme.headlineMedium,
),
),
Padding(
padding: const EdgeInsets.only(bottom: 30, left: 22, right:
20,),
child: Text(email ?? '', style: getEnteTextTheme(context).smallMuted,),
padding: const EdgeInsets.only(
bottom: 30,
left: 22,
right: 20,
),
child: Text(
email ?? '',
style: getEnteTextTheme(context).smallMuted,
),
),
Visibility(
// hidden textForm for suggesting auto-fill service for saving
@@ -133,19 +241,19 @@ State<LoginPasswordVerificationPage> {
),
suffixIcon: _passwordInFocus
? IconButton(
icon: Icon(
_passwordVisible
? Icons.visibility
: Icons.visibility_off,
color: Theme.of(context).iconTheme.color,
size: 20,
),
onPressed: () {
setState(() {
_passwordVisible = !_passwordVisible;
});
},
)
icon: Icon(
_passwordVisible
? Icons.visibility
: Icons.visibility_off,
color: Theme.of(context).iconTheme.color,
size: 20,
),
onPressed: () {
setState(() {
_passwordVisible = !_passwordVisible;
});
},
)
: null,
),
style: const TextStyle(
@@ -176,9 +284,11 @@ State<LoginPasswordVerificationPage> {
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () async {
await UserService.instance
.sendOtt(context, email!,
isResetPasswordScreen: true,);
await UserService.instance.sendOtt(
context,
email!,
isResetPasswordScreen: true,
);
},
child: Center(
child: Text(
@@ -187,9 +297,9 @@ State<LoginPasswordVerificationPage> {
.textTheme
.titleMedium!
.copyWith(
fontSize: 14,
decoration: TextDecoration.underline,
),
fontSize: 14,
decoration: TextDecoration.underline,
),
),
),
),
@@ -213,9 +323,9 @@ State<LoginPasswordVerificationPage> {
.textTheme
.titleMedium!
.copyWith(
fontSize: 14,
decoration: TextDecoration.underline,
),
fontSize: 14,
decoration: TextDecoration.underline,
),
),
),
),
@@ -229,4 +339,4 @@ State<LoginPasswordVerificationPage> {
],
);
}
}
}

View File

@@ -5,6 +5,7 @@ import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/ui/account/recovery_key_page.dart';
import 'package:ente_auth/ui/common/dynamic_fab.dart';
import 'package:ente_auth/ui/common/web_page.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_auth/ui/home_page.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/navigation_util.dart';
@@ -24,8 +25,7 @@ enum PasswordEntryMode {
class PasswordEntryPage extends StatefulWidget {
final PasswordEntryMode mode;
const PasswordEntryPage({required this.mode, Key? key})
: super(key: key);
const PasswordEntryPage({required this.mode, Key? key}) : super(key: key);
@override
State<PasswordEntryPage> createState() => _PasswordEntryPageState();
@@ -180,10 +180,11 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
.copyWith(fontSize: 14),
tags: {
'underline': StyledTextTag(
style: Theme.of(context).textTheme.titleMedium!.copyWith(
fontSize: 14,
decoration: TextDecoration.underline,
),
style:
Theme.of(context).textTheme.titleMedium!.copyWith(
fontSize: 14,
decoration: TextDecoration.underline,
),
),
},
),
@@ -356,10 +357,11 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
child: RichText(
text: TextSpan(
text: context.l10n.howItWorks,
style: Theme.of(context).textTheme.titleMedium!.copyWith(
fontSize: 14,
decoration: TextDecoration.underline,
),
style:
Theme.of(context).textTheme.titleMedium!.copyWith(
fontSize: 14,
decoration: TextDecoration.underline,
),
),
),
),
@@ -374,13 +376,18 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
}
void _updatePassword() async {
final logOutFromOthers = await logOutFromOtherDevices(context);
final dialog =
createProgressDialog(context, context.l10n.generatingEncryptionKeys);
await dialog.show();
try {
final result = await Configuration.instance
.getAttributesForNewPassword(_passwordController1.text);
await UserService.instance.updateKeyAttributes(result.item1, result.item2);
await UserService.instance.updateKeyAttributes(
result.item1,
result.item2,
logoutOtherDevices: logOutFromOthers,
);
await dialog.hide();
showShortToast(context, context.l10n.passwordChangedSuccessfully);
Navigator.of(context).pop();
@@ -394,13 +401,34 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
}
}
Future<bool> logOutFromOtherDevices(BuildContext context) async {
bool logOutFromOther = true;
await showChoiceDialog(
context,
title: context.l10n.signOutFromOtherDevices,
body: context.l10n.signOutOtherBody,
isDismissible: false,
firstButtonLabel: context.l10n.signOutOtherDevices,
firstButtonType: ButtonType.critical,
firstButtonOnTap: () async {
logOutFromOther = true;
},
secondButtonLabel: context.l10n.doNotSignOut,
secondButtonOnTap: () async {
logOutFromOther = false;
},
);
return logOutFromOther;
}
Future<void> _showRecoveryCodeDialog(String password) async {
final l10n = context.l10n;
final dialog =
createProgressDialog(context, l10n.generatingEncryptionKeysTitle);
await dialog.show();
try {
final KeyGenResult result = await Configuration.instance.generateKey(password);
final KeyGenResult result =
await Configuration.instance.generateKey(password);
Configuration.instance.setVolatilePassword(null);
await dialog.hide();
onDone() async {

View File

@@ -325,6 +325,7 @@ class _CodeWidgetState extends State<CodeWidget> {
? const EdgeInsets.only(left: 16)
: const EdgeInsets.all(0),
child: IconUtils.instance.getIcon(
context,
safeDecode(widget.code.issuer).trim(),
width: _shouldShowLargeIcon ? 42 : 24,
),

View File

@@ -0,0 +1,209 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/code.dart';
import 'package:ente_auth/services/authenticator_service.dart';
import 'package:ente_auth/store/code_store.dart';
import 'package:ente_auth/ui/common/progress_dialog.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/ui/components/dialog_widget.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_auth/ui/settings/data/import/import_success.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:pointycastle/export.dart';
Future<void> show2FasImportInstruction(BuildContext context) async {
final l10n = context.l10n;
final result = await showDialogWidget(
context: context,
title: l10n.importFromApp("2FAS Authenticator"),
body: l10n.import2FasGuide,
buttons: [
ButtonWidget(
buttonType: ButtonType.primary,
labelText: l10n.importSelectAppExport("2FAS Authenticator"),
isInAlert: true,
buttonSize: ButtonSize.large,
buttonAction: ButtonAction.first,
),
ButtonWidget(
buttonType: ButtonType.secondary,
labelText: context.l10n.cancel,
buttonSize: ButtonSize.large,
isInAlert: true,
buttonAction: ButtonAction.second,
),
],
);
if (result?.action != null && result!.action != ButtonAction.cancel) {
if (result.action == ButtonAction.first) {
await _pick2FasFile(context);
} else {}
}
}
Future<void> _pick2FasFile(BuildContext context) async {
final l10n = context.l10n;
FilePickerResult? result = await FilePicker.platform
.pickFiles(dialogTitle: l10n.importSelectJsonFile);
if (result == null) {
return;
}
final ProgressDialog progressDialog =
createProgressDialog(context, l10n.pleaseWait);
await progressDialog.show();
try {
String path = result.files.single.path!;
int? count = await _process2FasExportFile(context, path, progressDialog);
await progressDialog.hide();
if (count != null) {
await importSuccessDialog(context, count);
}
} catch (e, s) {
Logger('2FASImport').severe('exception while processing import', e, s);
await progressDialog.hide();
await showErrorDialog(
context,
context.l10n.sorry,
context.l10n.importFailureDesc,
);
}
}
Future<int?> _process2FasExportFile(
BuildContext context,
String path,
final ProgressDialog dialog,
) async {
File file = File(path);
final jsonString = await file.readAsString();
final decodedJson = jsonDecode(jsonString);
int version = (decodedJson['schemaVersion'] ?? 0) as int;
if (version != 3 && version != 4) {
await dialog.hide();
// todo: extract strings for l10n. Use same naming format as in aegis
// to avoid duplicate translation efforts.
await showErrorDialog(
context,
'Unsupported format: $version',
version == 0
? "The selected file is not a valid 2FAS Authenticator export."
: "Sorry, the app doesn't support this version of 2FAS Authenticator export",
);
return null;
}
var decodedServices = decodedJson['services'];
// https://github.com/twofas/2fas-android/blob/e97f1a1040eafaed6d5284d54d33403dff215886/data/services/src/main/java/com/twofasapp/data/services/domain/BackupContent.kt#L39
final isEncrypted = decodedJson['reference'] != null;
if (isEncrypted) {
String? password;
try {
await showTextInputDialog(
context,
title: "Enter password to decrypt 2FAS backup",
submitButtonLabel: "Submit",
isPasswordInput: true,
onSubmit: (value) async {
password = value;
},
);
if (password == null) {
await dialog.hide();
return null;
}
final content = decrypt2FasVault(decodedJson, password: password!);
decodedServices = jsonDecode(content);
} catch (e, s) {
Logger("2FASImport").warning("exception while decrypting backup", e, s);
await dialog.hide();
if (password != null) {
await showErrorDialog(
context,
"Failed to decrypt 2Fas export",
"Please check your password and try again.",
);
}
return null;
}
}
final parsedCodes = [];
for (var item in decodedServices) {
var kind = item['otp']['tokenType'];
var account = item['otp']['account'] ?? '';
var issuer = item['otp']['issuer'] ?? item['name'] ?? '';
var algorithm = item['otp']['algorithm'];
var secret = item['secret'];
var timer = item['otp']['period'];
var digits = item['otp']['digits'];
var counter = item['otp']['counter'];
// Build the OTP URL
String otpUrl;
if (kind.toLowerCase() == 'totp') {
otpUrl =
'otpauth://$kind/$issuer:$account?secret=$secret&issuer=$issuer&algorithm=$algorithm&digits=$digits&period=$timer';
} else if (kind.toLowerCase() == 'hotp') {
otpUrl =
'otpauth://$kind/$issuer:$account?secret=$secret&issuer=$issuer&algorithm=$algorithm&digits=$digits&counter=$counter';
} else {
throw Exception('Invalid OTP type');
}
parsedCodes.add(Code.fromRawData(otpUrl));
}
for (final code in parsedCodes) {
await CodeStore.instance.addCode(code, shouldSync: false);
}
unawaited(AuthenticatorService.instance.onlineSync());
int count = parsedCodes.length;
return count;
}
String decrypt2FasVault(dynamic data, {required String password}) {
int ITERATION_COUNT = 10000;
int KEY_SIZE = 256;
final String encryptedServices = data["servicesEncrypted"];
var split = encryptedServices.split(":");
final encryptedData = base64.decode(split[0]);
final salt = base64.decode(split[1]);
final iv = base64.decode(split[2]);
// derive 256 key using PBKDF2WithHmacSHA256 and 10000 iterations and above salt
final pbkdf2 = PBKDF2KeyDerivator(HMac(SHA256Digest(), 64));
final params = Pbkdf2Parameters(
salt,
ITERATION_COUNT,
KEY_SIZE ~/ 8,
);
pbkdf2.init(params);
Uint8List key = Uint8List(KEY_SIZE ~/ 8);
pbkdf2.deriveKey(Uint8List.fromList(utf8.encode(password)), 0, key, 0);
final decrypted = decrypt(key, iv, encryptedData);
final utf8Decode = utf8.decode(decrypted);
return utf8Decode;
}
Uint8List decrypt(Uint8List key, Uint8List iv, Uint8List data) {
final cipher = GCMBlockCipher(AESEngine())
..init(
false,
AEADParameters(
KeyParameter(key),
128,
iv,
Uint8List.fromList(<int>[]),
),
);
final dbBytes = cipher.process(data);
return dbBytes;
}

View File

@@ -0,0 +1,103 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/code.dart';
import 'package:ente_auth/services/authenticator_service.dart';
import 'package:ente_auth/store/code_store.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/ui/components/dialog_widget.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_auth/ui/settings/data/import/import_success.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
Future<void> showBitwardenImportInstruction(BuildContext context) async {
final l10n = context.l10n;
final result = await showDialogWidget(
context: context,
title: l10n.importFromApp("Bitwarden"),
body: l10n.importBitwardenGuide,
buttons: [
ButtonWidget(
buttonType: ButtonType.primary,
labelText: l10n.importSelectJsonFile,
isInAlert: true,
buttonSize: ButtonSize.large,
buttonAction: ButtonAction.first,
),
ButtonWidget(
buttonType: ButtonType.secondary,
labelText: context.l10n.cancel,
buttonSize: ButtonSize.large,
isInAlert: true,
buttonAction: ButtonAction.second,
),
],
);
if (result?.action != null && result!.action != ButtonAction.cancel) {
if (result.action == ButtonAction.first) {
await _pickBitwardenJsonFile(context);
}
}
}
Future<void> _pickBitwardenJsonFile(BuildContext context) async {
final l10n = context.l10n;
FilePickerResult? result = await FilePicker.platform.pickFiles();
if (result == null) {
return;
}
final progressDialog = createProgressDialog(context, l10n.pleaseWait);
await progressDialog.show();
try {
String path = result.files.single.path!;
int? count = await _processBitwardenExportFile(context, path);
await progressDialog.hide();
if (count != null) {
await importSuccessDialog(context, count);
}
} catch (e) {
await progressDialog.hide();
await showErrorDialog(
context,
context.l10n.sorry,
context.l10n.importFailureDesc,
);
}
}
Future<int?> _processBitwardenExportFile(
BuildContext context,
String path,
) async {
File file = File(path);
final jsonString = await file.readAsString();
final data = jsonDecode(jsonString);
List<dynamic> jsonArray = data['items'];
final parsedCodes = [];
for (var item in jsonArray) {
if (item['login']['totp'] != null) {
var issuer = item['name'];
var account = item['login']['username'];
var secret = item['login']['totp'];
parsedCodes.add(
Code.fromAccountAndSecret(
account,
issuer,
secret,
),
);
}
}
for (final code in parsedCodes) {
await CodeStore.instance.addCode(code, shouldSync: false);
}
unawaited(AuthenticatorService.instance.onlineSync());
return parsedCodes.length;
}

View File

@@ -1,4 +1,6 @@
import 'package:ente_auth/ui/settings/data/import/2fas_import.dart';
import 'package:ente_auth/ui/settings/data/import/aegis_import.dart';
import 'package:ente_auth/ui/settings/data/import/bitwarden_import.dart';
import 'package:ente_auth/ui/settings/data/import/encrypted_ente_import.dart';
import 'package:ente_auth/ui/settings/data/import/google_auth_import.dart';
import 'package:ente_auth/ui/settings/data/import/plain_text_import.dart';
@@ -31,6 +33,12 @@ class ImportService {
case ImportType.aegis:
showAegisImportInstruction(context);
break;
case ImportType.twoFas:
show2FasImportInstruction(context);
break;
case ImportType.bitwarden:
showBitwardenImportInstruction(context);
break;
}
}
}

View File

@@ -15,15 +15,19 @@ enum ImportType {
ravio,
googleAuthenticator,
aegis,
twoFas,
bitwarden,
}
class ImportCodePage extends StatelessWidget {
late List<ImportType> importOptions = [
ImportType.plainText,
ImportType.encrypted,
ImportType.ravio,
ImportType.twoFas,
ImportType.aegis,
ImportType.bitwarden,
ImportType.googleAuthenticator,
ImportType.ravio,
];
ImportCodePage({super.key});
@@ -40,6 +44,10 @@ class ImportCodePage extends StatelessWidget {
return 'Google Authenticator';
case ImportType.aegis:
return 'Aegis Authenticator';
case ImportType.twoFas:
return '2FAS Authenticator';
case ImportType.bitwarden:
return 'Bitwarden';
}
}
@@ -62,7 +70,7 @@ class ImportCodePage extends StatelessWidget {
iconButtonType: IconButtonType.secondary,
onTap: () {
Navigator.pop(context);
if(Navigator.canPop(context)) {
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
},

View File

@@ -13,6 +13,7 @@ import 'package:ente_auth/ui/components/toggle_switch_widget.dart';
import 'package:ente_auth/ui/settings/common_settings.dart';
import 'package:ente_auth/ui/settings/language_picker.dart';
import 'package:ente_auth/utils/navigation_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
import 'package:flutter/material.dart';
class AdvancedSectionWidget extends StatefulWidget {
@@ -86,6 +87,9 @@ class _AdvancedSectionWidgetState extends State<AdvancedSectionWidget> {
await PreferenceService.instance.setHideCodes(
!PreferenceService.instance.shouldHideCodes(),
);
if(PreferenceService.instance.shouldHideCodes()) {
showToast(context, context.l10n.doubleTapToViewHiddenCode);
}
setState(() {});
},
),
@@ -107,21 +111,6 @@ class _AdvancedSectionWidgetState extends State<AdvancedSectionWidget> {
),
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: CaptionedTextWidget(
title: l10n.crashAndErrorReporting,
),
trailingWidget: ToggleSwitchWidget(
value: () => SuperLogging.shouldReportErrors(),
onChanged: () async {
await SuperLogging.setShouldReportErrors(
!SuperLogging.shouldReportErrors(),
);
setState(() {});
},
),
),
sectionOptionSpacing,
if (Platform.isAndroid) ...[
MenuItemWidget(
captionedTextWidget: CaptionedTextWidget(
@@ -139,6 +128,21 @@ class _AdvancedSectionWidgetState extends State<AdvancedSectionWidget> {
),
sectionOptionSpacing,
],
MenuItemWidget(
captionedTextWidget: CaptionedTextWidget(
title: l10n.crashAndErrorReporting,
),
trailingWidget: ToggleSwitchWidget(
value: () => SuperLogging.shouldReportErrors(),
onChanged: () async {
await SuperLogging.setShouldReportErrors(
!SuperLogging.shouldReportErrors(),
);
setState(() {});
},
),
),
sectionOptionSpacing,
],
);
}

View File

@@ -8,6 +8,7 @@ import 'package:ente_auth/ui/settings/common_settings.dart';
import 'package:ente_auth/ui/settings/faq.dart';
import 'package:ente_auth/utils/email_util.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:url_launcher/url_launcher_string.dart';
class SupportSectionWidget extends StatefulWidget {
@@ -90,8 +91,12 @@ class _SupportSectionWidgetState extends State<SupportSectionWidget> {
await sendLogs(context, l10n.reportBug, "auth@ente.io");
},
onDoubleTap: () async {
final zipFilePath = await getZippedLogsFile(context);
await shareLogs(context, "auth@ente.io", zipFilePath);
try {
final zipFilePath = await getZippedLogsFile(context);
await shareLogs(context, "auth@ente.io", zipFilePath);
} catch (e) {
Logger("SupportSectionWidget").severe("failed to export logs", e);
}
},
),
sectionOptionSpacing,

View File

@@ -3,7 +3,9 @@ import 'dart:io';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/onboarding/view/onboarding_page.dart';
import 'package:ente_auth/services/local_authentication_service.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/store/code_store.dart';
import 'package:ente_auth/theme/colors.dart';
import 'package:ente_auth/theme/ente_theme.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
@@ -99,6 +101,19 @@ class SettingsPage extends StatelessWidget {
await handleExportClick(context);
} else {
if (result.action == ButtonAction.second) {
bool hasCodes =
(await CodeStore.instance.getAllCodes()).isNotEmpty;
if (hasCodes) {
final hasAuthenticated = await LocalAuthenticationService
.instance
.requestLocalAuthentication(
context,
context.l10n.authToInitiateSignIn,
);
if (!hasAuthenticated) {
return;
}
}
await routeToPage(
context,
const OnboardingPage(),

View File

@@ -1,5 +1,6 @@
import 'dart:io';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/ui/common/gradient_button.dart';
import 'package:ente_auth/ui/tools/app_lock.dart';
import 'package:ente_auth/utils/auth_util.dart';
@@ -13,13 +14,25 @@ class LockScreen extends StatefulWidget {
State<LockScreen> createState() => _LockScreenState();
}
class _LockScreenState extends State<LockScreen> {
class _LockScreenState extends State<LockScreen> with WidgetsBindingObserver {
final _logger = Logger("LockScreen");
bool _isShowingLockScreen = false;
bool _hasPlacedAppInBackground = false;
bool _hasAuthenticationFailed = false;
int? lastAuthenticatingTime;
@override
void initState() {
_showLockScreen();
_logger.info("initiatingState");
super.initState();
WidgetsBinding.instance.addObserver(this);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (isNonMobileIOSDevice()) {
_logger.info('ignore init for non mobile iOS device');
return;
}
_showLockScreen(source: "postFrameInit");
});
}
@override
@@ -34,16 +47,16 @@ class _LockScreenState extends State<LockScreen> {
alignment: Alignment.center,
children: [
Opacity(
opacity: 0.3,
opacity: 0.2,
child: Image.asset('assets/loading_photos_background.png'),
),
SizedBox(
width: 142,
width: 180,
child: GradientButton(
text: "Unlock",
text: context.l10n.unlock,
iconData: Icons.lock_open_outlined,
onTap: () async {
_showLockScreen();
_showLockScreen(source: "tapUnlock");
},
),
),
@@ -55,16 +68,76 @@ class _LockScreenState extends State<LockScreen> {
);
}
Future<void> _showLockScreen() async {
_logger.info("Showing lockscreen");
bool isNonMobileIOSDevice() {
if (Platform.isAndroid) {
return false;
}
var shortestSide = MediaQuery.of(context).size.shortestSide;
return shortestSide > 600 ? true : false;
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
_logger.info(state.toString());
if (state == AppLifecycleState.resumed && !_isShowingLockScreen) {
// This is triggered either when the lock screen is dismissed or when
// the app is brought to foreground
_hasPlacedAppInBackground = false;
final bool didAuthInLast5Seconds = lastAuthenticatingTime != null &&
DateTime.now().millisecondsSinceEpoch - lastAuthenticatingTime! <
5000;
if (!_hasAuthenticationFailed && !didAuthInLast5Seconds) {
// Show the lock screen again only if the app is resuming from the
// background, and not when the lock screen was explicitly dismissed
Future.delayed(
Duration.zero,
() => _showLockScreen(source: "lifeCycle"),
);
} else {
_hasAuthenticationFailed = false; // Reset failure state
}
} else if (state == AppLifecycleState.paused ||
state == AppLifecycleState.inactive) {
// This is triggered either when the lock screen pops up or when
// the app is pushed to background
if (!_isShowingLockScreen) {
_hasPlacedAppInBackground = true;
_hasAuthenticationFailed = false; // reset failure state
}
}
}
@override
void dispose() {
_logger.info('disposing');
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
Future<void> _showLockScreen({String source = ''}) async {
final int id = DateTime.now().millisecondsSinceEpoch;
_logger.info("Showing lock screen $source $id");
try {
_isShowingLockScreen = true;
final result = await requestAuthentication(
"Please authenticate to view your secrets",
context,
context.l10n.authToViewSecrets,
);
_logger.finest("LockScreen Result $result $id");
_isShowingLockScreen = false;
if (result) {
lastAuthenticatingTime = DateTime.now().millisecondsSinceEpoch;
AppLock.of(context)!.didUnlock();
} else {
if (!_hasPlacedAppInBackground) {
// Treat this as a failure only if user did not explicitly
// put the app in background
_hasAuthenticationFailed = true;
_logger.info("Authentication failed");
}
}
} catch (e, s) {
_isShowingLockScreen = false;
_logger.severe(e, s);
}
}

View File

@@ -1,9 +1,10 @@
import 'dart:convert';
import 'package:ente_auth/theme/ente_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_svg/svg.dart';
import 'package:logging/logging.dart';
class IconUtils {
IconUtils._privateConstructor();
@@ -19,6 +20,7 @@ class IconUtils {
}
Widget getIcon(
BuildContext context,
String provider, {
double width = 24,
}) {
@@ -37,6 +39,20 @@ class IconUtils {
_simpleIcons[title],
width,
);
} else if (title.isNotEmpty) {
bool showLargeIcon = width > 24;
return CircleAvatar(
radius: width / 2,
backgroundColor: getEnteColorScheme(context).avatarColors[
title.hashCode % getEnteColorScheme(context).avatarColors.length],
child: Text(
title.toUpperCase()[0],
// fixed color
style: showLargeIcon
? getEnteTextTheme(context).h3Bold.copyWith(color: Colors.white)
: getEnteTextTheme(context).body.copyWith(color: Colors.white),
),
);
} else {
return const SizedBox.shrink();
}
@@ -62,20 +78,32 @@ class IconUtils {
}
Future<void> _loadJson() async {
final simpleIconData = await rootBundle
.loadString('assets/simple-icons/_data/simple-icons.json');
final simpleIcons = json.decode(simpleIconData);
for (final icon in simpleIcons["icons"]) {
_simpleIcons[icon["title"].toString().toLowerCase()] = icon["hex"];
}
final customIconData = await rootBundle
.loadString('assets/custom-icons/_data/custom-icons.json');
final customIcons = json.decode(customIconData);
for (final icon in customIcons["icons"]) {
_customIcons[icon["title"].toString().toLowerCase()] = CustomIconData(
icon["slug"],
icon["hex"],
);
try {
final simpleIconData = await rootBundle
.loadString('assets/simple-icons/_data/simple-icons.json');
final simpleIcons = json.decode(simpleIconData);
for (final icon in simpleIcons["icons"]) {
_simpleIcons[icon["title"].toString().toLowerCase()] = icon["hex"];
}
final customIconData = await rootBundle
.loadString('assets/custom-icons/_data/custom-icons.json');
final customIcons = json.decode(customIconData);
for (final icon in customIcons["icons"]) {
_customIcons[icon["title"].toString().toLowerCase()] = CustomIconData(
icon["slug"],
icon["hex"],
);
if (icon["altNames"] != null) {
for (final name in icon["altNames"]) {
_customIcons[name] = CustomIconData(
icon["slug"],
icon["hex"],
);
}
}
}
} catch (e) {
Logger("IconUtils").severe("Error loading icons", e);
}
}

View File

@@ -1,25 +1,37 @@
import 'package:ente_auth/l10n/l10n.dart';
import 'package:flutter/cupertino.dart';
import 'package:local_auth/local_auth.dart';
import 'package:local_auth_android/local_auth_android.dart';
import 'package:local_auth_ios/types/auth_messages_ios.dart';
import 'package:logging/logging.dart';
Future<bool> requestAuthentication(String reason) async {
Future<bool> requestAuthentication(BuildContext context, String reason) async {
Logger("AuthUtil").info("Requesting authentication");
await LocalAuthentication().stopAuthentication();
final l10n = context.l10n;
return await LocalAuthentication().authenticate(
localizedReason: reason,
authMessages: [
const AndroidAuthMessages(
biometricHint: "Verify identity",
biometricNotRecognized: "Not recognized, try again",
biometricRequiredTitle: "Biometric required",
biometricSuccess: "Successfully verified",
cancelButton: "Cancel",
deviceCredentialsRequiredTitle: "Device credentials required",
deviceCredentialsSetupDescription: "Device credentials required",
goToSettingsButton: "Go to settings",
goToSettingsDescription:
"Authentication is not setup on your device, go to Settings > Security to set it up",
signInTitle: "Authentication required",
AndroidAuthMessages(
biometricHint: l10n.androidBiometricHint,
biometricNotRecognized: l10n.androidBiometricNotRecognized,
biometricRequiredTitle: l10n.androidBiometricRequiredTitle,
biometricSuccess: l10n.androidBiometricSuccess,
cancelButton: l10n.androidCancelButton,
deviceCredentialsRequiredTitle:
l10n.androidDeviceCredentialsRequiredTitle,
deviceCredentialsSetupDescription:
l10n.androidDeviceCredentialsSetupDescription,
goToSettingsButton: l10n.goToSettings,
goToSettingsDescription: l10n.androidGoToSettingsDescription,
signInTitle: l10n.androidSignInTitle,
),
IOSAuthMessages(
goToSettingsButton: l10n.goToSettings,
goToSettingsDescription: l10n.goToSettings,
lockOut: l10n.iOSLockOut,
// cancelButton default value is "Ok"
cancelButton: l10n.iOSOkButton,
),
],
);

View File

@@ -42,8 +42,8 @@ Uint8List cryptoPwHash(Map<String, dynamic> args) {
}
Uint8List cryptoKdfDeriveFromKey(
Map<String, dynamic> args,
) {
Map<String, dynamic> args,
) {
return Sodium.cryptoKdfDeriveFromKey(
args["subkeyLen"],
args["subkeyId"],
@@ -58,7 +58,7 @@ Future<Uint8List> cryptoGenericHash(Map<String, dynamic> args) async {
final sourceFileLength = await sourceFile.length();
final inputFile = sourceFile.openSync(mode: io.FileMode.read);
final state =
Sodium.cryptoGenerichashInit(null, Sodium.cryptoGenerichashBytesMax);
Sodium.cryptoGenerichashInit(null, Sodium.cryptoGenerichashBytesMax);
var bytesRead = 0;
bool isDone = false;
while (!isDone) {
@@ -77,7 +77,7 @@ Future<Uint8List> cryptoGenericHash(Map<String, dynamic> args) async {
EncryptionResult chachaEncryptData(Map<String, dynamic> args) {
final initPushResult =
Sodium.cryptoSecretstreamXchacha20poly1305InitPush(args["key"]);
Sodium.cryptoSecretstreamXchacha20poly1305InitPush(args["key"]);
final encryptedData = Sodium.cryptoSecretstreamXchacha20poly1305Push(
initPushResult.state,
args["source"],
@@ -102,7 +102,7 @@ Future<EncryptionResult> chachaEncryptFile(Map<String, dynamic> args) async {
final inputFile = sourceFile.openSync(mode: io.FileMode.read);
final key = args["key"] ?? Sodium.cryptoSecretstreamXchacha20poly1305Keygen();
final initPushResult =
Sodium.cryptoSecretstreamXchacha20poly1305InitPush(key);
Sodium.cryptoSecretstreamXchacha20poly1305InitPush(key);
var bytesRead = 0;
var tag = Sodium.cryptoSecretstreamXchacha20poly1305TagMessage;
while (tag != Sodium.cryptoSecretstreamXchacha20poly1305TagFinal) {
@@ -156,7 +156,7 @@ Future<void> chachaDecryptFile(Map<String, dynamic> args) async {
final buffer = await inputFile.read(chunkSize);
bytesRead += chunkSize;
final pullResult =
Sodium.cryptoSecretstreamXchacha20poly1305Pull(pullState, buffer, null);
Sodium.cryptoSecretstreamXchacha20poly1305Pull(pullState, buffer, null);
await destinationFile.writeAsBytes(pullResult.m, mode: io.FileMode.append);
tag = pullResult.tag;
}
@@ -190,20 +190,22 @@ class CryptoUtil {
Sodium.init();
}
static Uint8List base642bin(String b64, {
static Uint8List base642bin(
String b64, {
String? ignore,
int variant = Sodium.base64VariantOriginal,
}) {
return Sodium.base642bin(b64, ignore: ignore, variant: variant);
}
static String bin2base64(Uint8List bin, {
static String bin2base64(
Uint8List bin, {
bool urlSafe = false,
}) {
return Sodium.bin2base64(
bin,
variant:
urlSafe ? Sodium.base64VariantUrlsafe : Sodium.base64VariantOriginal,
urlSafe ? Sodium.base64VariantUrlsafe : Sodium.base64VariantOriginal,
);
}
@@ -237,9 +239,11 @@ class CryptoUtil {
// Decrypts the given cipher, with the given key and nonce using XSalsa20
// (w Poly1305 MAC).
static Future<Uint8List> decrypt(Uint8List cipher,
Uint8List key,
Uint8List nonce,) async {
static Future<Uint8List> decrypt(
Uint8List cipher,
Uint8List key,
Uint8List nonce,
) async {
final args = <String, dynamic>{};
args["cipher"] = cipher;
args["nonce"] = nonce;
@@ -256,9 +260,11 @@ class CryptoUtil {
// This function runs on the same thread as the caller, so should be used only
// for small amounts of data where thread switching can result in a degraded
// user experience
static Uint8List decryptSync(Uint8List cipher,
Uint8List key,
Uint8List nonce,) {
static Uint8List decryptSync(
Uint8List cipher,
Uint8List key,
Uint8List nonce,
) {
final args = <String, dynamic>{};
args["cipher"] = cipher;
args["nonce"] = nonce;
@@ -270,8 +276,10 @@ class CryptoUtil {
// nonce, using XChaCha20 (w Poly1305 MAC).
// This function runs on the isolate pool held by `_computer`.
// TODO: Remove "ChaCha", an implementation detail from the function name
static Future<EncryptionResult> encryptChaCha(Uint8List source,
Uint8List key,) async {
static Future<EncryptionResult> encryptChaCha(
Uint8List source,
Uint8List key,
) async {
final args = <String, dynamic>{};
args["source"] = source;
args["key"] = key;
@@ -285,9 +293,11 @@ class CryptoUtil {
// Decrypts the given source, with the given key and header using XChaCha20
// (w Poly1305 MAC).
// TODO: Remove "ChaCha", an implementation detail from the function name
static Future<Uint8List> decryptChaCha(Uint8List source,
Uint8List key,
Uint8List header,) async {
static Future<Uint8List> decryptChaCha(
Uint8List source,
Uint8List key,
Uint8List header,
) async {
final args = <String, dynamic>{};
args["source"] = source;
args["key"] = key;
@@ -304,10 +314,10 @@ class CryptoUtil {
// to the destinationFilePath.
// If a key is not provided, one is generated and returned.
static Future<EncryptionResult> encryptFile(
String sourceFilePath,
String destinationFilePath, {
Uint8List? key,
}) {
String sourceFilePath,
String destinationFilePath, {
Uint8List? key,
}) {
final args = <String, dynamic>{};
args["sourceFilePath"] = sourceFilePath;
args["destinationFilePath"] = destinationFilePath;
@@ -322,10 +332,11 @@ class CryptoUtil {
// Decrypts the file at sourceFilePath, with the given key and header using
// XChaCha20 (w Poly1305 MAC), and writes it to the destinationFilePath.
static Future<void> decryptFile(
String sourceFilePath,
String destinationFilePath,
Uint8List header,
Uint8List key,) {
String sourceFilePath,
String destinationFilePath,
Uint8List header,
Uint8List key,
) {
final args = <String, dynamic>{};
args["sourceFilePath"] = sourceFilePath;
args["destinationFilePath"] = destinationFilePath;
@@ -356,10 +367,10 @@ class CryptoUtil {
// Decrypts the input using the given publicKey-secretKey pair
static Uint8List openSealSync(
Uint8List input,
Uint8List publicKey,
Uint8List secretKey,
) {
Uint8List input,
Uint8List publicKey,
Uint8List secretKey,
) {
return Sodium.cryptoBoxSealOpen(input, publicKey, secretKey);
}
@@ -377,9 +388,9 @@ class CryptoUtil {
// At all points, we ensure that the product of these two variables (the area
// under the graph that determines the amount of work required) is a constant.
static Future<DerivedKeyResult> deriveSensitiveKey(
Uint8List password,
Uint8List salt,
) async {
Uint8List password,
Uint8List salt,
) async {
final logger = Logger("pwhash");
int memLimit = Sodium.cryptoPwhashMemlimitSensitive;
int opsLimit = Sodium.cryptoPwhashOpslimitSensitive;
@@ -407,7 +418,10 @@ class CryptoUtil {
return DerivedKeyResult(key, memLimit, opsLimit);
} catch (e, s) {
logger.warning(
"failed to deriveKey mem: $memLimit, ops: $opsLimit", e, s,);
"failed to deriveKey mem: $memLimit, ops: $opsLimit",
e,
s,
);
}
memLimit = (memLimit / 2).round();
opsLimit = opsLimit * 2;
@@ -421,9 +435,9 @@ class CryptoUtil {
// extra layer of authentication (atop the access token and collection key).
// More details @ https://ente.io/blog/building-shareable-links/
static Future<DerivedKeyResult> deriveInteractiveKey(
Uint8List password,
Uint8List salt,
) async {
Uint8List password,
Uint8List salt,
) async {
final int memLimit = Sodium.cryptoPwhashMemlimitInteractive;
final int opsLimit = Sodium.cryptoPwhashOpslimitInteractive;
final key = await deriveKey(password, salt, memLimit, opsLimit);
@@ -433,13 +447,13 @@ class CryptoUtil {
// Derives a key for a given password, salt, memLimit and opsLimit using
// Argon2id, v1.3.
static Future<Uint8List> deriveKey(
Uint8List password,
Uint8List salt,
int memLimit,
int opsLimit,
) {
Uint8List password,
Uint8List salt,
int memLimit,
int opsLimit,
) async {
try {
return _computer.compute(
return await _computer.compute(
cryptoPwHash,
param: {
"password": password,
@@ -449,7 +463,7 @@ class CryptoUtil {
},
taskName: "deriveKey",
);
} catch(e,s) {
} catch (e, s) {
final String errMessage = 'failed to deriveKey memLimit: $memLimit and '
'opsLimit: $opsLimit';
Logger("CryptoUtilDeriveKey").warning(errMessage, e, s);
@@ -461,20 +475,25 @@ class CryptoUtil {
// (Key Derivation Function) with the `loginSubKeyId` and
// `loginSubKeyLen` and `loginSubKeyContext` as context
static Future<Uint8List> deriveLoginKey(
Uint8List key,
) async {
final Uint8List derivedKey = await _computer.compute(
cryptoKdfDeriveFromKey,
param: {
"key": key,
"subkeyId": loginSubKeyId,
"subkeyLen": loginSubKeyLen,
"context": utf8.encode(loginSubKeyContext),
},
taskName: "deriveLoginKey",
);
// return the first 16 bytes of the derived key
return derivedKey.sublist(0, 16);
Uint8List key,
) async {
try {
final Uint8List derivedKey = await _computer.compute(
cryptoKdfDeriveFromKey,
param: {
"key": key,
"subkeyId": loginSubKeyId,
"subkeyLen": loginSubKeyLen,
"context": utf8.encode(loginSubKeyContext),
},
taskName: "deriveLoginKey",
);
// return the first 16 bytes of the derived key
return derivedKey.sublist(0, 16);
} catch (e, s) {
Logger("deriveLoginKey").severe("loginKeyDerivation failed", e, s);
throw LoginKeyDerivationError();
}
}
// Computes and returns the hash of the source file

View File

@@ -13,9 +13,11 @@ import 'package:ente_auth/ui/tools/debug/log_file_viewer.dart';
// import 'package:ente_auth/ui/tools/debug/log_file_viewer.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
import 'package:file_saver/file_saver.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_email_sender/flutter_email_sender.dart';
import 'package:intl/intl.dart';
import 'package:logging/logging.dart';
// import 'package:open_mail_app/open_mail_app.dart';
import 'package:package_info_plus/package_info_plus.dart';
@@ -198,9 +200,26 @@ Future<void> shareLogs(
],
);
if (result?.action != null && result!.action == ButtonAction.second) {
final Size size = MediaQuery.of(context).size;
await Share.shareFiles(
[zipFilePath],
await exportLogs(context, zipFilePath);
}
}
Future<void> exportLogs(BuildContext context, String zipFilePath) async {
final Size size = MediaQuery.of(context).size;
if (Platform.isAndroid) {
DateTime now = DateTime.now().toUtc();
String shortMonthName = DateFormat('MMM').format(now); // Short month name
String logFileName =
'ente-logs-${now.year}-$shortMonthName-${now.day}-${now.hour}-${now.minute}';
await FileSaver.instance.saveAs(
name: logFileName,
filePath: zipFilePath,
mimeType: MimeType.zip,
ext: 'zip',
);
} else {
await Share.shareXFiles(
[XFile(zipFilePath, mimeType: 'application/zip')],
sharePositionOrigin: Rect.fromLTWH(0, 0, size.width, size.height / 2),
);
}

View File

@@ -6,11 +6,15 @@
#include "generated_plugin_registrant.h"
#include <file_saver/file_saver_plugin.h>
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
#include <sentry_flutter/sentry_flutter_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_saver_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin");
file_saver_plugin_register_with_registrar(file_saver_registrar);
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);

View File

@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
file_saver
flutter_secure_storage_linux
sentry_flutter
url_launcher_linux

View File

@@ -7,12 +7,13 @@ import Foundation
import connectivity_macos
import device_info_plus
import file_saver
import flutter_local_notifications
import flutter_secure_storage_macos
import package_info_plus
import path_provider_foundation
import sentry_flutter
import share_plus_macos
import share_plus
import shared_preferences_foundation
import sqflite
import url_launcher_macos
@@ -20,6 +21,7 @@ import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))

View File

@@ -10,6 +10,9 @@ A user on GitHub has written a guide to export our data from Authy (morpheus on
## Exporting from Authy
To export your data, please follow [this guide](https://gist.github.com/gboudreau/94bb0c11a6209c82418d01a59d958c93). This will create a new JSON file with all your Authy TOTP data in it. **Do not share this file with anyone!**
Or, you can [use this tool by Neeraj](https://github.com/ua741/authy-export/releases/tag/v0.0.4) to simplify things and skip to importing to ente Authenticator.
### *Do note that these tools may not export ALL of your codes. Make sure that all your accounts have been imported successfully before deleting any codes from your Authy account!*
## Converting the export for ente Authenticator
So now that you have the JSON file, does that mean it can be imported into ente Authenticator? Nope.
@@ -17,7 +20,7 @@ This is because the code in the guide exports your Authy data for Bitwarden, not
<img width="454" alt="ente Authenticator Screenshot" src="https://github.com/gweeeen/auth/assets/41323182/30566a69-cfa0-4de0-9f0d-95967d4c5cad">
So, this means that even if you try to import this file, nothing will happen. But don't worry, I've written a program in Python that converts the JSON file into a TXT file that ente Authenticator can use!
So, this means that even if you try to import this file, nothing will happen. But don't worry, I've written a program in Python that converts the JSON file into a TXT file that ente Authenticator can use! (It's definitely not written **professionaly**, but hey it gets the job done so I'm happy with that.)
You can download my program [here](https://github.com/gweeeen/ducky/blob/main/duckys_other_stuff/authy_to_ente.py). Or if you **really like making life hard**, then you can make a new Python file and copy this code to it:

View File

@@ -418,6 +418,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.3.1"
file_saver:
dependency: "direct main"
description:
name: file_saver
sha256: "591d25e750e3a4b654f7b0293abc2ed857242f82ca7334051b2a8ceeb369dac8"
url: "https://pub.dev"
source: hosted
version: "0.2.8"
fixnum:
dependency: transitive
description:
@@ -675,10 +683,10 @@ packages:
dependency: "direct main"
description:
name: http
sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
url: "https://pub.dev"
source: hosted
version: "0.13.6"
version: "1.1.0"
http_multi_server:
dependency: transitive
description:
@@ -760,7 +768,7 @@ packages:
source: hosted
version: "2.1.7"
local_auth_android:
dependency: transitive
dependency: "direct main"
description:
name: local_auth_android
sha256: "523dd636ce061ddb296cbc3db410cb8f21efb7d8798f7b9532c8038ce2f8bad5"
@@ -768,7 +776,7 @@ packages:
source: hosted
version: "1.0.31"
local_auth_ios:
dependency: transitive
dependency: "direct main"
description:
name: local_auth_ios
sha256: edc2977c5145492f3451db9507a2f2f284ee4f408950b3e16670838726761940
@@ -1123,50 +1131,18 @@ packages:
dependency: "direct main"
description:
name: share_plus
sha256: f582d5741930f3ad1bf0211d358eddc0508cc346e5b4b248bd1e569c995ebb7a
sha256: f74fc3f1cbd99f39760182e176802f693fa0ec9625c045561cfad54681ea93dd
url: "https://pub.dev"
source: hosted
version: "4.5.3"
share_plus_linux:
dependency: transitive
description:
name: share_plus_linux
sha256: dc32bf9f1151b9864bb86a997c61a487967a08f2e0b4feaa9a10538712224da4
url: "https://pub.dev"
source: hosted
version: "3.0.1"
share_plus_macos:
dependency: transitive
description:
name: share_plus_macos
sha256: "44daa946f2845045ecd7abb3569b61cd9a55ae9cc4cbec9895b2067b270697ae"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
version: "7.2.1"
share_plus_platform_interface:
dependency: transitive
description:
name: share_plus_platform_interface
sha256: "0c6e61471bd71b04a138b8b588fa388e66d8b005e6f2deda63371c5c505a0981"
sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956
url: "https://pub.dev"
source: hosted
version: "3.2.1"
share_plus_web:
dependency: transitive
description:
name: share_plus_web
sha256: eaef05fa8548b372253e772837dd1fbe4ce3aca30ea330765c945d7d4f7c9935
url: "https://pub.dev"
source: hosted
version: "3.1.0"
share_plus_windows:
dependency: transitive
description:
name: share_plus_windows
sha256: "3a21515ae7d46988d42130cd53294849e280a5de6ace24bae6912a1bffd757d4"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
version: "3.3.1"
shared_preferences:
dependency: "direct main"
description:

View File

@@ -1,6 +1,6 @@
name: ente_auth
description: ente two-factor authenticator
version: 2.0.10+210
version: 2.0.26+226
publish_to: none
environment:
@@ -27,6 +27,8 @@ dependencies:
expandable: ^5.0.1
expansion_tile_card: ^3.0.0
file_picker: ^5.2.4
# https://github.com/incrediblezayed/file_saver/issues/86
file_saver: 0.2.8
fk_user_agent: ^2.1.0
flutter:
sdk: flutter
@@ -48,10 +50,13 @@ dependencies:
flutter_svg: ^2.0.5
fluttertoast: ^8.1.1
google_nav_bar: ^5.0.5 #supported
http: ^0.13.4
http: ^1.1.0
intl: ^0.18.0
json_annotation: ^4.5.0
local_auth: ^2.1.7
local_auth_android: ^1.0.31
local_auth_ios: ^1.1.3
logging: ^1.0.1
modal_bottom_sheet: ^3.0.0-pre
move_to_background: ^1.0.2
@@ -67,7 +72,7 @@ dependencies:
qr_flutter: 4.0.0
sentry: ^7.9.0
sentry_flutter: ^7.9.0
share_plus: ^4.4.0
share_plus: ^7.2.1
shared_preferences: ^2.0.5
sqflite: ^2.1.0
step_progress_indicator: ^1.0.2

View File

@@ -6,18 +6,24 @@
#include "generated_plugin_registrant.h"
#include <file_saver/file_saver_plugin.h>
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <local_auth_windows/local_auth_plugin.h>
#include <sentry_flutter/sentry_flutter_plugin.h>
#include <share_plus/share_plus_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
FileSaverPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSaverPlugin"));
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
LocalAuthPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("LocalAuthPlugin"));
SentryFlutterPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SentryFlutterPlugin"));
SharePlusWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
}

View File

@@ -3,9 +3,11 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
file_saver
flutter_secure_storage_windows
local_auth_windows
sentry_flutter
share_plus
url_launcher_windows
)