Compare commits
397 Commits
update_doc
...
auth-v2.0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62276d49d2 | ||
|
|
e7f26ba250 | ||
|
|
86a334a769 | ||
|
|
a6cb9023bf | ||
|
|
a4ecc2c344 | ||
|
|
147d79c64f | ||
|
|
704abf1265 | ||
|
|
9f32a8aa83 | ||
|
|
4b7155dd13 | ||
|
|
3b29f8ec2c | ||
|
|
eecb8b70f4 | ||
|
|
8e4ca0bc79 | ||
|
|
b98da635b6 | ||
|
|
37dce2bdb5 | ||
|
|
f8a36852a6 | ||
|
|
892bf852a5 | ||
|
|
f53b1361e8 | ||
|
|
4261624da5 | ||
|
|
186e76c62b | ||
|
|
96ea53face | ||
|
|
67468c6ff9 | ||
|
|
1859bfd9da | ||
|
|
893421de6e | ||
|
|
549e1a2753 | ||
|
|
e8acbd8c9f | ||
|
|
b185a37435 | ||
|
|
2522da2b5e | ||
|
|
bd5c56349a | ||
|
|
dd7e87274a | ||
|
|
106ba270fe | ||
|
|
ed4886a6a5 | ||
|
|
bdd6663461 | ||
|
|
a403cd88e2 | ||
|
|
d11ee7acb4 | ||
|
|
47370bdeaf | ||
|
|
f3bdfa84ef | ||
|
|
4bf99c8bea | ||
|
|
d3093809d6 | ||
|
|
81ba5379c9 | ||
|
|
af22c48a99 | ||
|
|
f21dc84840 | ||
|
|
19c2e50ef2 | ||
|
|
b1d0909675 | ||
|
|
5aec3bc7fd | ||
|
|
7704b902c4 | ||
|
|
bdb2a22863 | ||
|
|
1f091e33df | ||
|
|
47a7042d55 | ||
|
|
a5c3aff54b | ||
|
|
9e6e91fe7e | ||
|
|
35ebf37849 | ||
|
|
120edbbc65 | ||
|
|
7a3209ebf6 | ||
|
|
efa49bd2b0 | ||
|
|
e953c1c16b | ||
|
|
f7d2b735f9 | ||
|
|
bbeac2bdf0 | ||
|
|
517ee0914c | ||
|
|
53c53cebbc | ||
|
|
d673537110 | ||
|
|
0fd4b2936d | ||
|
|
a96cb58f87 | ||
|
|
2de4e51c7b | ||
|
|
d297465ea6 | ||
|
|
0fdb2fb357 | ||
|
|
6c8f4185f7 | ||
|
|
31ad9a3eb3 | ||
|
|
0a7991bc0e | ||
|
|
302b9a46b5 | ||
|
|
68fe6f45c4 | ||
|
|
03bad54bce | ||
|
|
3dbf82552d | ||
|
|
22e57669fb | ||
|
|
193c8d8899 | ||
|
|
f083f0633d | ||
|
|
f2c288bdab | ||
|
|
87de9e2afa | ||
|
|
1830a1b931 | ||
|
|
b6b4ee7e3a | ||
|
|
c41bb571f6 | ||
|
|
94ac21950d | ||
|
|
8c40b950c7 | ||
|
|
8391e7edfc | ||
|
|
d98c3686c5 | ||
|
|
025ef4e1d9 | ||
|
|
18deac3a41 | ||
|
|
b1f45c8602 | ||
|
|
388904a46a | ||
|
|
c9e008895c | ||
|
|
784b003ceb | ||
|
|
4c33030f28 | ||
|
|
1157919496 | ||
|
|
3c4619e5e4 | ||
|
|
e58ef51d12 | ||
|
|
e4b0cca777 | ||
|
|
11e30cf79a | ||
|
|
21526722df | ||
|
|
4ea43e619b | ||
|
|
cd1d22cdfb | ||
|
|
cc623e5010 | ||
|
|
bb865a7b15 | ||
|
|
1361ca0c8e | ||
|
|
a4b8571642 | ||
|
|
2a5d9cd47f | ||
|
|
93e187d9e7 | ||
|
|
1acbe806ae | ||
|
|
b1ab27e42c | ||
|
|
5710dec46b | ||
|
|
3a2c7ce5f9 | ||
|
|
46d7803da0 | ||
|
|
19c2f07a19 | ||
|
|
43ebd7147d | ||
|
|
812e02d4d7 | ||
|
|
dd0678d3d0 | ||
|
|
8101fe1495 | ||
|
|
ec258e8b2a | ||
|
|
b769c428eb | ||
|
|
61d5f3189b | ||
|
|
7b1b6bf88f | ||
|
|
4c651da244 | ||
|
|
467acf2b8e | ||
|
|
7cc795ead3 | ||
|
|
e25089a55c | ||
|
|
964fc8c992 | ||
|
|
0eedd5e57f | ||
|
|
0634727ef3 | ||
|
|
8b0ce5d521 | ||
|
|
b77ac2a2d8 | ||
|
|
35ba5b6685 | ||
|
|
01ec6e04d4 | ||
|
|
4b3a8117b7 | ||
|
|
2bf03cbcaf | ||
|
|
8f2bb9c861 | ||
|
|
83477cfe37 | ||
|
|
3ce8513e48 | ||
|
|
b3289f2b8d | ||
|
|
3a6c7b2dcd | ||
|
|
502469e97f | ||
|
|
c98762b448 | ||
|
|
e00b8fd2f1 | ||
|
|
eeaa5165ab | ||
|
|
e4684b22df | ||
|
|
d80fad6cf1 | ||
|
|
a8de049519 | ||
|
|
e9fa525fe9 | ||
|
|
bff58f7b78 | ||
|
|
a0f95b6080 | ||
|
|
c19370ec51 | ||
|
|
341f0fa559 | ||
|
|
e64d44639d | ||
|
|
04b9966425 | ||
|
|
0e861d5c48 | ||
|
|
b7b33eba4a | ||
|
|
1266648562 | ||
|
|
eb4f9402db | ||
|
|
983beb89dc | ||
|
|
f29d9e1583 | ||
|
|
d83f9d0210 | ||
|
|
65d25690e5 | ||
|
|
7c383023e4 | ||
|
|
5092b74503 | ||
|
|
ed547d5f2c | ||
|
|
1e50570936 | ||
|
|
2419b079af | ||
|
|
cfd298a052 | ||
|
|
f0547d0a10 | ||
|
|
fa3f249a7a | ||
|
|
449284a6a1 | ||
|
|
685e1b82c9 | ||
|
|
e7de4da64e | ||
|
|
2b3494e61c | ||
|
|
fc3aea7694 | ||
|
|
e8756a8cf7 | ||
|
|
3df49beb07 | ||
|
|
05b0f1649f | ||
|
|
7634b2c153 | ||
|
|
14ad67e86c | ||
|
|
2f9a49e1ca | ||
|
|
303b12c709 | ||
|
|
9e44f5cc20 | ||
|
|
f0236acf8f | ||
|
|
100fa04de7 | ||
|
|
f6e575885d | ||
|
|
9fe8d1fd53 | ||
|
|
19724367df | ||
|
|
ba896f2f3a | ||
|
|
64c95d2397 | ||
|
|
2486a94d21 | ||
|
|
19799957c0 | ||
|
|
4ff378cd06 | ||
|
|
4e8222afa1 | ||
|
|
bc0716808f | ||
|
|
37a1f01b3f | ||
|
|
99fbfea65f | ||
|
|
42142514a1 | ||
|
|
adf38b9262 | ||
|
|
44f0bfd8b5 | ||
|
|
d9b56a95aa | ||
|
|
a292114d68 | ||
|
|
99c43cc3ef | ||
|
|
fb0e2d2604 | ||
|
|
c177efe79c | ||
|
|
27b13765d7 | ||
|
|
f494832bd7 | ||
|
|
c564e50d59 | ||
|
|
53cf5ffc45 | ||
|
|
a341f81932 | ||
|
|
c43a0a7182 | ||
|
|
2f38f23b77 | ||
|
|
b9ec72bbb4 | ||
|
|
7ded8ad4fe | ||
|
|
30cf709eb2 | ||
|
|
d8347c3976 | ||
|
|
5ee878c30e | ||
|
|
f695c5e0d3 | ||
|
|
303b26b95f | ||
|
|
9ba9b8a984 | ||
|
|
d893efe5d1 | ||
|
|
a0599f7b33 | ||
|
|
9beedcf4e0 | ||
|
|
c7fd976ab7 | ||
|
|
47747f1d56 | ||
|
|
7de60f1ced | ||
|
|
4acc8395ba | ||
|
|
309c72d934 | ||
|
|
efe6a5a713 | ||
|
|
e098511960 | ||
|
|
6bbd296c25 | ||
|
|
3dfd993430 | ||
|
|
022c11d7fa | ||
|
|
9012a96a58 | ||
|
|
f85f220c1d | ||
|
|
ba6a8b6aa4 | ||
|
|
12b9ac4db6 | ||
|
|
e927064476 | ||
|
|
b78b2fe715 | ||
|
|
36982c5332 | ||
|
|
a5340764a8 | ||
|
|
bf3b257344 | ||
|
|
426e1c772f | ||
|
|
64814ef82f | ||
|
|
bb622bfcd0 | ||
|
|
07c41c4c92 | ||
|
|
e1b1d57714 | ||
|
|
2a84f7810d | ||
|
|
11ccb37382 | ||
|
|
92ae1be40a | ||
|
|
87d39ae2dd | ||
|
|
14c75333f3 | ||
|
|
7941d4bcb6 | ||
|
|
ed2e3bae0d | ||
|
|
1e8f772280 | ||
|
|
ebbdf8335b | ||
|
|
d488924f78 | ||
|
|
5841aefb75 | ||
|
|
1874053004 | ||
|
|
bd75eba941 | ||
|
|
a4985116b8 | ||
|
|
487be4a451 | ||
|
|
987250d4f3 | ||
|
|
0b6d44eb99 | ||
|
|
53e4b490cc | ||
|
|
2f86fbdd95 | ||
|
|
123fc8a883 | ||
|
|
cd9c6f713a | ||
|
|
12bdbcfbdc | ||
|
|
dc29ab496f | ||
|
|
dc2474801c | ||
|
|
477e3fee80 | ||
|
|
b79f8347b6 | ||
|
|
74a2cd7fe4 | ||
|
|
7aed641189 | ||
|
|
ddeafe0749 | ||
|
|
0472cde0cb | ||
|
|
7ddfeb93dd | ||
|
|
aaffc740bc | ||
|
|
3e38d56579 | ||
|
|
6e9893e5c5 | ||
|
|
678879e5fd | ||
|
|
22685ae911 | ||
|
|
966cc697e7 | ||
|
|
3d23a3b21a | ||
|
|
ffae4ae99b | ||
|
|
a010a90ed6 | ||
|
|
2905315e00 | ||
|
|
b9a7d09265 | ||
|
|
80498340b6 | ||
|
|
96072a607c | ||
|
|
9c04c8fbe6 | ||
|
|
bc47368a01 | ||
|
|
8bf17af6de | ||
|
|
6af62d8727 | ||
|
|
da3c6a78d4 | ||
|
|
324eeed1c5 | ||
|
|
3ecc699962 | ||
|
|
46e2cb6012 | ||
|
|
91c5e54cdc | ||
|
|
cc7dc0f142 | ||
|
|
56b750c1ac | ||
|
|
39e5415e0a | ||
|
|
5634b50528 | ||
|
|
318e8643fb | ||
|
|
f1e68e9eef | ||
|
|
6b70d8556b | ||
|
|
6f94cfb2ce | ||
|
|
0e4ca2ad21 | ||
|
|
d4cf8c5f9a | ||
|
|
48a8bab862 | ||
|
|
23aacd5810 | ||
|
|
c71d06d405 | ||
|
|
b1da0c1b70 | ||
|
|
cfe7b1baa0 | ||
|
|
abc93872e9 | ||
|
|
7b850cec43 | ||
|
|
3ffe43e9bf | ||
|
|
6287d40a18 | ||
|
|
03c875a8b8 | ||
|
|
c18115ef52 | ||
|
|
55e3a93cf6 | ||
|
|
306ee1ad1c | ||
|
|
60d216c6fa | ||
|
|
b2d64d9b72 | ||
|
|
bb0f584e91 | ||
|
|
26ad4a0a70 | ||
|
|
fd04b59985 | ||
|
|
419f562aed | ||
|
|
3cd7d68192 | ||
|
|
7fabb3a03c | ||
|
|
1abf7edb99 | ||
|
|
925133a297 | ||
|
|
3b10c7e93f | ||
|
|
f884aa6d71 | ||
|
|
0c670d785b | ||
|
|
0b96b1afe6 | ||
|
|
40cea19634 | ||
|
|
343eb34a7a | ||
|
|
ba5686a07a | ||
|
|
1cacefa1fd | ||
|
|
20a6b81b05 | ||
|
|
4731f2c43b | ||
|
|
190357fe64 | ||
|
|
d65daba103 | ||
|
|
38c36d203a | ||
|
|
a4a1ee93f8 | ||
|
|
f4acb98044 | ||
|
|
4ccab22c3f | ||
|
|
8d8591cf60 | ||
|
|
72719d2234 | ||
|
|
77276d8d6c | ||
|
|
c6c7b0ab32 | ||
|
|
df316463ef | ||
|
|
be9af355ce | ||
|
|
2faef37f4b | ||
|
|
83aa3db795 | ||
|
|
5db0da9aaf | ||
|
|
3abc7249bd | ||
|
|
6ef1da68e0 | ||
|
|
1c1c9bb0d7 | ||
|
|
b96e7341e3 | ||
|
|
163c5de1cc | ||
|
|
124ef86054 | ||
|
|
ccb6a4a283 | ||
|
|
a3c80556d2 | ||
|
|
851ce5de73 | ||
|
|
f8d956d47f | ||
|
|
7543dc6b57 | ||
|
|
d9b1081849 | ||
|
|
cfb7c61c63 | ||
|
|
609e4d1207 | ||
|
|
d4d2c68299 | ||
|
|
438387a4cb | ||
|
|
60a8ca3e2e | ||
|
|
d0a73d729d | ||
|
|
267bd56285 | ||
|
|
44568dc142 | ||
|
|
83a17e4d11 | ||
|
|
cbdb4907cf | ||
|
|
27a1d420db | ||
|
|
69048844d7 | ||
|
|
048162ba1f | ||
|
|
2928cae79a | ||
|
|
1d4f92c39b | ||
|
|
d1ce545e5f | ||
|
|
174274ee41 | ||
|
|
9dad24313c | ||
|
|
b007bec3c2 | ||
|
|
9057ee92c6 | ||
|
|
09098c6d07 | ||
|
|
d33bc6956c | ||
|
|
666649d218 | ||
|
|
868190928d | ||
|
|
06104f2d3b | ||
|
|
4e452d6b43 | ||
|
|
5fe5ac0935 | ||
|
|
47d7238f2b | ||
|
|
4b3ba41159 | ||
|
|
b86729050a |
8
.github/workflows/auth-crowdin.yml
vendored
8
.github/workflows/auth-crowdin.yml
vendored
@@ -2,15 +2,15 @@ name: "Sync Crowdin translations (auth)"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
# Run workflow when auth's intl_en.arb is changed
|
||||
- "mobile/lib/l10n/arb/app_en.arb"
|
||||
# Or the workflow itself is changed
|
||||
- ".github/workflows/auth-crowdin.yml"
|
||||
branches: [main]
|
||||
schedule:
|
||||
# See: [Note: Run every 24 hours]
|
||||
- cron: "50 1 * * *"
|
||||
# See: [Note: Run workflow on specific days of the week]
|
||||
- cron: "50 1 * * 2,5"
|
||||
# Also allow manually running the workflow
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
base_path: "auth/"
|
||||
config: "auth/crowdin.yml"
|
||||
upload_sources: true
|
||||
upload_translations: true
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
localization_branch_name: crowdin-translations-auth
|
||||
create_pull_request: true
|
||||
|
||||
4
.github/workflows/auth-lint.yml
vendored
4
.github/workflows/auth-lint.yml
vendored
@@ -3,13 +3,13 @@ name: "Lint (auth)"
|
||||
on:
|
||||
# Run on every push to a branch other than main that changes auth/
|
||||
push:
|
||||
branches-ignore: [main]
|
||||
branches-ignore: [main, "deploy/**"]
|
||||
paths:
|
||||
- "auth/**"
|
||||
- ".github/workflows/auth-lint.yml"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.16.9"
|
||||
FLUTTER_VERSION: "3.19.3"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
|
||||
22
.github/workflows/auth-release.yml
vendored
22
.github/workflows/auth-release.yml
vendored
@@ -29,7 +29,7 @@ on:
|
||||
- "auth-v*"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.13.4"
|
||||
FLUTTER_VERSION: "3.19.3"
|
||||
|
||||
jobs:
|
||||
build-ubuntu:
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
- name: Install dependencies for desktop build
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y libsecret-1-dev libsodium-dev libwebkit2gtk-4.0-dev libfuse2 ninja-build libgtk-3-dev dpkg-dev pkg-config rpm libsqlite3-dev locate
|
||||
sudo apt-get install -y libsecret-1-dev libsodium-dev libwebkit2gtk-4.0-dev libfuse2 ninja-build libgtk-3-dev dpkg-dev pkg-config rpm libsqlite3-dev locate appindicator3-0.1 libappindicator3-dev
|
||||
|
||||
- name: Install appimagetool
|
||||
run: |
|
||||
@@ -92,8 +92,6 @@ jobs:
|
||||
mv appimagetool /usr/local/bin/
|
||||
|
||||
- name: Build desktop app
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
run: |
|
||||
flutter config --enable-linux-desktop
|
||||
dart pub global activate flutter_distributor
|
||||
@@ -118,6 +116,8 @@ jobs:
|
||||
updateOnlyUnreleased: true
|
||||
|
||||
- name: Upload AAB to PlayStore
|
||||
# disable this step if release tag contains nightly or beta
|
||||
if: startsWith(github.ref, 'refs/tags/auth-v') && !contains(github.ref, 'nightly') && !contains(github.ref, 'beta')
|
||||
uses: r0adkll/upload-google-play@v1
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||||
@@ -149,8 +149,6 @@ jobs:
|
||||
run: mkdir artifacts
|
||||
|
||||
- name: Build Windows installer
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
run: |
|
||||
flutter config --enable-windows-desktop
|
||||
dart pub global activate flutter_distributor
|
||||
@@ -159,13 +157,9 @@ jobs:
|
||||
mv dist/**/ente_auth-*-windows-setup.exe artifacts/ente-${{ github.ref_name }}-installer.exe
|
||||
|
||||
- name: Retain Windows EXE and DLLs
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
run: cp -r build/windows/x64/runner/Release ente-${{ github.ref_name }}-windows
|
||||
|
||||
- name: Code sign Windows installer and EXE
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
uses: dlemstra/code-sign-action@v1
|
||||
with:
|
||||
certificate: "${{ secrets.WINDOWS_CERTIFICATE }}"
|
||||
@@ -175,8 +169,6 @@ jobs:
|
||||
auth/ente-${{ github.ref_name }}-windows/auth.exe
|
||||
|
||||
- name: Zip Windows EXE and DLLs
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
run: tar.exe -a -c -f auth/artifacts/ente-${{ github.ref_name }}-windows.zip auth/ente-${{ github.ref_name }}-windows
|
||||
|
||||
- name: Create a draft GitHub release
|
||||
@@ -248,8 +240,6 @@ jobs:
|
||||
run: mkdir artifacts
|
||||
|
||||
- name: Build macOS DMG
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
run: |
|
||||
flutter config --enable-macos-desktop
|
||||
dart pub global activate flutter_distributor
|
||||
@@ -257,16 +247,12 @@ jobs:
|
||||
mv dist/**/ente_auth-*-macos.dmg artifacts/ente-${{ github.ref_name }}.dmg
|
||||
|
||||
- name: Code sign DMG
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
run: |
|
||||
CERT_NAME=$(security find-identity -v -p codesigning | grep "Developer ID Application" | awk -F'"' '{print $2}' | grep -m1 "")
|
||||
codesign --force --timestamp --sign "$CERT_NAME" --options runtime artifacts/ente-${{ github.ref_name }}.dmg
|
||||
codesign --verify --verbose=4 artifacts/ente-${{ github.ref_name }}.dmg
|
||||
|
||||
- name: Notarize and staple DMG
|
||||
# Temporarily disable desktop builds
|
||||
if: false
|
||||
run: |
|
||||
xcrun notarytool submit artifacts/ente-${{ github.ref_name }}.dmg \
|
||||
--wait \
|
||||
|
||||
24
.github/workflows/copycat-db-release.yaml
vendored
Normal file
24
.github/workflows/copycat-db-release.yaml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: "Release (copycat-db)"
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Run manually
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
name: Check out code
|
||||
|
||||
- uses: mr-smithers-excellent/docker-build-push@v6
|
||||
name: Build & Push
|
||||
with:
|
||||
dockerfile: infra/copycat-db/Dockerfile
|
||||
directory: infra/copycat-db
|
||||
image: ente/copycat-db
|
||||
registry: rg.fr-par.scw.cloud
|
||||
enableBuildKit: true
|
||||
buildArgs: GIT_COMMIT=${GITHUB_SHA}
|
||||
tags: ${GITHUB_SHA}, latest
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
2
.github/workflows/docs-verify-build.yml
vendored
2
.github/workflows/docs-verify-build.yml
vendored
@@ -6,7 +6,7 @@ name: "Verify build (docs)"
|
||||
on:
|
||||
# Run on every push to a branch other than main that changes docs/
|
||||
push:
|
||||
branches-ignore: [main]
|
||||
branches-ignore: [main, "deploy/**"]
|
||||
paths:
|
||||
- "docs/**"
|
||||
- ".github/workflows/docs-verify-build.yml"
|
||||
|
||||
8
.github/workflows/mobile-crowdin.yml
vendored
8
.github/workflows/mobile-crowdin.yml
vendored
@@ -2,15 +2,15 @@ name: "Sync Crowdin translations (mobile)"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
# Run workflow when mobiles's intl_en.arb is changed
|
||||
- "mobile/lib/l10n/intl_en.arb"
|
||||
# Or the workflow itself is changed
|
||||
- ".github/workflows/mobile-crowdin.yml"
|
||||
branches: [main]
|
||||
schedule:
|
||||
# See: [Note: Run every 24 hours]
|
||||
- cron: "40 1 * * *"
|
||||
# See: [Note: Run workflow on specific days of the week]
|
||||
- cron: "40 1 * * 2,5"
|
||||
# Also allow manually running the workflow
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
base_path: "mobile/"
|
||||
config: "mobile/crowdin.yml"
|
||||
upload_sources: true
|
||||
upload_translations: true
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
localization_branch_name: crowdin-translations-mobile
|
||||
create_pull_request: true
|
||||
|
||||
2
.github/workflows/mobile-lint.yml
vendored
2
.github/workflows/mobile-lint.yml
vendored
@@ -3,7 +3,7 @@ name: "Lint (mobile)"
|
||||
on:
|
||||
# Run on every push to a branch other than main that changes mobile/
|
||||
push:
|
||||
branches-ignore: [main]
|
||||
branches-ignore: [main, f-droid, "deploy/**"]
|
||||
paths:
|
||||
- "mobile/**"
|
||||
- ".github/workflows/mobile-lint.yml"
|
||||
|
||||
2
.github/workflows/mobile-release.yml
vendored
2
.github/workflows/mobile-release.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD_PHOTOS }}
|
||||
|
||||
- name: Checksum
|
||||
run: sha256sum build/app/outputs/flutter-apk/ente.apk > build/app/outputs/flutter-apk/sha256sum
|
||||
run: sha256sum build/app/outputs/flutter-apk/ente-${{ github.ref_name }}.apk > build/app/outputs/flutter-apk/sha256sum
|
||||
|
||||
- name: Create a draft GitHub release
|
||||
uses: ncipollo/release-action@v1
|
||||
|
||||
2
.github/workflows/server-lint.yml
vendored
2
.github/workflows/server-lint.yml
vendored
@@ -3,7 +3,7 @@ name: "Lint (server)"
|
||||
on:
|
||||
# Run on every push to a branch other than main that changes server/
|
||||
push:
|
||||
branches-ignore: [main]
|
||||
branches-ignore: [main, "deploy/**"]
|
||||
paths:
|
||||
- "server/**"
|
||||
- ".github/workflows/server-lint.yml"
|
||||
|
||||
14
.github/workflows/web-crowdin.yml
vendored
14
.github/workflows/web-crowdin.yml
vendored
@@ -2,15 +2,21 @@ name: "Sync Crowdin translations (web)"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
# Run workflow when web's en-US/translation.json is changed
|
||||
- "web/apps/photos/public/locales/en-US/translation.json"
|
||||
# Or the workflow itself is changed
|
||||
- ".github/workflows/web-crowdin.yml"
|
||||
branches: [main]
|
||||
schedule:
|
||||
# See: [Note: Run every 24 hours]
|
||||
- cron: "20 1 * * *"
|
||||
# [Note: Run workflow on specific days of the week]
|
||||
#
|
||||
# The last (5th) component of the cron syntax denotes the day of the
|
||||
# week, with 0 == SUN and 6 == SAT. So, for example, to run on every TUE
|
||||
# and FRI, this can be set to `2,5`.
|
||||
#
|
||||
# See also: [Note: Run workflow every 24 hours]
|
||||
- cron: "20 1 * * 2,5"
|
||||
# Also allow manually running the workflow
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -28,7 +34,7 @@ jobs:
|
||||
base_path: "web/"
|
||||
config: "web/crowdin.yml"
|
||||
upload_sources: true
|
||||
upload_translations: true
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
localization_branch_name: crowdin-translations-web
|
||||
create_pull_request: true
|
||||
|
||||
2
.github/workflows/web-lint.yml
vendored
2
.github/workflows/web-lint.yml
vendored
@@ -3,7 +3,7 @@ name: "Lint (web)"
|
||||
on:
|
||||
# Run on every push to a branch other than main that changes web/
|
||||
push:
|
||||
branches-ignore: [main]
|
||||
branches-ignore: [main, "deploy/**"]
|
||||
paths:
|
||||
- "web/**"
|
||||
- ".github/workflows/web-lint.yml"
|
||||
|
||||
2
.github/workflows/web-nightly.yml
vendored
2
.github/workflows/web-nightly.yml
vendored
@@ -2,7 +2,7 @@ name: "Nightly (web)"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# [Note: Run every 24 hours]
|
||||
# [Note: Run workflow every 24 hours]
|
||||
#
|
||||
# Run every 24 hours - First field is minute, second is hour of the day
|
||||
# This runs 23:15 UTC everyday - 1 and 15 are just arbitrary offset to
|
||||
|
||||
7
.gitmodules
vendored
7
.gitmodules
vendored
@@ -9,16 +9,9 @@
|
||||
[submodule "auth/assets/simple-icons"]
|
||||
path = auth/assets/simple-icons
|
||||
url = https://github.com/simple-icons/simple-icons.git
|
||||
[submodule "mobile/thirdparty/flutter"]
|
||||
path = mobile/thirdparty/flutter
|
||||
url = https://github.com/flutter/flutter.git
|
||||
branch = stable
|
||||
[submodule "mobile/plugins/clip_ggml"]
|
||||
path = mobile/plugins/clip_ggml
|
||||
url = https://github.com/ente-io/clip-ggml.git
|
||||
[submodule "mobile/thirdparty/isar"]
|
||||
path = mobile/thirdparty/isar
|
||||
url = https://github.com/isar/isar
|
||||
[submodule "web/apps/photos/thirdparty/ffmpeg-wasm"]
|
||||
path = web/apps/photos/thirdparty/ffmpeg-wasm
|
||||
url = https://github.com/abhinavkgrd/ffmpeg.wasm.git
|
||||
|
||||
9
auth/.gitignore
vendored
9
auth/.gitignore
vendored
@@ -9,12 +9,20 @@
|
||||
.history
|
||||
.svn/
|
||||
|
||||
# Editors
|
||||
.vscode/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
@@ -32,3 +40,4 @@ lib/generated_plugin_registrant.dart
|
||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||
|
||||
android/key.properties
|
||||
dist/
|
||||
@@ -1,11 +1,11 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled.
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
||||
channel: unknown
|
||||
revision: "ba393198430278b6595976de84fe170f553cc728"
|
||||
channel: "[user-branch]"
|
||||
|
||||
project_type: app
|
||||
|
||||
@@ -13,17 +13,26 @@ project_type: app
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
||||
base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
||||
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
- platform: android
|
||||
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
- platform: ios
|
||||
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
- platform: linux
|
||||
create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
||||
base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
||||
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
- platform: macos
|
||||
create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
||||
base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
||||
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
- platform: web
|
||||
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
- platform: windows
|
||||
create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
||||
base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
||||
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||
|
||||
# User provided section
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
compileSdkVersion 34
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
@@ -46,7 +46,7 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "io.ente.auth"
|
||||
minSdkVersion 20
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
@@ -56,11 +56,11 @@ android {
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : System.getenv("SIGNING_KEY_PATH") ? file(System.getenv("SIGNING_KEY_PATH")) : null
|
||||
keyAlias keystoreProperties['keyAlias'] ? keystoreProperties['keyAlias'] : System.getenv("SIGNING_KEY_ALIAS")
|
||||
keyPassword keystoreProperties['keyPassword'] ? keystoreProperties['keyPassword'] : System.getenv("SIGNING_KEY_PASSWORD")
|
||||
storePassword keystoreProperties['storePassword'] ? keystoreProperties['storePassword'] : System.getenv("SIGNING_STORE_PASSWORD")
|
||||
}
|
||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : System.getenv("SIGNING_KEY_PATH") ? file(System.getenv("SIGNING_KEY_PATH")) : null
|
||||
keyAlias keystoreProperties['keyAlias'] ? keystoreProperties['keyAlias'] : System.getenv("SIGNING_KEY_ALIAS")
|
||||
keyPassword keystoreProperties['keyPassword'] ? keystoreProperties['keyPassword'] : System.getenv("SIGNING_KEY_PASSWORD")
|
||||
storePassword keystoreProperties['storePassword'] ? keystoreProperties['storePassword'] : System.getenv("SIGNING_STORE_PASSWORD")
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions "default"
|
||||
@@ -109,6 +109,7 @@ dependencies {
|
||||
implementation 'io.sentry:sentry-android:2.0.0'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'com.android.support:multidex:1.0.3'
|
||||
implementation 'com.google.guava:guava:28.2-android'
|
||||
implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
||||
|
||||
@@ -62,6 +62,11 @@
|
||||
{
|
||||
"title": "Crowdpear"
|
||||
},
|
||||
{
|
||||
"title": "DCS",
|
||||
"altNames": ["Digital Combat Simulator"],
|
||||
"slug": "dcs"
|
||||
},
|
||||
{
|
||||
"title": "DEGIRO"
|
||||
},
|
||||
@@ -267,6 +272,10 @@
|
||||
"title": "Revolt",
|
||||
"hex": "858585"
|
||||
},
|
||||
{
|
||||
"title": "Rockstar Games",
|
||||
"slug": "rockstar_games"
|
||||
},
|
||||
{
|
||||
"title": "Rust Language Forum",
|
||||
"slug": "rust_language_forum",
|
||||
@@ -356,6 +365,10 @@
|
||||
{
|
||||
"title": "Wise"
|
||||
},
|
||||
{
|
||||
"title": "WYZE",
|
||||
"slug": "wyze"
|
||||
},
|
||||
{
|
||||
"title": "X",
|
||||
"altNames": ["twitter"],
|
||||
|
||||
9
auth/assets/custom-icons/icons/dcs.svg
Normal file
9
auth/assets/custom-icons/icons/dcs.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;">
|
||||
<g transform="matrix(1.59257,0,0,1.59257,0,9.06171)">
|
||||
<path d="M0.87,0.08L3.17,0.08C4.2,0.08 5.13,0.51 5.13,1.67C5.13,2.92 3.91,3.6 2.78,3.6L0.06,3.6C0.06,3.6 0.87,0.09 0.87,0.08ZM2.85,2.82C3.44,2.82 4.14,2.39 4.14,1.74C4.14,1.19 3.7,0.85 3.17,0.85L1.71,0.85L1.26,2.81L2.85,2.81L2.85,2.82Z" style="fill:rgb(245,158,15);fill-rule:nonzero;stroke:rgb(245,158,15);stroke-width:0.09px;"/>
|
||||
<path d="M11.95,1.33C11.87,1.31 11.65,1.26 11.65,1.14C11.65,0.81 12.52,0.81 12.74,0.81C13.25,0.81 13.96,0.91 14.4,1.17L15,0.59C14.39,0.18 13.59,0.04 12.86,0.04C12.07,0.04 10.65,0.18 10.65,1.24C10.65,2.45 13.65,1.96 13.65,2.52C13.65,2.86 12.89,2.88 12.66,2.88C11.9,2.88 11.39,2.74 10.77,2.29C10.57,2.48 10.36,2.67 10.16,2.86C10.95,3.44 11.7,3.64 12.67,3.64C13.4,3.64 14.68,3.36 14.68,2.42C14.68,1.34 12.67,1.5 11.97,1.32L11.95,1.32L11.95,1.33Z" style="fill:rgb(245,158,15);fill-rule:nonzero;stroke:rgb(245,158,15);stroke-width:0.09px;"/>
|
||||
<path d="M9.18,2.35L9.16,2.35C9.07,2.43 8.41,2.95 7.67,2.87C7.14,2.81 6.41,2.44 6.41,1.95C6.41,1.15 7.26,0.83 7.94,0.83C8.39,0.83 8.82,0.93 9.17,1.18C9.48,1.07 9.8,0.97 10.11,0.86C9.59,0.3 8.82,0.07 8.06,0.07C6.92,0.07 5.43,0.73 5.43,2.06C5.43,3.22 6.65,3.63 7.61,3.63C8.41,3.63 9.18,3.4 9.78,2.88C9.78,2.88 9.18,2.38 9.17,2.37L9.16,2.37L9.18,2.35Z" style="fill:rgb(245,158,15);fill-rule:nonzero;stroke:rgb(245,158,15);stroke-width:0.09px;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
1
auth/assets/custom-icons/icons/rockstar_games.svg
Normal file
1
auth/assets/custom-icons/icons/rockstar_games.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="6.525 7.459 339.266 319.582"><path fill="orange" d="M71.598 11.25H280.72c33.844 0 61.282 25.782 61.282 57.586v196.512c0 31.804-27.437 57.586-61.282 57.586H71.598c-33.845 0-61.28-25.782-61.28-57.586V68.836c0-31.804 27.435-57.586 61.28-57.586z"/><path d="M280.719 326.725H71.598c-35.881 0-65.072-27.533-65.072-61.377V68.836c0-33.844 29.19-61.377 65.072-61.377H280.72c35.88 0 65.072 27.533 65.072 61.377v196.512c0 33.844-29.192 61.377-65.073 61.377zM71.598 15.042c-31.7 0-57.49 24.131-57.49 53.794v196.512c0 29.662 25.79 53.794 57.49 53.794H280.72c31.7 0 57.49-24.132 57.49-53.794V68.836c0-29.662-25.79-53.794-57.49-53.794H71.598z"/><path d="M127.423 64.013l62.975.149c13.161-.099 22.989 2.002 29.48 6.303 7.928 5.272 11.89 14.343 11.89 27.213 0 21.19-9.828 33.195-29.482 36.012v.297c8.667 2.159 13.048 9.335 13.146 21.528 0 6.245-.233 14.245-.7 24 0 6.542 1.384 12.202 4.156 16.98H184.38c-1.611-1.747-2.416-5.305-2.416-10.677.66-8.98.99-16.347.99-22.098 0-11.617-5.582-17.425-16.746-17.425h-22.329l-9.738 46.987h-33.613l26.895-129.269zm53.236 26.941h-24.744l-6.453 31.192h26.775c14.967 0 22.497-5.906 22.595-17.721.001-8.98-6.058-13.47-18.173-13.47z"/><path d="M223.456 196.346l24.915-43.18 6.717 43.478h42.506l-38.349 27.534 6.14 43.18-33.204-26.05-44.633 27.089 20.878-45.973-24.333-26.2 39.363.122zm113.568 113.887c1.38 0 2.726.362 4.04 1.086 1.315.723 2.339 1.76 3.07 3.108.735 1.348 1.101 2.753 1.101 4.216 0 1.449-.36 2.841-1.084 4.177a7.735 7.735 0 01-3.039 3.114c-1.302.74-2.665 1.108-4.09 1.108-1.421 0-2.784-.37-4.089-1.108a7.762 7.762 0 01-3.044-3.114c-.725-1.336-1.089-2.73-1.089-4.177 0-1.463.369-2.868 1.106-4.216.738-1.348 1.763-2.384 3.077-3.108 1.316-.725 2.662-1.086 4.04-1.086zm0 1.391c-1.154 0-2.278.303-3.37.909a6.451 6.451 0 00-2.566 2.594c-.617 1.126-.926 2.297-.926 3.514 0 1.211.304 2.372.91 3.482a6.542 6.542 0 002.542 2.596c1.089.619 2.224.93 3.409.93 1.183 0 2.32-.311 3.41-.93a6.498 6.498 0 002.536-2.596c.603-1.11.904-2.27.904-3.482 0-1.218-.308-2.388-.92-3.514a6.39 6.39 0 00-2.565-2.594c-1.094-.606-2.216-.909-3.364-.909zm-3.604 11.664v-9.045h3.038c1.039 0 1.79.083 2.254.25.466.167.835.459 1.11.875.277.416.415.857.415 1.325 0 .661-.23 1.237-.692 1.726-.46.49-1.072.764-1.835.824.312.135.563.294.75.48.358.356.792.954 1.308 1.792l1.078 1.772h-1.742l-.783-1.426c-.617-1.12-1.115-1.823-1.492-2.105-.262-.209-.643-.313-1.145-.313h-.838v3.844h-1.426zm1.426-5.092h1.732c.829 0 1.393-.126 1.694-.38.3-.25.451-.586.451-1.002a1.24 1.24 0 00-.218-.718 1.302 1.302 0 00-.604-.473c-.258-.103-.734-.157-1.431-.157h-1.624v2.73z"/><path fill="#FFF" d="M252.503 221.088l25.47-18.142h-28.177l-4.881-31.464-17.882 31.168h-28.467l17.302 18.587-14.305 31.193 31.052-18.735 24.48 19.18-4.592-31.787z"/></svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
1
auth/assets/custom-icons/icons/wyze.svg
Normal file
1
auth/assets/custom-icons/icons/wyze.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg fill="#1DF0BB" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Wyze</title><path d="M5.475 13.171 7.3 9.469h.974L5.779 14.53h-.608l-1.034-2.082-1.034 2.082h-.609L0 9.469h.973l1.826 3.673.851-1.706-.973-1.967h.973l1.825 3.702Zm8.457-3.702-2.251 3.442v1.591h-.882v-1.591L8.517 9.469h1.034l1.673 2.545 1.673-2.545h1.035Zm5.444 4.194H24v.867h-4.624v-.867Zm0-4.194H24v.868h-4.624v-.868Zm0 2.083H24v.867h-4.624v-.867Zm-.273-2.083-3.438 4.223h3.133v.838H13.84l3.407-4.222h-3.042v-.839h4.898Z"/></svg>
|
||||
|
After Width: | Height: | Size: 523 B |
BIN
auth/assets/icon-light.ico
Normal file
BIN
auth/assets/icon-light.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
25
auth/distribute_options.yaml
Normal file
25
auth/distribute_options.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
output: dist/
|
||||
|
||||
releases:
|
||||
- name: dev
|
||||
jobs:
|
||||
- name: release-dev-linux-zip
|
||||
package:
|
||||
platform: linux
|
||||
target: zip
|
||||
- name: release-dev-linux-deb
|
||||
package:
|
||||
platform: linux
|
||||
target: deb
|
||||
- name: release-dev-linux-appimage
|
||||
package:
|
||||
platform: linux
|
||||
target: appimage
|
||||
- name: release-dev-windows-exe
|
||||
package:
|
||||
platform: windows
|
||||
target: exe
|
||||
- name: release-dev-macos-dmg
|
||||
package:
|
||||
platform: macos
|
||||
target: dmg
|
||||
@@ -1,7 +1,14 @@
|
||||
# Releases
|
||||
|
||||
Create a PR to bump up the version in `pubspec.yaml`. Once that is merged, tag
|
||||
main, and push the tag.
|
||||
Create a PR to bump up the version in `pubspec.yaml`.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Use [semver](https://semver.org/) for the tags, with `auth-` as a prefix.
|
||||
> Multiple beta releases for the same upcoming version can be done by adding
|
||||
> build metadata at the end, e.g. `auth-v1.2.3-beta+3`.
|
||||
|
||||
Once that is merged, tag main, and push the tag.
|
||||
|
||||
```sh
|
||||
git tag auth-v1.2.3
|
||||
@@ -16,6 +23,11 @@ This'll trigger a GitHub workflow that:
|
||||
* Creates a new release in the internal track on Play Store.
|
||||
|
||||
Once the workflow completes, go to the draft GitHub release that was created.
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Keep the title of the release same as the tag.
|
||||
|
||||
Set "Previous tag" to the last release of auth and press "Generate release
|
||||
notes". The generated release note will contain all PRs and new contributors
|
||||
from all the releases in the monorepo, so you'll need to filter them to keep
|
||||
|
||||
@@ -37,4 +37,4 @@ file, that adheres to the above format.
|
||||
SUPPORT
|
||||
|
||||
If you need help, please reach out to support@ente.io, and a human will get in touch with you.
|
||||
If you have feature requests, please create an issue @ https://github.com/ente-io/auth
|
||||
If you have feature requests, please create an issue @ https://github.com/ente-io/ente
|
||||
|
||||
Submodule auth/flutter updated: 41456452f2...ba39319843
@@ -1,7 +1,9 @@
|
||||
PODS:
|
||||
- connectivity (0.0.1):
|
||||
- app_links (0.0.1):
|
||||
- Flutter
|
||||
- Reachability
|
||||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
- ReachabilitySwift
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- DKImagePickerController/Core (4.3.4):
|
||||
@@ -45,27 +47,26 @@ PODS:
|
||||
- Flutter (1.0.0)
|
||||
- flutter_email_sender (0.0.1):
|
||||
- Flutter
|
||||
- flutter_inappwebview (0.0.1):
|
||||
- flutter_inappwebview_ios (0.0.1):
|
||||
- Flutter
|
||||
- flutter_inappwebview/Core (= 0.0.1)
|
||||
- flutter_inappwebview_ios/Core (= 0.0.1)
|
||||
- OrderedSet (~> 5.0)
|
||||
- flutter_inappwebview/Core (0.0.1):
|
||||
- flutter_inappwebview_ios/Core (0.0.1):
|
||||
- Flutter
|
||||
- OrderedSet (~> 5.0)
|
||||
- flutter_local_authentication (1.2.0):
|
||||
- Flutter
|
||||
- flutter_local_notifications (0.0.1):
|
||||
- Flutter
|
||||
- flutter_native_splash (0.0.1):
|
||||
- Flutter
|
||||
- flutter_secure_storage (6.0.0):
|
||||
- Flutter
|
||||
- flutter_sodium (0.0.1):
|
||||
- Flutter
|
||||
- fluttertoast (0.0.2):
|
||||
- Flutter
|
||||
- Toast
|
||||
- FMDB (2.7.5):
|
||||
- FMDB/standard (= 2.7.5)
|
||||
- FMDB/standard (2.7.5)
|
||||
- local_auth_darwin (0.0.1):
|
||||
- Flutter
|
||||
- local_auth_ios (0.0.1):
|
||||
- Flutter
|
||||
- move_to_background (0.0.1):
|
||||
@@ -82,46 +83,65 @@ PODS:
|
||||
- qr_code_scanner (0.2.0):
|
||||
- Flutter
|
||||
- MTBBarcodeScanner
|
||||
- Reachability (3.2)
|
||||
- SDWebImage (5.17.0):
|
||||
- SDWebImage/Core (= 5.17.0)
|
||||
- SDWebImage/Core (5.17.0)
|
||||
- Sentry/HybridSDK (8.9.1):
|
||||
- SentryPrivate (= 8.9.1)
|
||||
- ReachabilitySwift (5.2.1)
|
||||
- SDWebImage (5.19.0):
|
||||
- SDWebImage/Core (= 5.19.0)
|
||||
- SDWebImage/Core (5.19.0)
|
||||
- Sentry/HybridSDK (8.21.0):
|
||||
- SentryPrivate (= 8.21.0)
|
||||
- sentry_flutter (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- Sentry/HybridSDK (= 8.9.1)
|
||||
- SentryPrivate (8.9.1)
|
||||
- Sentry/HybridSDK (= 8.21.0)
|
||||
- SentryPrivate (8.21.0)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- smart_auth (0.0.1):
|
||||
- Flutter
|
||||
- sodium_libs (2.2.1):
|
||||
- Flutter
|
||||
- sqflite (0.0.3):
|
||||
- Flutter
|
||||
- FMDB (>= 2.7.5)
|
||||
- SwiftyGif (5.4.4)
|
||||
- Toast (4.0.0)
|
||||
- uni_links (0.0.1):
|
||||
- FlutterMacOS
|
||||
- sqlite3 (3.45.1):
|
||||
- sqlite3/common (= 3.45.1)
|
||||
- sqlite3/common (3.45.1)
|
||||
- sqlite3/fts5 (3.45.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/perf-threadsafe (3.45.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/rtree (3.45.1):
|
||||
- sqlite3/common
|
||||
- sqlite3_flutter_libs (0.0.1):
|
||||
- Flutter
|
||||
- sqlite3 (~> 3.45.1)
|
||||
- sqlite3/fts5
|
||||
- sqlite3/perf-threadsafe
|
||||
- sqlite3/rtree
|
||||
- SwiftyGif (5.4.4)
|
||||
- Toast (4.1.0)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- connectivity (from `.symlinks/plugins/connectivity/ios`)
|
||||
- app_links (from `.symlinks/plugins/app_links/ios`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/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`)
|
||||
- flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`)
|
||||
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
|
||||
- flutter_local_authentication (from `.symlinks/plugins/flutter_local_authentication/ios`)
|
||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||
- flutter_sodium (from `.symlinks/plugins/flutter_sodium/ios`)
|
||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
||||
- local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`)
|
||||
- move_to_background (from `.symlinks/plugins/move_to_background/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
@@ -131,27 +151,31 @@ DEPENDENCIES:
|
||||
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||
- uni_links (from `.symlinks/plugins/uni_links/ios`)
|
||||
- smart_auth (from `.symlinks/plugins/smart_auth/ios`)
|
||||
- sodium_libs (from `.symlinks/plugins/sodium_libs/ios`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
|
||||
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- DKImagePickerController
|
||||
- DKPhotoGallery
|
||||
- FMDB
|
||||
- MTBBarcodeScanner
|
||||
- OrderedSet
|
||||
- Reachability
|
||||
- ReachabilitySwift
|
||||
- SDWebImage
|
||||
- Sentry
|
||||
- SentryPrivate
|
||||
- sqlite3
|
||||
- SwiftyGif
|
||||
- Toast
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
connectivity:
|
||||
:path: ".symlinks/plugins/connectivity/ios"
|
||||
app_links:
|
||||
:path: ".symlinks/plugins/app_links/ios"
|
||||
connectivity_plus:
|
||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
file_picker:
|
||||
@@ -164,18 +188,20 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter
|
||||
flutter_email_sender:
|
||||
:path: ".symlinks/plugins/flutter_email_sender/ios"
|
||||
flutter_inappwebview:
|
||||
:path: ".symlinks/plugins/flutter_inappwebview/ios"
|
||||
flutter_inappwebview_ios:
|
||||
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
|
||||
flutter_local_authentication:
|
||||
:path: ".symlinks/plugins/flutter_local_authentication/ios"
|
||||
flutter_local_notifications:
|
||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||
flutter_native_splash:
|
||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||
flutter_secure_storage:
|
||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||
flutter_sodium:
|
||||
:path: ".symlinks/plugins/flutter_sodium/ios"
|
||||
fluttertoast:
|
||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||
local_auth_darwin:
|
||||
:path: ".symlinks/plugins/local_auth_darwin/darwin"
|
||||
local_auth_ios:
|
||||
:path: ".symlinks/plugins/local_auth_ios/ios"
|
||||
move_to_background:
|
||||
@@ -194,50 +220,58 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
shared_preferences_foundation:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
smart_auth:
|
||||
:path: ".symlinks/plugins/smart_auth/ios"
|
||||
sodium_libs:
|
||||
:path: ".symlinks/plugins/sodium_libs/ios"
|
||||
sqflite:
|
||||
:path: ".symlinks/plugins/sqflite/ios"
|
||||
uni_links:
|
||||
:path: ".symlinks/plugins/uni_links/ios"
|
||||
:path: ".symlinks/plugins/sqflite/darwin"
|
||||
sqlite3_flutter_libs:
|
||||
:path: ".symlinks/plugins/sqlite3_flutter_libs/ios"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467
|
||||
device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
|
||||
app_links: e70ca16b4b0f88253b3b3660200d4a10b4ea9795
|
||||
connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
|
||||
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
||||
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
|
||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
||||
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
|
||||
file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de
|
||||
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
|
||||
fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
|
||||
flutter_inappwebview: acd4fc0f012cefd09015000c241137d82f01ba62
|
||||
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
|
||||
flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0
|
||||
flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb
|
||||
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
|
||||
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
||||
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
|
||||
flutter_sodium: c84426b4de738514b5b66cfdeb8a06634e72fe0b
|
||||
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605
|
||||
local_auth_darwin: c7e464000a6a89e952235699e32b329457608d98
|
||||
local_auth_ios: 5046a18c018dd973247a0564496c8898dbb5adf9
|
||||
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
|
||||
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
||||
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
||||
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
|
||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
||||
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||
privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e
|
||||
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
|
||||
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
|
||||
SDWebImage: 750adf017a315a280c60fde706ab1e552a3ae4e9
|
||||
Sentry: e3203780941722a1fcfee99e351de14244c7f806
|
||||
sentry_flutter: 8f0ffd53088e6a4d50c095852c5cad9e4405025c
|
||||
SentryPrivate: 5e3683390f66611fc7c6215e27645873adb55d13
|
||||
ReachabilitySwift: 5ae15e16814b5f9ef568963fb2c87aeb49158c66
|
||||
SDWebImage: 981fd7e860af070920f249fd092420006014c3eb
|
||||
Sentry: ebc12276bd17613a114ab359074096b6b3725203
|
||||
sentry_flutter: dff1df05dc39c83d04f9330b36360fc374574c5e
|
||||
SentryPrivate: d651efb234cf385ec9a1cdd3eff94b5e78a0e0fe
|
||||
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
||||
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
||||
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
||||
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
||||
smart_auth: 4bedbc118723912d0e45a07e8ab34039c19e04f2
|
||||
sodium_libs: 1faae17af662384acbd13e41867a0008cd2e2318
|
||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||
sqlite3: 73b7fc691fdc43277614250e04d183740cb15078
|
||||
sqlite3_flutter_libs: af0e8fe9bce48abddd1ffdbbf839db0302d72d80
|
||||
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
|
||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
|
||||
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||
Toast: ec33c32b8688982cecc6348adeae667c1b9938da
|
||||
url_launcher_ios: 6116280ddcfe98ab8820085d8d76ae7449447586
|
||||
|
||||
PODFILE CHECKSUM: b4e3a7eabb03395b66e81fc061789f61526ee6bb
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1430;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
97C146ED1CF9000F007C117D = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1430"
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import UIKit
|
||||
import Flutter
|
||||
import UIKit
|
||||
import app_links
|
||||
|
||||
@UIApplicationMain
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
@@ -8,6 +9,15 @@ import Flutter
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
|
||||
super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
|
||||
if let url = AppLinks.shared.getLink(launchOptions: launchOptions) {
|
||||
AppLinks.shared.handleLink(url: url)
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
// return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,5 +78,9 @@
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -12,15 +12,18 @@ import 'package:ente_auth/locale.dart';
|
||||
import "package:ente_auth/onboarding/view/onboarding_page.dart";
|
||||
import 'package:ente_auth/services/update_service.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/services/window_listener_service.dart';
|
||||
import 'package:ente_auth/ui/home_page.dart';
|
||||
import 'package:ente_auth/ui/settings/app_update_dialog.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:system_tray/system_tray.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
class App extends StatefulWidget {
|
||||
final Locale locale;
|
||||
const App({Key? key, this.locale = const Locale("en")}) : super(key: key);
|
||||
const App({super.key, this.locale = const Locale("en")});
|
||||
|
||||
static void setLocale(BuildContext context, Locale newLocale) {
|
||||
_AppState state = context.findAncestorStateOfType<_AppState>()!;
|
||||
@@ -31,7 +34,7 @@ class App extends StatefulWidget {
|
||||
State<App> createState() => _AppState();
|
||||
}
|
||||
|
||||
class _AppState extends State<App> {
|
||||
class _AppState extends State<App> with WindowListener {
|
||||
late StreamSubscription<SignedOutEvent> _signedOutEvent;
|
||||
late StreamSubscription<SignedInEvent> _signedInEvent;
|
||||
Locale? locale;
|
||||
@@ -41,8 +44,14 @@ class _AppState extends State<App> {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> initWindowManager() async {
|
||||
windowManager.addListener(this);
|
||||
await windowManager.setPreventClose(true);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
initWindowManager();
|
||||
_signedOutEvent = Bus.instance.on<SignedOutEvent>().listen((event) {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
@@ -76,6 +85,7 @@ class _AppState extends State<App> {
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
windowManager.removeListener(this);
|
||||
_signedOutEvent.cancel();
|
||||
_signedInEvent.cancel();
|
||||
}
|
||||
@@ -134,4 +144,15 @@ class _AppState extends State<App> {
|
||||
: const OnboardingPage(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowClose() async {
|
||||
final AppWindow appWindow = AppWindow();
|
||||
await appWindow.hide();
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowResize() {
|
||||
WindowListenerService.instance.onWindowResize().ignore();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
class AppRoute {
|
||||
static const String enterSecretKeyPage = "enterSecretKeyPage";
|
||||
static const String settings = "settings";
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
import "package:flutter/material.dart";
|
||||
|
||||
final lightTheme = ThemeData(
|
||||
fontFamily: "Inter",
|
||||
brightness: Brightness.light,
|
||||
scaffoldBackgroundColor: Colors.white,
|
||||
appBarTheme: const AppBarTheme().copyWith(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black,
|
||||
iconTheme: const IconThemeData(color: Colors.black),
|
||||
elevation: 0,
|
||||
),
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: Colors.black,
|
||||
),
|
||||
textTheme: _buildTextTheme(Colors.black),
|
||||
outlinedButtonTheme: buildOutlinedButtonThemeData(
|
||||
bgDisabled: Colors.grey.shade500,
|
||||
bgEnabled: Colors.black,
|
||||
fgDisabled: Colors.white,
|
||||
fgEnabled: Colors.white,
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
fillColor: null,
|
||||
filled: true,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 14,
|
||||
),
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final darkTheme = ThemeData(
|
||||
fontFamily: "Inter",
|
||||
brightness: Brightness.dark,
|
||||
scaffoldBackgroundColor: Colors.black,
|
||||
appBarTheme: const AppBarTheme(color: Colors.orange),
|
||||
textTheme: _buildTextTheme(Colors.white),
|
||||
outlinedButtonTheme: buildOutlinedButtonThemeData(
|
||||
bgDisabled: Colors.grey.shade500,
|
||||
bgEnabled: Colors.white,
|
||||
fgDisabled: Colors.white,
|
||||
fgEnabled: Colors.black,
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
fillColor: null,
|
||||
filled: true,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 14,
|
||||
),
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
), colorScheme: const ColorScheme.dark(primary: Colors.white).copyWith(background: Colors.black),
|
||||
);
|
||||
|
||||
TextTheme _buildTextTheme(Color textColor) {
|
||||
return const TextTheme().copyWith(
|
||||
headlineMedium: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontFamily: "Inter",
|
||||
),
|
||||
headlineSmall: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFamily: "Inter",
|
||||
),
|
||||
// AG: Body
|
||||
titleLarge: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: 18,
|
||||
fontFamily: "Inter",
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
// Use labels for buttons or notifications
|
||||
labelMedium: TextStyle(
|
||||
color: textColor,
|
||||
fontFamily: "Inter",
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
height: 28,
|
||||
),
|
||||
|
||||
titleMedium: TextStyle(
|
||||
color: textColor,
|
||||
fontFamily: "Inter",
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
titleSmall: TextStyle(
|
||||
color: textColor,
|
||||
fontFamily: "Inter",
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
bodyLarge: TextStyle(
|
||||
fontFamily: "Inter",
|
||||
color: textColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
bodyMedium: TextStyle(
|
||||
fontFamily: "Inter",
|
||||
color: textColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
bodySmall: TextStyle(
|
||||
color: textColor.withOpacity(0.6),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
OutlinedButtonThemeData buildOutlinedButtonThemeData({
|
||||
required Color bgDisabled,
|
||||
required Color bgEnabled,
|
||||
required Color fgDisabled,
|
||||
required Color fgEnabled,
|
||||
}) {
|
||||
return OutlinedButtonThemeData(
|
||||
style: OutlinedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 50),
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
fontFamily: "Inter",
|
||||
fontSize: 18,
|
||||
),
|
||||
).copyWith(
|
||||
backgroundColor: MaterialStateProperty.resolveWith<Color>(
|
||||
(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return bgDisabled;
|
||||
}
|
||||
return bgEnabled;
|
||||
},
|
||||
),
|
||||
foregroundColor: MaterialStateProperty.resolveWith<Color>(
|
||||
(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return fgDisabled;
|
||||
}
|
||||
return fgEnabled;
|
||||
},
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
import "dart:async";
|
||||
import "dart:developer";
|
||||
|
||||
|
||||
@@ -13,12 +13,12 @@ import 'package:ente_auth/models/key_attributes.dart';
|
||||
import 'package:ente_auth/models/key_gen_result.dart';
|
||||
import 'package:ente_auth/models/private_key_attributes.dart';
|
||||
import 'package:ente_auth/store/authenticator_db.dart';
|
||||
import 'package:ente_auth/utils/crypto_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class Configuration {
|
||||
@@ -72,9 +72,10 @@ class Configuration {
|
||||
|
||||
Future<void> init() async {
|
||||
_preferences = await SharedPreferences.getInstance();
|
||||
sqfliteFfiInit();
|
||||
_secureStorage = const FlutterSecureStorage();
|
||||
_documentsDirectory = (await getApplicationDocumentsDirectory()).path;
|
||||
_tempDirectory = _documentsDirectory + "/temp/";
|
||||
_tempDirectory = "$_documentsDirectory/temp/";
|
||||
final tempDirectory = io.Directory(_tempDirectory);
|
||||
try {
|
||||
final currentTime = DateTime.now().microsecondsSinceEpoch;
|
||||
@@ -162,7 +163,7 @@ class Configuration {
|
||||
// decrypt the master key
|
||||
final kekSalt = CryptoUtil.getSaltToDeriveKey();
|
||||
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
|
||||
utf8.encode(password) as Uint8List,
|
||||
utf8.encode(password),
|
||||
kekSalt,
|
||||
);
|
||||
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
|
||||
@@ -172,28 +173,28 @@ class Configuration {
|
||||
CryptoUtil.encryptSync(masterKey, derivedKeyResult.key);
|
||||
|
||||
// Generate a public-private keypair and encrypt the latter
|
||||
final keyPair = await CryptoUtil.generateKeyPair();
|
||||
final keyPair = CryptoUtil.generateKeyPair();
|
||||
final encryptedSecretKeyData =
|
||||
CryptoUtil.encryptSync(keyPair.sk, masterKey);
|
||||
CryptoUtil.encryptSync(keyPair.secretKey.extractBytes(), masterKey);
|
||||
|
||||
final attributes = KeyAttributes(
|
||||
Sodium.bin2base64(kekSalt),
|
||||
Sodium.bin2base64(encryptedKeyData.encryptedData!),
|
||||
Sodium.bin2base64(encryptedKeyData.nonce!),
|
||||
Sodium.bin2base64(keyPair.pk),
|
||||
Sodium.bin2base64(encryptedSecretKeyData.encryptedData!),
|
||||
Sodium.bin2base64(encryptedSecretKeyData.nonce!),
|
||||
CryptoUtil.bin2base64(kekSalt),
|
||||
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedKeyData.nonce!),
|
||||
CryptoUtil.bin2base64(keyPair.publicKey),
|
||||
CryptoUtil.bin2base64(encryptedSecretKeyData.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedSecretKeyData.nonce!),
|
||||
derivedKeyResult.memLimit,
|
||||
derivedKeyResult.opsLimit,
|
||||
Sodium.bin2base64(encryptedMasterKey.encryptedData!),
|
||||
Sodium.bin2base64(encryptedMasterKey.nonce!),
|
||||
Sodium.bin2base64(encryptedRecoveryKey.encryptedData!),
|
||||
Sodium.bin2base64(encryptedRecoveryKey.nonce!),
|
||||
CryptoUtil.bin2base64(encryptedMasterKey.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedMasterKey.nonce!),
|
||||
CryptoUtil.bin2base64(encryptedRecoveryKey.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedRecoveryKey.nonce!),
|
||||
);
|
||||
final privateAttributes = PrivateKeyAttributes(
|
||||
Sodium.bin2base64(masterKey),
|
||||
Sodium.bin2hex(recoveryKey),
|
||||
Sodium.bin2base64(keyPair.sk),
|
||||
CryptoUtil.bin2base64(masterKey),
|
||||
CryptoUtil.bin2hex(recoveryKey),
|
||||
CryptoUtil.bin2base64(keyPair.secretKey.extractBytes()),
|
||||
);
|
||||
return KeyGenResult(attributes, privateAttributes, loginKey);
|
||||
}
|
||||
@@ -208,7 +209,7 @@ class Configuration {
|
||||
// decrypt the master key
|
||||
final kekSalt = CryptoUtil.getSaltToDeriveKey();
|
||||
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
|
||||
utf8.encode(password) as Uint8List,
|
||||
utf8.encode(password),
|
||||
kekSalt,
|
||||
);
|
||||
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
|
||||
@@ -220,9 +221,9 @@ class Configuration {
|
||||
final existingAttributes = getKeyAttributes();
|
||||
|
||||
final updatedAttributes = existingAttributes!.copyWith(
|
||||
kekSalt: Sodium.bin2base64(kekSalt),
|
||||
encryptedKey: Sodium.bin2base64(encryptedKeyData.encryptedData!),
|
||||
keyDecryptionNonce: Sodium.bin2base64(encryptedKeyData.nonce!),
|
||||
kekSalt: CryptoUtil.bin2base64(kekSalt),
|
||||
encryptedKey: CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
|
||||
keyDecryptionNonce: CryptoUtil.bin2base64(encryptedKeyData.nonce!),
|
||||
memLimit: derivedKeyResult.memLimit,
|
||||
opsLimit: derivedKeyResult.opsLimit,
|
||||
);
|
||||
@@ -240,8 +241,8 @@ class Configuration {
|
||||
}) async {
|
||||
_logger.info('Start decryptAndSaveSecrets');
|
||||
keyEncryptionKey ??= await CryptoUtil.deriveKey(
|
||||
utf8.encode(password) as Uint8List,
|
||||
Sodium.base642bin(attributes.kekSalt),
|
||||
utf8.encode(password),
|
||||
CryptoUtil.base642bin(attributes.kekSalt),
|
||||
attributes.memLimit,
|
||||
attributes.opsLimit,
|
||||
);
|
||||
@@ -250,31 +251,31 @@ class Configuration {
|
||||
Uint8List key;
|
||||
try {
|
||||
key = CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(attributes.encryptedKey),
|
||||
CryptoUtil.base642bin(attributes.encryptedKey),
|
||||
keyEncryptionKey,
|
||||
Sodium.base642bin(attributes.keyDecryptionNonce),
|
||||
CryptoUtil.base642bin(attributes.keyDecryptionNonce),
|
||||
);
|
||||
} catch (e) {
|
||||
_logger.severe('master-key failed, incorrect password?', e);
|
||||
throw Exception("Incorrect password");
|
||||
}
|
||||
_logger.info("master-key done");
|
||||
await setKey(Sodium.bin2base64(key));
|
||||
await setKey(CryptoUtil.bin2base64(key));
|
||||
final secretKey = CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(attributes.encryptedSecretKey),
|
||||
CryptoUtil.base642bin(attributes.encryptedSecretKey),
|
||||
key,
|
||||
Sodium.base642bin(attributes.secretKeyDecryptionNonce),
|
||||
CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce),
|
||||
);
|
||||
_logger.info("secret-key done");
|
||||
await setSecretKey(Sodium.bin2base64(secretKey));
|
||||
await setSecretKey(CryptoUtil.bin2base64(secretKey));
|
||||
final token = CryptoUtil.openSealSync(
|
||||
Sodium.base642bin(getEncryptedToken()!),
|
||||
Sodium.base642bin(attributes.publicKey),
|
||||
CryptoUtil.base642bin(getEncryptedToken()!),
|
||||
CryptoUtil.base642bin(attributes.publicKey),
|
||||
secretKey,
|
||||
);
|
||||
_logger.info('appToken done');
|
||||
await setToken(
|
||||
Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe),
|
||||
CryptoUtil.bin2base64(token, urlSafe: true),
|
||||
);
|
||||
return keyEncryptionKey;
|
||||
}
|
||||
@@ -293,28 +294,28 @@ class Configuration {
|
||||
Uint8List masterKey;
|
||||
try {
|
||||
masterKey = await CryptoUtil.decrypt(
|
||||
Sodium.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey),
|
||||
Sodium.hex2bin(recoveryKey),
|
||||
Sodium.base642bin(attributes.masterKeyDecryptionNonce),
|
||||
CryptoUtil.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey),
|
||||
CryptoUtil.hex2bin(recoveryKey),
|
||||
CryptoUtil.base642bin(attributes.masterKeyDecryptionNonce),
|
||||
);
|
||||
} catch (e) {
|
||||
_logger.severe(e);
|
||||
rethrow;
|
||||
}
|
||||
await setKey(Sodium.bin2base64(masterKey));
|
||||
await setKey(CryptoUtil.bin2base64(masterKey));
|
||||
final secretKey = CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(attributes.encryptedSecretKey),
|
||||
CryptoUtil.base642bin(attributes.encryptedSecretKey),
|
||||
masterKey,
|
||||
Sodium.base642bin(attributes.secretKeyDecryptionNonce),
|
||||
CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce),
|
||||
);
|
||||
await setSecretKey(Sodium.bin2base64(secretKey));
|
||||
await setSecretKey(CryptoUtil.bin2base64(secretKey));
|
||||
final token = CryptoUtil.openSealSync(
|
||||
Sodium.base642bin(getEncryptedToken()!),
|
||||
Sodium.base642bin(attributes.publicKey),
|
||||
CryptoUtil.base642bin(getEncryptedToken()!),
|
||||
CryptoUtil.base642bin(attributes.publicKey),
|
||||
secretKey,
|
||||
);
|
||||
await setToken(
|
||||
Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe),
|
||||
CryptoUtil.bin2base64(token, urlSafe: true),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -407,27 +408,31 @@ class Configuration {
|
||||
}
|
||||
|
||||
Uint8List? getKey() {
|
||||
return _key == null ? null : Sodium.base642bin(_key!);
|
||||
return _key == null ? null : CryptoUtil.base642bin(_key!);
|
||||
}
|
||||
|
||||
Uint8List? getSecretKey() {
|
||||
return _secretKey == null ? null : Sodium.base642bin(_secretKey!);
|
||||
return _secretKey == null ? null : CryptoUtil.base642bin(_secretKey!);
|
||||
}
|
||||
|
||||
Uint8List? getAuthSecretKey() {
|
||||
return _authSecretKey == null ? null : Sodium.base642bin(_authSecretKey!);
|
||||
return _authSecretKey == null
|
||||
? null
|
||||
: CryptoUtil.base642bin(_authSecretKey!);
|
||||
}
|
||||
|
||||
Uint8List? getOfflineSecretKey() {
|
||||
return _offlineAuthKey == null ? null : Sodium.base642bin(_offlineAuthKey!);
|
||||
return _offlineAuthKey == null
|
||||
? null
|
||||
: CryptoUtil.base642bin(_offlineAuthKey!);
|
||||
}
|
||||
|
||||
Uint8List getRecoveryKey() {
|
||||
final keyAttributes = getKeyAttributes()!;
|
||||
return CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey),
|
||||
CryptoUtil.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey),
|
||||
getKey()!,
|
||||
Sodium.base642bin(keyAttributes.recoveryKeyDecryptionNonce),
|
||||
CryptoUtil.base642bin(keyAttributes.recoveryKeyDecryptionNonce),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -454,7 +459,7 @@ class Configuration {
|
||||
iOptions: _secureStorageOptionsIOS,
|
||||
);
|
||||
} else {
|
||||
_offlineAuthKey = Sodium.bin2base64(CryptoUtil.generateKey());
|
||||
_offlineAuthKey = CryptoUtil.bin2base64(CryptoUtil.generateKey());
|
||||
await _secureStorage.write(
|
||||
key: offlineAuthSecretKey,
|
||||
value: _offlineAuthKey,
|
||||
|
||||
@@ -7,7 +7,8 @@ const String sentryDSN =
|
||||
"https://ed4ddd6309b847ba8849935e26e9b648@sentry.ente.io/9";
|
||||
const String sentryTunnel = "https://sentry-reporter.ente.io";
|
||||
const String roadmapURL = "https://roadmap.ente.io";
|
||||
const String githubDiscussionsUrl = "https://github.com/ente-io/ente/discussions";
|
||||
const String githubIssuesUrl =
|
||||
"https://github.com/ente-io/ente/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc";
|
||||
const int microSecondsInDay = 86400000000;
|
||||
const int android11SDKINT = 30;
|
||||
const int galleryLoadStartTime = -8000000000000000; // Wednesday, March 6, 1748
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
class InvalidFileError extends ArgumentError {
|
||||
InvalidFileError(String message) : super(message);
|
||||
InvalidFileError(String super.message);
|
||||
}
|
||||
|
||||
class InvalidFileUploadState extends AssertionError {
|
||||
InvalidFileUploadState(String message) : super(message);
|
||||
InvalidFileUploadState(String super.message);
|
||||
}
|
||||
|
||||
class SubscriptionAlreadyClaimedError extends Error {}
|
||||
@@ -30,19 +30,15 @@ class UnauthorizedError extends Error {}
|
||||
class RequestCancelledError extends Error {}
|
||||
|
||||
class InvalidSyncStatusError extends AssertionError {
|
||||
InvalidSyncStatusError(String message) : super(message);
|
||||
InvalidSyncStatusError(String super.message);
|
||||
}
|
||||
|
||||
class UnauthorizedEditError extends AssertionError {}
|
||||
|
||||
class InvalidStateError extends AssertionError {
|
||||
InvalidStateError(String message) : super(message);
|
||||
InvalidStateError(String super.message);
|
||||
}
|
||||
|
||||
class KeyDerivationError extends Error {}
|
||||
|
||||
class LoginKeyDerivationError extends Error {}
|
||||
|
||||
class SrpSetupNotCompleteError extends Error {}
|
||||
|
||||
class AuthenticatorKeyNotFound extends Error {}
|
||||
|
||||
@@ -235,14 +235,14 @@ class SuperLogging {
|
||||
extraLines = null;
|
||||
}
|
||||
|
||||
final str = (config.prefix) + " " + rec.toPrettyString(extraLines);
|
||||
final str = "${config.prefix} ${rec.toPrettyString(extraLines)}";
|
||||
|
||||
// write to stdout
|
||||
printLog(str);
|
||||
|
||||
// push to log queue
|
||||
if (fileIsEnabled) {
|
||||
fileQueueEntries.add(str + '\n');
|
||||
fileQueueEntries.add('$str\n');
|
||||
if (fileQueueEntries.length == 1) {
|
||||
flushQueue();
|
||||
}
|
||||
@@ -275,7 +275,7 @@ class SuperLogging {
|
||||
static var logChunkSize = 800;
|
||||
|
||||
static void printLog(String text) {
|
||||
text.chunked(logChunkSize).forEach(print);
|
||||
text.chunked(logChunkSize).forEach(debugPrint);
|
||||
}
|
||||
|
||||
/// A queue to be consumed by [setupSentry].
|
||||
@@ -354,7 +354,7 @@ class SuperLogging {
|
||||
final date = config.dateFmt!.parse(basename(file.path));
|
||||
dates[file as File] = date;
|
||||
files.add(file);
|
||||
} on FormatException {}
|
||||
} on Exception catch (_) {}
|
||||
}
|
||||
final nowTime = DateTime.now();
|
||||
|
||||
@@ -374,7 +374,7 @@ class SuperLogging {
|
||||
"deleting log file ${file.path}",
|
||||
);
|
||||
await file.delete();
|
||||
} catch (ignore) {}
|
||||
} on Exception catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class TunneledTransport implements Transport {
|
||||
_options.logger(
|
||||
SentryLevel.error,
|
||||
'API returned an error, statusCode = ${response.statusCode}, '
|
||||
'body = ${response.body}',
|
||||
'body = ${response.body}',
|
||||
);
|
||||
}
|
||||
return const SentryId.empty();
|
||||
@@ -65,8 +65,8 @@ class TunneledTransport implements Transport {
|
||||
}
|
||||
|
||||
Future<StreamedRequest> _createStreamedRequest(
|
||||
SentryEnvelope envelope,
|
||||
) async {
|
||||
SentryEnvelope envelope,
|
||||
) async {
|
||||
final streamedRequest = StreamedRequest('POST', _tunnel);
|
||||
envelope
|
||||
.envelopeStream(_options)
|
||||
@@ -91,10 +91,10 @@ class _CredentialBuilder {
|
||||
_clock = clock;
|
||||
|
||||
factory _CredentialBuilder(
|
||||
Dsn? dsn,
|
||||
String sdkIdentifier,
|
||||
ClockProvider clock,
|
||||
) {
|
||||
Dsn? dsn,
|
||||
String sdkIdentifier,
|
||||
ClockProvider clock,
|
||||
) {
|
||||
final authHeader = _buildAuthHeader(
|
||||
publicKey: dsn?.publicKey,
|
||||
secretKey: dsn?.secretKey,
|
||||
|
||||
@@ -4,9 +4,10 @@ import 'package:dio/dio.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/events/endpoint_updated_event.dart';
|
||||
import 'package:ente_auth/utils/package_info_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:fk_user_agent/fk_user_agent.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
int kConnectTimeout = 15000;
|
||||
@@ -16,34 +17,41 @@ class Network {
|
||||
late Dio _enteDio;
|
||||
|
||||
Future<void> init() async {
|
||||
await FkUserAgent.init();
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
if (PlatformUtil.isMobile()) await FkUserAgent.init();
|
||||
final packageInfo = await PackageInfoUtil().getPackageInfo();
|
||||
final version = PackageInfoUtil().getVersion(packageInfo);
|
||||
final packageName = PackageInfoUtil().getPackageName(packageInfo);
|
||||
final endpoint = Configuration.instance.getHttpEndpoint();
|
||||
|
||||
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
connectTimeout: kConnectTimeout,
|
||||
connectTimeout: Duration(milliseconds: kConnectTimeout),
|
||||
headers: {
|
||||
HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
|
||||
'X-Client-Version': packageInfo.version,
|
||||
'X-Client-Package': packageInfo.packageName,
|
||||
HttpHeaders.userAgentHeader: PlatformUtil.isMobile()
|
||||
? FkUserAgent.userAgent
|
||||
: Platform.operatingSystem,
|
||||
'X-Client-Version': version,
|
||||
'X-Client-Package': packageName,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
_enteDio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: endpoint,
|
||||
connectTimeout: kConnectTimeout,
|
||||
connectTimeout: Duration(milliseconds: kConnectTimeout),
|
||||
headers: {
|
||||
HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
|
||||
'X-Client-Version': packageInfo.version,
|
||||
'X-Client-Package': packageInfo.packageName,
|
||||
if (PlatformUtil.isMobile())
|
||||
HttpHeaders.userAgentHeader: FkUserAgent.userAgent
|
||||
else
|
||||
HttpHeaders.userAgentHeader: Platform.operatingSystem,
|
||||
'X-Client-Version': version,
|
||||
'X-Client-Package': packageName,
|
||||
},
|
||||
),
|
||||
);
|
||||
_setupInterceptors(endpoint);
|
||||
|
||||
|
||||
Bus.instance.on<EndpointUpdatedEvent>().listen((event) {
|
||||
final endpoint = Configuration.instance.getHttpEndpoint();
|
||||
_enteDio.options.baseUrl = endpoint;
|
||||
|
||||
@@ -5,12 +5,16 @@ import 'package:flutter/material.dart';
|
||||
final lightThemeData = ThemeData(
|
||||
fontFamily: 'Inter',
|
||||
brightness: Brightness.light,
|
||||
dividerTheme: const DividerThemeData(
|
||||
color: Colors.black12,
|
||||
),
|
||||
hintColor: const Color.fromRGBO(158, 158, 158, 1),
|
||||
primaryColor: const Color.fromRGBO(255, 110, 64, 1),
|
||||
primaryColorLight: const Color.fromRGBO(0, 0, 0, 0.541),
|
||||
iconTheme: const IconThemeData(color: Colors.black),
|
||||
primaryIconTheme:
|
||||
const IconThemeData(color: Colors.red, opacity: 1.0, size: 50.0),
|
||||
buttonTheme: const ButtonThemeData(),
|
||||
outlinedButtonTheme: buildOutlinedButtonThemeData(
|
||||
bgDisabled: const Color.fromRGBO(158, 158, 158, 1),
|
||||
bgEnabled: const Color.fromRGBO(0, 0, 0, 1),
|
||||
@@ -72,24 +76,42 @@ final lightThemeData = ThemeData(
|
||||
? const Color.fromRGBO(255, 255, 255, 1)
|
||||
: const Color.fromRGBO(0, 0, 0, 1);
|
||||
}),
|
||||
), radioTheme: RadioThemeData(
|
||||
fillColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) { return null; }
|
||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
||||
return null;
|
||||
}),
|
||||
), switchTheme: SwitchThemeData(
|
||||
thumbColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) { return null; }
|
||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
||||
return null;
|
||||
}),
|
||||
trackColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) { return null; }
|
||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
||||
return null;
|
||||
}),
|
||||
), colorScheme: const ColorScheme.light(
|
||||
),
|
||||
radioTheme: RadioThemeData(
|
||||
fillColor:
|
||||
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return null;
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return const Color.fromRGBO(102, 187, 106, 1);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
),
|
||||
switchTheme: SwitchThemeData(
|
||||
thumbColor:
|
||||
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return null;
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return const Color.fromRGBO(102, 187, 106, 1);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
trackColor:
|
||||
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return null;
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return const Color.fromRGBO(102, 187, 106, 1);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
),
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: Colors.black,
|
||||
secondary: Color.fromARGB(255, 163, 163, 163),
|
||||
).copyWith(background: const Color.fromRGBO(255, 255, 255, 1)),
|
||||
@@ -98,6 +120,9 @@ final lightThemeData = ThemeData(
|
||||
final darkThemeData = ThemeData(
|
||||
fontFamily: 'Inter',
|
||||
brightness: Brightness.dark,
|
||||
dividerTheme: const DividerThemeData(
|
||||
color: Colors.white12,
|
||||
),
|
||||
primaryColorLight: const Color.fromRGBO(255, 255, 255, 0.702),
|
||||
iconTheme: const IconThemeData(color: Colors.white),
|
||||
primaryIconTheme:
|
||||
@@ -105,6 +130,7 @@ final darkThemeData = ThemeData(
|
||||
hintColor: const Color.fromRGBO(158, 158, 158, 1),
|
||||
buttonTheme: const ButtonThemeData().copyWith(
|
||||
buttonColor: const Color.fromRGBO(45, 194, 98, 1.0),
|
||||
height: 56,
|
||||
),
|
||||
textTheme: _buildTextTheme(const Color.fromRGBO(255, 255, 255, 1)),
|
||||
outlinedButtonTheme: buildOutlinedButtonThemeData(
|
||||
@@ -164,24 +190,43 @@ final darkThemeData = ThemeData(
|
||||
return const Color.fromRGBO(158, 158, 158, 1);
|
||||
}
|
||||
}),
|
||||
), radioTheme: RadioThemeData(
|
||||
fillColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) { return null; }
|
||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
||||
return null;
|
||||
}),
|
||||
), switchTheme: SwitchThemeData(
|
||||
thumbColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) { return null; }
|
||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
||||
return null;
|
||||
}),
|
||||
trackColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) { return null; }
|
||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
||||
return null;
|
||||
}),
|
||||
), colorScheme: const ColorScheme.dark(primary: Colors.white).copyWith(background: const Color.fromRGBO(0, 0, 0, 1)),
|
||||
),
|
||||
radioTheme: RadioThemeData(
|
||||
fillColor:
|
||||
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return null;
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return const Color.fromRGBO(102, 187, 106, 1);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
),
|
||||
switchTheme: SwitchThemeData(
|
||||
thumbColor:
|
||||
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return null;
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return const Color.fromRGBO(102, 187, 106, 1);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
trackColor:
|
||||
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return null;
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return const Color.fromRGBO(102, 187, 106, 1);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
),
|
||||
colorScheme: const ColorScheme.dark(primary: Colors.white)
|
||||
.copyWith(background: const Color.fromRGBO(0, 0, 0, 1)),
|
||||
);
|
||||
|
||||
TextTheme _buildTextTheme(Color textColor) {
|
||||
@@ -400,6 +445,7 @@ OutlinedButtonThemeData buildOutlinedButtonThemeData({
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
fixedSize: const Size.fromHeight(56),
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.fromLTRB(50, 16, 50, 16),
|
||||
textStyle: const TextStyle(
|
||||
@@ -436,7 +482,9 @@ ElevatedButtonThemeData buildElevatedButtonThemeData({
|
||||
}) {
|
||||
return ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
foregroundColor: onPrimary, backgroundColor: primary, elevation: elevation,
|
||||
foregroundColor: onPrimary,
|
||||
backgroundColor: primary,
|
||||
elevation: elevation,
|
||||
alignment: Alignment.center,
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
|
||||
@@ -25,7 +25,7 @@ class AuthenticatorGateway {
|
||||
try {
|
||||
final response = await _enteDio.get("/authenticator/key");
|
||||
return AuthKey.fromMap(response.data);
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
if (e.response != null && (e.response!.statusCode ?? 0) == 404) {
|
||||
throw AuthenticatorKeyNotFound();
|
||||
} else {
|
||||
@@ -90,7 +90,7 @@ class AuthenticatorGateway {
|
||||
}
|
||||
return authEntities;
|
||||
} catch (e) {
|
||||
if (e is DioError && e.response?.statusCode == 401) {
|
||||
if (e is DioException && e.response?.statusCode == 401) {
|
||||
throw UnauthorizedError();
|
||||
} else {
|
||||
rethrow;
|
||||
|
||||
1
auth/lib/l10n/arb/app_bg.arb
Normal file
1
auth/lib/l10n/arb/app_bg.arb
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -144,6 +144,8 @@
|
||||
"enterCodeHint": "Geben Sie den 6-stelligen Code \naus Ihrer Authentifikator-App ein.",
|
||||
"lostDeviceTitle": "Gerät verloren?",
|
||||
"twoFactorAuthTitle": "Zwei-Faktor-Authentifizierung",
|
||||
"passkeyAuthTitle": "Passkey Authentifizierung",
|
||||
"verifyPasskey": "Passkey verifizieren",
|
||||
"recoverAccount": "Konto wiederherstellen",
|
||||
"enterRecoveryKeyHint": "Geben Sie Ihren Wiederherstellungsschlüssel ein",
|
||||
"recover": "Wiederherstellen",
|
||||
@@ -404,5 +406,15 @@
|
||||
"signOutOtherDevices": "Andere Geräte abmelden",
|
||||
"doNotSignOut": "Nicht abmelden",
|
||||
"hearUsWhereTitle": "Wie hast du von Ente erfahren? (optional)",
|
||||
"hearUsExplanation": "Wir tracken keine App-Installationen. Es würde uns jedoch helfen, wenn du uns mitteilst, wie du von uns erfahren hast!"
|
||||
"hearUsExplanation": "Wir tracken keine App-Installationen. Es würde uns jedoch helfen, wenn du uns mitteilst, wie du von uns erfahren hast!",
|
||||
"waitingForBrowserRequest": "Warten auf Browseranfrage...",
|
||||
"waitingForVerification": "Warte auf Bestätigung...",
|
||||
"passkey": "Passkey",
|
||||
"developerSettingsWarning": "Sind Sie sicher, dass Sie die Entwicklereinstellungen ändern möchten?",
|
||||
"developerSettings": "Entwicklereinstellungen",
|
||||
"serverEndpoint": "Server Endpunkt",
|
||||
"invalidEndpoint": "Ungültiger Endpunkt",
|
||||
"invalidEndpointMessage": "Der eingegebene Endpunkt ist ungültig. Bitte geben Sie einen gültigen Endpunkt ein und versuchen Sie es erneut.",
|
||||
"endpointUpdatedMessage": "Endpunkt erfolgreich aktualisiert",
|
||||
"customEndpoint": "Mit {endpoint} verbunden"
|
||||
}
|
||||
@@ -199,6 +199,10 @@
|
||||
"recoveryKeySaveDescription": "We don't store this key, please save this 24 word key in a safe place.",
|
||||
"doThisLater": "Do this later",
|
||||
"saveKey": "Save key",
|
||||
"save": "Save",
|
||||
"send": "Send",
|
||||
"saveOrSendDescription": "Do you want to save this to your storage (Downloads folder by default) or send it to other apps?",
|
||||
"saveOnlyDescription": "Do you want to save this to your storage (Downloads folder by default)?",
|
||||
"back": "Back",
|
||||
"createAccount": "Create account",
|
||||
"passwordStrength": "Password strength: {passwordStrengthValue}",
|
||||
@@ -407,6 +411,7 @@
|
||||
"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!",
|
||||
"recoveryKeySaved": "Recovery key saved in Downloads folder!",
|
||||
"waitingForBrowserRequest": "Waiting for browser request...",
|
||||
"waitingForVerification": "Waiting for verification...",
|
||||
"passkey": "Passkey",
|
||||
|
||||
@@ -144,6 +144,8 @@
|
||||
"enterCodeHint": "認証アプリに表示された 6 桁のコードを入力してください",
|
||||
"lostDeviceTitle": "デバイスを紛失しましたか?",
|
||||
"twoFactorAuthTitle": "2 要素認証",
|
||||
"passkeyAuthTitle": "パスキー認証",
|
||||
"verifyPasskey": "パスキーの認証",
|
||||
"recoverAccount": "アカウントを回復",
|
||||
"enterRecoveryKeyHint": "回復キーを入力",
|
||||
"recover": "回復",
|
||||
@@ -404,5 +406,15 @@
|
||||
"signOutOtherDevices": "他のデバイスからサインアウトする",
|
||||
"doNotSignOut": "サインアウトしない",
|
||||
"hearUsWhereTitle": "Ente についてどのようにお聞きになりましたか?(任意)",
|
||||
"hearUsExplanation": "私たちはアプリのインストールを追跡していません。私たちをお知りになった場所を教えてください!"
|
||||
"hearUsExplanation": "私たちはアプリのインストールを追跡していません。私たちをお知りになった場所を教えてください!",
|
||||
"waitingForBrowserRequest": "ブラウザのリクエストを待っています...",
|
||||
"waitingForVerification": "認証を待っています...",
|
||||
"passkey": "パスキー",
|
||||
"developerSettingsWarning": "開発者向け設定を変更してもよろしいですか?",
|
||||
"developerSettings": "開発者向け設定",
|
||||
"serverEndpoint": "サーバーエンドポイント",
|
||||
"invalidEndpoint": "無効なエンドポイントです",
|
||||
"invalidEndpointMessage": "入力されたエンドポイントは無効です。有効なエンドポイントを入力して再試行してください。",
|
||||
"endpointUpdatedMessage": "エンドポイントの更新に成功しました",
|
||||
"customEndpoint": "{endpoint} に接続しました"
|
||||
}
|
||||
@@ -144,6 +144,8 @@
|
||||
"enterCodeHint": "Digite o código de 6 dígitos de\nseu aplicativo autenticador",
|
||||
"lostDeviceTitle": "Perdeu seu dispositivo?",
|
||||
"twoFactorAuthTitle": "Autenticação de dois fatores",
|
||||
"passkeyAuthTitle": "Autenticação via Chave de acesso",
|
||||
"verifyPasskey": "Verificar chave de acesso",
|
||||
"recoverAccount": "Recuperar conta",
|
||||
"enterRecoveryKeyHint": "Digite sua chave de recuperação",
|
||||
"recover": "Recuperar",
|
||||
@@ -404,5 +406,15 @@
|
||||
"signOutOtherDevices": "Terminar sessão em outros dispositivos",
|
||||
"doNotSignOut": "Não encerrar sessão",
|
||||
"hearUsWhereTitle": "Como você ouviu sobre o Ente? (opcional)",
|
||||
"hearUsExplanation": "Não rastreamos instalações do aplicativo. Seria útil se você nos contasse onde nos encontrou!"
|
||||
"hearUsExplanation": "Não rastreamos instalações do aplicativo. Seria útil se você nos contasse onde nos encontrou!",
|
||||
"waitingForBrowserRequest": "Aguardando solicitação do navegador...",
|
||||
"waitingForVerification": "Esperando por verificação...",
|
||||
"passkey": "Chave de acesso",
|
||||
"developerSettingsWarning": "Tem certeza de que deseja modificar as configurações de Desenvolvedor?",
|
||||
"developerSettings": "Configurações de desenvolvedor",
|
||||
"serverEndpoint": "Endpoint do servidor",
|
||||
"invalidEndpoint": "Endpoint inválido",
|
||||
"invalidEndpointMessage": "Desculpe, o endpoint que você inseriu é inválido. Por favor, insira um endpoint válido e tente novamente.",
|
||||
"endpointUpdatedMessage": "Endpoint atualizado com sucesso",
|
||||
"customEndpoint": "Conectado a {endpoint}"
|
||||
}
|
||||
@@ -131,6 +131,16 @@
|
||||
"about": "Om",
|
||||
"terms": "Villkor",
|
||||
"warning": "Varning",
|
||||
"importSuccessDesc": "Du har importerat {count} koder!",
|
||||
"@importSuccessDesc": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"description": "The number of codes imported",
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pendingSyncs": "Varning",
|
||||
"activeSessions": "Aktiva sessioner",
|
||||
"enterPassword": "Ange lösenord",
|
||||
@@ -143,5 +153,7 @@
|
||||
"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": "Ingen internetanslutning",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "Kontrollera din internetanslutning och försök igen."
|
||||
}
|
||||
@@ -145,6 +145,7 @@
|
||||
"lostDeviceTitle": "丢失了设备吗?",
|
||||
"twoFactorAuthTitle": "双因素认证",
|
||||
"passkeyAuthTitle": "通行密钥认证",
|
||||
"verifyPasskey": "验证通行密钥",
|
||||
"recoverAccount": "恢复账户",
|
||||
"enterRecoveryKeyHint": "输入您的恢复密钥",
|
||||
"recover": "恢复",
|
||||
@@ -407,6 +408,13 @@
|
||||
"hearUsWhereTitle": "您是如何知道Ente的? (可选的)",
|
||||
"hearUsExplanation": "我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!",
|
||||
"waitingForBrowserRequest": "正在等待浏览器请求...",
|
||||
"launchPasskeyUrlAgain": "再次启动 通行密钥 URL",
|
||||
"passkey": "通行密钥"
|
||||
"waitingForVerification": "等待验证...",
|
||||
"passkey": "通行密钥",
|
||||
"developerSettingsWarning": "您确定要修改开发者设置吗?",
|
||||
"developerSettings": "开发者设置",
|
||||
"serverEndpoint": "服务器端点",
|
||||
"invalidEndpoint": "端点无效",
|
||||
"invalidEndpointMessage": "抱歉,您输入的端点无效。请输入有效的端点,然后重试。",
|
||||
"endpointUpdatedMessage": "端点更新成功",
|
||||
"customEndpoint": "已连接至 {endpoint}"
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:adaptive_theme/adaptive_theme.dart';
|
||||
import 'package:computer/computer.dart';
|
||||
import "package:ente_auth/app/view/app.dart";
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/constants.dart';
|
||||
@@ -17,11 +16,14 @@ import 'package:ente_auth/services/preference_service.dart';
|
||||
import 'package:ente_auth/services/update_service.dart';
|
||||
import 'package:ente_auth/services/user_remote_flag_service.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/services/window_listener_service.dart';
|
||||
import 'package:ente_auth/store/code_store.dart';
|
||||
import 'package:ente_auth/ui/tools/app_lock.dart';
|
||||
import 'package:ente_auth/ui/tools/lock_screen.dart';
|
||||
import 'package:ente_auth/ui/utils/icon_utils.dart';
|
||||
import 'package:ente_auth/utils/crypto_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/window_protocol_handler.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import 'package:flutter/scheduler.dart';
|
||||
@@ -29,11 +31,60 @@ import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:privacy_screen/privacy_screen.dart';
|
||||
import 'package:system_tray/system_tray.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
final _logger = Logger("main");
|
||||
|
||||
Future<void> initSystemTray() async {
|
||||
String path =
|
||||
Platform.isWindows ? 'assets/icon-light.ico' : 'assets/icon-light.png';
|
||||
|
||||
final AppWindow appWindow = AppWindow();
|
||||
final SystemTray systemTray = SystemTray();
|
||||
|
||||
// We first init the systray menu
|
||||
await systemTray.initSystemTray(
|
||||
title: "",
|
||||
iconPath: path,
|
||||
);
|
||||
|
||||
// create context menu
|
||||
final show = MenuItem(label: 'Show', onClicked: () => appWindow.show());
|
||||
final hide = MenuItem(label: 'Hide', onClicked: () => appWindow.hide());
|
||||
final exit = MenuItem(label: 'Exit', onClicked: () => appWindow.close());
|
||||
|
||||
// set context menu
|
||||
await systemTray.setContextMenu([show, hide, exit]);
|
||||
|
||||
const kSystemTrayEventClick = 'leftMouseDown';
|
||||
const kSystemTrayEventRightClick = 'rightMouseDown';
|
||||
// // handle system tray event
|
||||
systemTray.registerSystemTrayEventHandler((eventName) {
|
||||
if (eventName == kSystemTrayEventClick) {
|
||||
Platform.isWindows ? appWindow.show() : systemTray.popUpContextMenu();
|
||||
} else if (eventName == kSystemTrayEventRightClick) {
|
||||
Platform.isWindows ? systemTray.popUpContextMenu() : appWindow.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
initSystemTray().ignore();
|
||||
|
||||
if (PlatformUtil.isDesktop()) {
|
||||
await windowManager.ensureInitialized();
|
||||
await WindowListenerService.instance.init();
|
||||
WindowOptions windowOptions = WindowOptions(
|
||||
size: WindowListenerService.instance.getWindowSize(),
|
||||
);
|
||||
await windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||
await windowManager.show();
|
||||
await windowManager.focus();
|
||||
});
|
||||
}
|
||||
await _runInForeground();
|
||||
await _setupPrivacyScreen();
|
||||
if (Platform.isAndroid) {
|
||||
@@ -70,10 +121,14 @@ ThemeMode _themeMode(AdaptiveThemeMode? savedThemeMode) {
|
||||
}
|
||||
|
||||
Future _runWithLogs(Function() function, {String prefix = ""}) async {
|
||||
String dir = "";
|
||||
try {
|
||||
dir = "${(await getApplicationSupportDirectory()).path}/logs";
|
||||
} catch (_) {}
|
||||
await SuperLogging.main(
|
||||
LogConfig(
|
||||
body: function,
|
||||
logDirPath: (await getApplicationSupportDirectory()).path + "/logs",
|
||||
logDirPath: dir,
|
||||
maxLogFiles: 5,
|
||||
sentryDsn: sentryDSN,
|
||||
enableInDebugMode: true,
|
||||
@@ -82,10 +137,19 @@ Future _runWithLogs(Function() function, {String prefix = ""}) async {
|
||||
);
|
||||
}
|
||||
|
||||
void _registerWindowsProtocol() {
|
||||
const kWindowsScheme = 'ente';
|
||||
// Register our protocol only on Windows platform
|
||||
if (!kIsWeb && Platform.isWindows) {
|
||||
WindowsProtocolHandler()
|
||||
.register(kWindowsScheme, executable: null, arguments: null);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _init(bool bool, {String? via}) async {
|
||||
// Start workers asynchronously. No need to wait for them to start
|
||||
Computer.shared().turnOn(workersCount: 4, verbose: kDebugMode).ignore();
|
||||
CryptoUtil.init();
|
||||
_registerWindowsProtocol();
|
||||
await initCryptoUtil();
|
||||
|
||||
await PreferenceService.instance.init();
|
||||
await CodeStore.instance.init();
|
||||
await Configuration.instance.init();
|
||||
@@ -100,6 +164,7 @@ Future<void> _init(bool bool, {String? via}) async {
|
||||
}
|
||||
|
||||
Future<void> _setupPrivacyScreen() async {
|
||||
if (!PlatformUtil.isMobile()) return;
|
||||
final brightness =
|
||||
SchedulerBinding.instance.platformDispatcher.platformBrightness;
|
||||
bool isInDarkMode = brightness == Brightness.dark;
|
||||
|
||||
@@ -57,14 +57,7 @@ class Code {
|
||||
updatedAlgo,
|
||||
updatedType,
|
||||
updatedCounter,
|
||||
"otpauth://${updatedType.name}/" +
|
||||
updateIssuer +
|
||||
":" +
|
||||
updateAccount +
|
||||
"?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=" +
|
||||
updateIssuer +
|
||||
"&period=$updatePeriod&secret=" +
|
||||
updatedSecret + (updatedType == Type.hotp ? "&counter=$updatedCounter" : ""),
|
||||
"otpauth://${updatedType.name}/$updateIssuer:$updateAccount?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=$updateIssuer&period=$updatePeriod&secret=$updatedSecret${updatedType == Type.hotp ? "&counter=$updatedCounter" : ""}",
|
||||
generatedID: generatedID,
|
||||
);
|
||||
}
|
||||
@@ -83,35 +76,28 @@ class Code {
|
||||
Algorithm.sha1,
|
||||
Type.totp,
|
||||
0,
|
||||
"otpauth://totp/" +
|
||||
issuer +
|
||||
":" +
|
||||
account +
|
||||
"?algorithm=SHA1&digits=6&issuer=" +
|
||||
issuer +
|
||||
"&period=30&secret=" +
|
||||
secret,
|
||||
"otpauth://totp/$issuer:$account?algorithm=SHA1&digits=6&issuer=$issuer&period=30&secret=$secret",
|
||||
);
|
||||
}
|
||||
|
||||
static Code fromRawData(String rawData) {
|
||||
Uri uri = Uri.parse(rawData);
|
||||
try {
|
||||
return Code(
|
||||
_getAccount(uri),
|
||||
_getIssuer(uri),
|
||||
_getDigits(uri),
|
||||
_getPeriod(uri),
|
||||
getSanitizedSecret(uri.queryParameters['secret']!),
|
||||
_getAlgorithm(uri),
|
||||
_getType(uri),
|
||||
_getCounter(uri),
|
||||
rawData,
|
||||
);
|
||||
} catch(e) {
|
||||
return Code(
|
||||
_getAccount(uri),
|
||||
_getIssuer(uri),
|
||||
_getDigits(uri),
|
||||
_getPeriod(uri),
|
||||
getSanitizedSecret(uri.queryParameters['secret']!),
|
||||
_getAlgorithm(uri),
|
||||
_getType(uri),
|
||||
_getCounter(uri),
|
||||
rawData,
|
||||
);
|
||||
} catch (e) {
|
||||
// if account name contains # without encoding,
|
||||
// rest of the url are treated as url fragment
|
||||
if(rawData.contains("#")) {
|
||||
if (rawData.contains("#")) {
|
||||
return Code.fromRawData(rawData.replaceAll("#", '%23'));
|
||||
} else {
|
||||
rethrow;
|
||||
@@ -141,7 +127,7 @@ class Code {
|
||||
if (uri.queryParameters.containsKey("issuer")) {
|
||||
String issuerName = uri.queryParameters['issuer']!;
|
||||
// Handle issuer name with period
|
||||
// See https://github.com/ente-io/auth/pull/77
|
||||
// See https://github.com/ente-io/ente/pull/77
|
||||
if (issuerName.contains("period=")) {
|
||||
return issuerName.substring(0, issuerName.indexOf("period="));
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
class DerivedKeyResult {
|
||||
final Uint8List key;
|
||||
final int memLimit;
|
||||
final int opsLimit;
|
||||
|
||||
DerivedKeyResult(this.key, this.memLimit, this.opsLimit);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
class EncryptionResult {
|
||||
final Uint8List? encryptedData;
|
||||
final Uint8List? key;
|
||||
final Uint8List? header;
|
||||
final Uint8List? nonce;
|
||||
|
||||
EncryptionResult({
|
||||
this.encryptedData,
|
||||
this.key,
|
||||
this.header,
|
||||
this.nonce,
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/app/view/app.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
@@ -28,7 +29,7 @@ import "package:flutter/material.dart";
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
|
||||
class OnboardingPage extends StatefulWidget {
|
||||
const OnboardingPage({Key? key}) : super(key: key);
|
||||
const OnboardingPage({super.key});
|
||||
|
||||
@override
|
||||
State<OnboardingPage> createState() => _OnboardingPageState();
|
||||
@@ -86,118 +87,128 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
|
||||
child: Column(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
kDebugMode
|
||||
? GestureDetector(
|
||||
child: const Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Text("Lang"),
|
||||
),
|
||||
onTap: () async {
|
||||
final locale = await getLocale();
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(
|
||||
context,
|
||||
LanguageSelectorPage(
|
||||
appSupportedLocales,
|
||||
(locale) async {
|
||||
await setLocale(locale);
|
||||
App.setLocale(context, locale);
|
||||
},
|
||||
locale,
|
||||
),
|
||||
).then((value) {
|
||||
setState(() {});
|
||||
});
|
||||
},
|
||||
)
|
||||
: const SizedBox(),
|
||||
Image.asset(
|
||||
"assets/sheild-front-gradient.png",
|
||||
width: 200,
|
||||
height: 200,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
"ente",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'Montserrat',
|
||||
fontSize: 42,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"Authenticator",
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
Text(
|
||||
l10n.onBoardingBody,
|
||||
textAlign: TextAlign.center,
|
||||
style:
|
||||
Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||
color: Colors.white38,
|
||||
child: SingleChildScrollView(
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
const BoxConstraints.tightFor(height: 800, width: 450),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 40.0,
|
||||
horizontal: 40,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
kDebugMode
|
||||
? GestureDetector(
|
||||
child: const Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Text("Lang"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 100),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: GradientButton(
|
||||
onTap: _navigateToSignUpPage,
|
||||
text: l10n.newUser,
|
||||
onTap: () async {
|
||||
final locale = await getLocale();
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(
|
||||
context,
|
||||
LanguageSelectorPage(
|
||||
appSupportedLocales,
|
||||
(locale) async {
|
||||
await setLocale(locale);
|
||||
App.setLocale(context, locale);
|
||||
},
|
||||
locale,
|
||||
),
|
||||
).then((value) {
|
||||
setState(() {});
|
||||
});
|
||||
},
|
||||
)
|
||||
: const SizedBox(),
|
||||
Image.asset(
|
||||
"assets/sheild-front-gradient.png",
|
||||
width: 200,
|
||||
height: 200,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
"ente",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'Montserrat',
|
||||
fontSize: 42,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
"Authenticator",
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
Text(
|
||||
l10n.onBoardingBody,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge!
|
||||
.copyWith(
|
||||
color: Colors.white38,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(20, 12, 20, 0),
|
||||
child: Hero(
|
||||
tag: "log_in",
|
||||
child: ElevatedButton(
|
||||
style: Theme.of(context)
|
||||
.colorScheme
|
||||
.optionalActionButtonStyle,
|
||||
onPressed: _navigateToSignInPage,
|
||||
child: Text(
|
||||
l10n.existingUser,
|
||||
style: const TextStyle(
|
||||
color: Colors.black, // same for both themes
|
||||
const SizedBox(height: 100),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: GradientButton(
|
||||
onTap: _navigateToSignUpPage,
|
||||
text: l10n.newUser,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
height: 56,
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||
child: Hero(
|
||||
tag: "log_in",
|
||||
child: ElevatedButton(
|
||||
style: Theme.of(context)
|
||||
.colorScheme
|
||||
.optionalActionButtonStyle,
|
||||
onPressed: _navigateToSignInPage,
|
||||
child: Text(
|
||||
l10n.existingUser,
|
||||
style: const TextStyle(
|
||||
color: Colors.black, // same for both themes
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.only(top: 20, bottom: 20),
|
||||
child: GestureDetector(
|
||||
onTap: _optForOfflineMode,
|
||||
child: Center(
|
||||
child: Text(
|
||||
l10n.useOffline,
|
||||
style: body.copyWith(
|
||||
color:
|
||||
Theme.of(context).colorScheme.mutedTextColor,
|
||||
const SizedBox(height: 4),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.only(top: 20, bottom: 20),
|
||||
child: GestureDetector(
|
||||
onTap: _optForOfflineMode,
|
||||
child: Center(
|
||||
child: Text(
|
||||
l10n.useOffline,
|
||||
style: body.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.mutedTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const DeveloperSettingsWidget(),
|
||||
],
|
||||
const DeveloperSettingsWidget(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -208,7 +219,9 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
}
|
||||
|
||||
Future<void> _optForOfflineMode() async {
|
||||
bool canCheckBio = await LocalAuthentication().canCheckBiometrics;
|
||||
bool canCheckBio = Platform.isMacOS || Platform.isLinux
|
||||
? true
|
||||
: await LocalAuthentication().canCheckBiometrics;
|
||||
if (!canCheckBio) {
|
||||
showToast(
|
||||
context,
|
||||
|
||||
@@ -9,7 +9,7 @@ import "package:flutter/material.dart";
|
||||
class SetupEnterSecretKeyPage extends StatefulWidget {
|
||||
final Code? code;
|
||||
|
||||
SetupEnterSecretKeyPage({this.code, Key? key}) : super(key: key);
|
||||
SetupEnterSecretKeyPage({this.code, super.key});
|
||||
|
||||
@override
|
||||
State<SetupEnterSecretKeyPage> createState() =>
|
||||
@@ -32,7 +32,7 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
||||
widget.code != null ? safeDecode(widget.code!.account).trim() : null,
|
||||
);
|
||||
_secretController = TextEditingController(
|
||||
text: widget.code != null ? widget.code!.secret : null,
|
||||
text: widget.code?.secret,
|
||||
);
|
||||
_secretKeyObscured = widget.code != null;
|
||||
super.initState();
|
||||
@@ -45,8 +45,8 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.importAccountPageTitle),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
|
||||
child: Column(
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import "package:ente_auth/l10n/l10n.dart";
|
||||
@@ -10,7 +9,7 @@ import 'package:qr_flutter/qr_flutter.dart';
|
||||
class ViewQrPage extends StatelessWidget {
|
||||
final Code? code;
|
||||
|
||||
ViewQrPage({this.code, Key? key}) : super(key: key);
|
||||
ViewQrPage({this.code, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -22,15 +21,22 @@ class ViewQrPage extends StatelessWidget {
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.qrCode),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
|
||||
child: Column(
|
||||
children: [
|
||||
QrImage(
|
||||
QrImageView(
|
||||
data: code!.rawData,
|
||||
foregroundColor: Theme.of(context).colorScheme.onBackground,
|
||||
eyeStyle: QrEyeStyle(
|
||||
eyeShape: QrEyeShape.square,
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
dataModuleStyle: QrDataModuleStyle(
|
||||
dataModuleShape: QrDataModuleShape.square,
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
version: QrVersions.auto,
|
||||
size: qrSize,
|
||||
),
|
||||
|
||||
@@ -15,9 +15,8 @@ import 'package:ente_auth/models/authenticator/entity_result.dart';
|
||||
import 'package:ente_auth/models/authenticator/local_auth_entity.dart';
|
||||
import 'package:ente_auth/store/authenticator_db.dart';
|
||||
import 'package:ente_auth/store/offline_authenticator_db.dart';
|
||||
import 'package:ente_auth/utils/crypto_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
@@ -75,10 +74,10 @@ class AuthenticatorService {
|
||||
final key = await getOrCreateAuthDataKey(mode);
|
||||
for (LocalAuthEntity e in result) {
|
||||
try {
|
||||
final decryptedValue = await CryptoUtil.decryptChaCha(
|
||||
Sodium.base642bin(e.encryptedData),
|
||||
final decryptedValue = await CryptoUtil.decryptData(
|
||||
CryptoUtil.base642bin(e.encryptedData),
|
||||
key,
|
||||
Sodium.base642bin(e.header),
|
||||
CryptoUtil.base642bin(e.header),
|
||||
);
|
||||
final hasSynced = !(e.id == null || e.shouldSync);
|
||||
entities.add(
|
||||
@@ -101,12 +100,13 @@ class AuthenticatorService {
|
||||
AccountMode accountMode,
|
||||
) async {
|
||||
var key = await getOrCreateAuthDataKey(accountMode);
|
||||
final encryptedKeyData = await CryptoUtil.encryptChaCha(
|
||||
utf8.encode(plainText) as Uint8List,
|
||||
final encryptedKeyData = await CryptoUtil.encryptData(
|
||||
utf8.encode(plainText),
|
||||
key,
|
||||
);
|
||||
String encryptedData = Sodium.bin2base64(encryptedKeyData.encryptedData!);
|
||||
String header = Sodium.bin2base64(encryptedKeyData.header!);
|
||||
String encryptedData =
|
||||
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!);
|
||||
String header = CryptoUtil.bin2base64(encryptedKeyData.header!);
|
||||
final insertedID = accountMode.isOnline
|
||||
? await _db.insert(encryptedData, header)
|
||||
: await _offlineDb.insert(encryptedData, header);
|
||||
@@ -123,12 +123,13 @@ class AuthenticatorService {
|
||||
AccountMode accountMode,
|
||||
) async {
|
||||
var key = await getOrCreateAuthDataKey(accountMode);
|
||||
final encryptedKeyData = await CryptoUtil.encryptChaCha(
|
||||
utf8.encode(plainText) as Uint8List,
|
||||
final encryptedKeyData = await CryptoUtil.encryptData(
|
||||
utf8.encode(plainText),
|
||||
key,
|
||||
);
|
||||
String encryptedData = Sodium.bin2base64(encryptedKeyData.encryptedData!);
|
||||
String header = Sodium.bin2base64(encryptedKeyData.header!);
|
||||
String encryptedData =
|
||||
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!);
|
||||
String header = CryptoUtil.bin2base64(encryptedKeyData.header!);
|
||||
final int affectedRows = accountMode.isOnline
|
||||
? await _db.updateEntry(generatedID, encryptedData, header)
|
||||
: await _offlineDb.updateEntry(generatedID, encryptedData, header);
|
||||
@@ -191,25 +192,25 @@ class AuthenticatorService {
|
||||
Future<void> _remoteToLocalSync() async {
|
||||
_logger.info('Initiating remote to local sync');
|
||||
final int lastSyncTime = _prefs.getInt(_lastEntitySyncTime) ?? 0;
|
||||
_logger.info("Current sync is " + lastSyncTime.toString());
|
||||
_logger.info("Current sync is $lastSyncTime");
|
||||
const int fetchLimit = 500;
|
||||
final List<AuthEntity> result =
|
||||
await _gateway.getDiff(lastSyncTime, limit: fetchLimit);
|
||||
_logger.info(result.length.toString() + " entries fetched from remote");
|
||||
_logger.info("${result.length} entries fetched from remote");
|
||||
if (result.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final maxSyncTime = result.map((e) => e.updatedAt).reduce(max);
|
||||
List<String> deletedIDs =
|
||||
result.where((element) => element.isDeleted).map((e) => e.id).toList();
|
||||
_logger.info(deletedIDs.length.toString() + " entries deleted");
|
||||
_logger.info("${deletedIDs.length} entries deleted");
|
||||
result.removeWhere((element) => element.isDeleted);
|
||||
await _db.insertOrReplace(result);
|
||||
if (deletedIDs.isNotEmpty) {
|
||||
await _db.deleteByIDs(ids: deletedIDs);
|
||||
}
|
||||
await _prefs.setInt(_lastEntitySyncTime, maxSyncTime);
|
||||
_logger.info("Setting synctime to " + maxSyncTime.toString());
|
||||
_logger.info("Setting synctime to $maxSyncTime");
|
||||
if (result.length == fetchLimit) {
|
||||
_logger.info("Diff limit reached, pulling again");
|
||||
await _remoteToLocalSync();
|
||||
@@ -223,7 +224,7 @@ class AuthenticatorService {
|
||||
.where((element) => element.shouldSync || element.id == null)
|
||||
.toList();
|
||||
_logger.info(
|
||||
pendingUpdate.length.toString() + " entries to be updated at remote",
|
||||
"${pendingUpdate.length} entries to be updated at remote",
|
||||
);
|
||||
for (LocalAuthEntity entity in pendingUpdate) {
|
||||
if (entity.id == null) {
|
||||
@@ -262,21 +263,21 @@ class AuthenticatorService {
|
||||
try {
|
||||
final AuthKey response = await _gateway.getKey();
|
||||
final authKey = CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(response.encryptedKey),
|
||||
CryptoUtil.base642bin(response.encryptedKey),
|
||||
_config.getKey()!,
|
||||
Sodium.base642bin(response.header),
|
||||
CryptoUtil.base642bin(response.header),
|
||||
);
|
||||
await _config.setAuthSecretKey(Sodium.bin2base64(authKey));
|
||||
await _config.setAuthSecretKey(CryptoUtil.bin2base64(authKey));
|
||||
return authKey;
|
||||
} on AuthenticatorKeyNotFound catch (e) {
|
||||
_logger.info("AuthenticatorKeyNotFound generating key ${e.stackTrace}");
|
||||
final key = CryptoUtil.generateKey();
|
||||
final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey()!);
|
||||
await _gateway.createKey(
|
||||
Sodium.bin2base64(encryptedKeyData.encryptedData!),
|
||||
Sodium.bin2base64(encryptedKeyData.nonce!),
|
||||
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedKeyData.nonce!),
|
||||
);
|
||||
await _config.setAuthSecretKey(Sodium.bin2base64(key));
|
||||
await _config.setAuthSecretKey(CryptoUtil.bin2base64(key));
|
||||
return key;
|
||||
} catch (e, s) {
|
||||
_logger.severe("Failed to getOrCreateAuthDataKey", e, s);
|
||||
|
||||
@@ -50,7 +50,7 @@ class BillingService {
|
||||
|
||||
Future<Response<dynamic>> _fetchPrivateBillingPlans() {
|
||||
return _dio.get(
|
||||
_config.getHttpEndpoint() + "/billing/user-plans/",
|
||||
"${_config.getHttpEndpoint()}/billing/user-plans/",
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
@@ -60,7 +60,7 @@ class BillingService {
|
||||
}
|
||||
|
||||
Future<Response<dynamic>> _fetchPublicBillingPlans() {
|
||||
return _dio.get(_config.getHttpEndpoint() + "/billing/plans/v2");
|
||||
return _dio.get("${_config.getHttpEndpoint()}/billing/plans/v2");
|
||||
}
|
||||
|
||||
Future<Subscription> verifySubscription(
|
||||
@@ -70,7 +70,7 @@ class BillingService {
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/billing/verify-subscription",
|
||||
"${_config.getHttpEndpoint()}/billing/verify-subscription",
|
||||
data: {
|
||||
"paymentProvider": paymentProvider ??
|
||||
(Platform.isAndroid ? "playstore" : "appstore"),
|
||||
@@ -84,7 +84,7 @@ class BillingService {
|
||||
),
|
||||
);
|
||||
return Subscription.fromMap(response.data["subscription"]);
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
if (e.response != null && e.response!.statusCode == 409) {
|
||||
throw SubscriptionAlreadyClaimedError();
|
||||
} else {
|
||||
@@ -100,7 +100,7 @@ class BillingService {
|
||||
if (_cachedSubscription == null) {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
_config.getHttpEndpoint() + "/billing/subscription",
|
||||
"${_config.getHttpEndpoint()}/billing/subscription",
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
@@ -109,7 +109,7 @@ class BillingService {
|
||||
);
|
||||
_cachedSubscription =
|
||||
Subscription.fromMap(response.data["subscription"]);
|
||||
} on DioError catch (e, s) {
|
||||
} on DioException catch (e, s) {
|
||||
_logger.severe(e, s);
|
||||
rethrow;
|
||||
}
|
||||
@@ -120,7 +120,7 @@ class BillingService {
|
||||
Future<Subscription> cancelStripeSubscription() async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/billing/stripe/cancel-subscription",
|
||||
"${_config.getHttpEndpoint()}/billing/stripe/cancel-subscription",
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
@@ -129,7 +129,7 @@ class BillingService {
|
||||
);
|
||||
final subscription = Subscription.fromMap(response.data["subscription"]);
|
||||
return subscription;
|
||||
} on DioError catch (e, s) {
|
||||
} on DioException catch (e, s) {
|
||||
_logger.severe(e, s);
|
||||
rethrow;
|
||||
}
|
||||
@@ -138,7 +138,7 @@ class BillingService {
|
||||
Future<Subscription> activateStripeSubscription() async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/billing/stripe/activate-subscription",
|
||||
"${_config.getHttpEndpoint()}/billing/stripe/activate-subscription",
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
@@ -147,7 +147,7 @@ class BillingService {
|
||||
);
|
||||
final subscription = Subscription.fromMap(response.data["subscription"]);
|
||||
return subscription;
|
||||
} on DioError catch (e, s) {
|
||||
} on DioException catch (e, s) {
|
||||
_logger.severe(e, s);
|
||||
rethrow;
|
||||
}
|
||||
@@ -158,7 +158,7 @@ class BillingService {
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
_config.getHttpEndpoint() + "/billing/stripe/customer-portal",
|
||||
"${_config.getHttpEndpoint()}/billing/stripe/customer-portal",
|
||||
queryParameters: {
|
||||
"redirectURL": kWebPaymentRedirectUrl,
|
||||
},
|
||||
@@ -169,7 +169,7 @@ class BillingService {
|
||||
),
|
||||
);
|
||||
return response.data["url"];
|
||||
} on DioError catch (e, s) {
|
||||
} on DioException catch (e, s) {
|
||||
_logger.severe(e, s);
|
||||
rethrow;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/ui/tools/app_lock.dart';
|
||||
import 'package:ente_auth/utils/auth_util.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_local_authentication/flutter_local_authentication.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class LocalAuthenticationService {
|
||||
LocalAuthenticationService._privateConstructor();
|
||||
static final LocalAuthenticationService instance =
|
||||
LocalAuthenticationService._privateConstructor();
|
||||
final logger = Logger((LocalAuthenticationService).toString());
|
||||
|
||||
Future<bool> requestLocalAuthentication(
|
||||
BuildContext context,
|
||||
@@ -38,7 +44,7 @@ class LocalAuthenticationService {
|
||||
String errorDialogContent, [
|
||||
String errorDialogTitle = "",
|
||||
]) async {
|
||||
if (await LocalAuthentication().isDeviceSupported()) {
|
||||
if (await _isLocalAuthSupportedOnDevice()) {
|
||||
AppLock.of(context)!.disable();
|
||||
final result = await requestAuthentication(
|
||||
context,
|
||||
@@ -65,6 +71,12 @@ class LocalAuthenticationService {
|
||||
}
|
||||
|
||||
Future<bool> _isLocalAuthSupportedOnDevice() async {
|
||||
return await LocalAuthentication().isDeviceSupported();
|
||||
try {
|
||||
return Platform.isMacOS || Platform.isLinux
|
||||
? await FlutterLocalAuthentication().canAuthenticate()
|
||||
: await LocalAuthentication().isDeviceSupported();
|
||||
} on MissingPluginException {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,7 @@ class NotificationService {
|
||||
_flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>();
|
||||
if (implementation != null) {
|
||||
// ignore: unawaited_futures
|
||||
implementation.requestPermission();
|
||||
await implementation.requestNotificationsPermission();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:io';
|
||||
import 'package:ente_auth/core/constants.dart';
|
||||
import 'package:ente_auth/core/network.dart';
|
||||
import 'package:ente_auth/services/notification_service.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
@@ -130,7 +131,8 @@ class UpdateService {
|
||||
|
||||
bool isIndependent() {
|
||||
return flavor == "independent" ||
|
||||
_packageInfo.packageName.endsWith("independent");
|
||||
_packageInfo.packageName.endsWith("independent") ||
|
||||
PlatformUtil.isDesktop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +143,7 @@ class LatestVersionInfo {
|
||||
final bool? shouldForceUpdate;
|
||||
final int lastSupportedVersionCode;
|
||||
final String? url;
|
||||
final String? release;
|
||||
final int? size;
|
||||
final bool? shouldNotify;
|
||||
|
||||
@@ -151,6 +154,7 @@ class LatestVersionInfo {
|
||||
this.shouldForceUpdate,
|
||||
this.lastSupportedVersionCode,
|
||||
this.url,
|
||||
this.release,
|
||||
this.size,
|
||||
this.shouldNotify,
|
||||
);
|
||||
@@ -163,6 +167,7 @@ class LatestVersionInfo {
|
||||
map['shouldForceUpdate'],
|
||||
map['lastSupportedVersionCode'] ?? 1,
|
||||
map['url'],
|
||||
map['release'],
|
||||
map['size'],
|
||||
map['shouldNotify'],
|
||||
);
|
||||
|
||||
@@ -96,7 +96,7 @@ class UserRemoteFlagService {
|
||||
queryParams["defaultValue"] = defaultValue;
|
||||
}
|
||||
final response = await _dio.get(
|
||||
_config.getHttpEndpoint() + "/remote-store",
|
||||
"${_config.getHttpEndpoint()}/remote-store",
|
||||
queryParameters: queryParams,
|
||||
options: Options(
|
||||
headers: {
|
||||
@@ -119,7 +119,7 @@ class UserRemoteFlagService {
|
||||
Future<void> _updateKeyValue(String key, String value) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/remote-store/update",
|
||||
"${_config.getHttpEndpoint()}/remote-store/update",
|
||||
data: {
|
||||
"key": key,
|
||||
"value": value,
|
||||
|
||||
@@ -30,9 +30,9 @@ import 'package:ente_auth/ui/home_page.dart';
|
||||
import 'package:ente_auth/ui/passkey_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/toast_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import "package:flutter/foundation.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -80,7 +80,7 @@ class UserService {
|
||||
await dialog.show();
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/users/ott",
|
||||
"${_config.getHttpEndpoint()}/users/ott",
|
||||
data: {"email": email, "purpose": isChangeEmail ? "change" : ""},
|
||||
);
|
||||
await dialog.hide();
|
||||
@@ -102,7 +102,7 @@ class UserService {
|
||||
return;
|
||||
}
|
||||
unawaited(showGenericErrorDialog(context: context));
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.info(e);
|
||||
if (e.response != null && e.response!.statusCode == 403) {
|
||||
@@ -129,7 +129,7 @@ class UserService {
|
||||
String type = "SubCancellation",
|
||||
}) async {
|
||||
await _dio.post(
|
||||
_config.getHttpEndpoint() + "/anonymous/feedback",
|
||||
"${_config.getHttpEndpoint()}/anonymous/feedback",
|
||||
data: {"feedback": feedback, "type": "type"},
|
||||
);
|
||||
}
|
||||
@@ -173,7 +173,7 @@ class UserService {
|
||||
try {
|
||||
final response = await _enteDio.get("/users/sessions");
|
||||
return Sessions.fromMap(response.data);
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
_logger.info(e);
|
||||
rethrow;
|
||||
}
|
||||
@@ -187,7 +187,7 @@ class UserService {
|
||||
"token": token,
|
||||
},
|
||||
);
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
_logger.info(e);
|
||||
rethrow;
|
||||
}
|
||||
@@ -196,7 +196,7 @@ class UserService {
|
||||
Future<void> leaveFamilyPlan() async {
|
||||
try {
|
||||
await _enteDio.delete("/family/leave");
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
_logger.warning('failed to leave family plan', e);
|
||||
rethrow;
|
||||
}
|
||||
@@ -306,11 +306,11 @@ class UserService {
|
||||
"ott": ott,
|
||||
};
|
||||
if (!_config.isLoggedIn()) {
|
||||
verifyData["source"] = 'auth:' + _getRefSource();
|
||||
verifyData["source"] = 'auth:${_getRefSource()}';
|
||||
}
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/users/verify-email",
|
||||
"${_config.getHttpEndpoint()}/users/verify-email",
|
||||
data: verifyData,
|
||||
);
|
||||
await dialog.hide();
|
||||
@@ -346,7 +346,7 @@ class UserService {
|
||||
// should never reach here
|
||||
throw Exception("unexpected response during email verification");
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
_logger.info(e);
|
||||
await dialog.hide();
|
||||
if (e.response != null && e.response!.statusCode == 410) {
|
||||
@@ -410,7 +410,7 @@ class UserService {
|
||||
context.l10n.oops,
|
||||
context.l10n.verificationFailedPleaseTryAgain,
|
||||
);
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
await dialog.hide();
|
||||
if (e.response != null && e.response!.statusCode == 403) {
|
||||
// ignore: unawaited_futures
|
||||
@@ -460,7 +460,7 @@ class UserService {
|
||||
Future<SrpAttributes> getSrpAttributes(String email) async {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
_config.getHttpEndpoint() + "/users/srp/attributes",
|
||||
"${_config.getHttpEndpoint()}/users/srp/attributes",
|
||||
queryParameters: {
|
||||
"email": email,
|
||||
},
|
||||
@@ -470,7 +470,7 @@ class UserService {
|
||||
} else {
|
||||
throw Exception("get-srp-attributes action failed");
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
if (e.response != null && e.response!.statusCode == 404) {
|
||||
throw SrpSetupNotCompleteError();
|
||||
}
|
||||
@@ -523,7 +523,7 @@ class UserService {
|
||||
// ignore: need to calculate secret to get M1, unused_local_variable
|
||||
final clientS = client.calculateSecret(serverB);
|
||||
final clientM = client.calculateClientEvidenceMessage();
|
||||
// ignore: unused_local_variable
|
||||
|
||||
late Response _;
|
||||
if (setKeysRequest == null) {
|
||||
_ = await _enteDio.post(
|
||||
@@ -573,7 +573,7 @@ class UserService {
|
||||
late Uint8List keyEncryptionKey;
|
||||
_logger.finest('Start deriving key');
|
||||
keyEncryptionKey = await CryptoUtil.deriveKey(
|
||||
utf8.encode(userPassword) as Uint8List,
|
||||
utf8.encode(userPassword),
|
||||
CryptoUtil.base642bin(srpAttributes.kekSalt),
|
||||
srpAttributes.memLimit,
|
||||
srpAttributes.opsLimit,
|
||||
@@ -596,7 +596,7 @@ class UserService {
|
||||
|
||||
final A = client.generateClientCredentials(salt, identity, password);
|
||||
final createSessionResponse = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/users/srp/create-session",
|
||||
"${_config.getHttpEndpoint()}/users/srp/create-session",
|
||||
data: {
|
||||
"srpUserID": srpAttributes.srpUserID,
|
||||
"srpA": base64Encode(SRP6Util.encodeBigInt(A!)),
|
||||
@@ -610,7 +610,7 @@ class UserService {
|
||||
final clientS = client.calculateSecret(serverB);
|
||||
final clientM = client.calculateClientEvidenceMessage();
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/users/srp/verify-session",
|
||||
"${_config.getHttpEndpoint()}/users/srp/verify-session",
|
||||
data: {
|
||||
"sessionID": sessionID,
|
||||
"srpUserID": srpAttributes.srpUserID,
|
||||
@@ -709,7 +709,7 @@ class UserService {
|
||||
await dialog.show();
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/users/two-factor/verify",
|
||||
"${_config.getHttpEndpoint()}/users/two-factor/verify",
|
||||
data: {
|
||||
"sessionID": sessionID,
|
||||
"code": code,
|
||||
@@ -729,7 +729,7 @@ class UserService {
|
||||
(route) => route.isFirst,
|
||||
);
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
if (e.response != null && e.response!.statusCode == 404) {
|
||||
@@ -772,7 +772,7 @@ class UserService {
|
||||
await dialog.show();
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
_config.getHttpEndpoint() + "/users/two-factor/recover",
|
||||
"${_config.getHttpEndpoint()}/users/two-factor/recover",
|
||||
queryParameters: {
|
||||
"sessionID": sessionID,
|
||||
"twoFactorType": twoFactorTypeToString(type),
|
||||
@@ -794,7 +794,7 @@ class UserService {
|
||||
(route) => route.isFirst,
|
||||
);
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
if (e.response != null && e.response!.statusCode == 404) {
|
||||
@@ -868,7 +868,7 @@ class UserService {
|
||||
}
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/users/two-factor/remove",
|
||||
"${_config.getHttpEndpoint()}/users/two-factor/remove",
|
||||
data: {
|
||||
"sessionID": sessionID,
|
||||
"secret": secret,
|
||||
@@ -891,7 +891,7 @@ class UserService {
|
||||
(route) => route.isFirst,
|
||||
);
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
if (e.response != null && e.response!.statusCode == 404) {
|
||||
|
||||
36
auth/lib/services/window_listener_service.dart
Normal file
36
auth/lib/services/window_listener_service.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
class WindowListenerService {
|
||||
late SharedPreferences _preferences;
|
||||
|
||||
WindowListenerService._privateConstructor();
|
||||
|
||||
static final WindowListenerService instance =
|
||||
WindowListenerService._privateConstructor();
|
||||
|
||||
Future<void> init() async {
|
||||
_preferences = await SharedPreferences.getInstance();
|
||||
}
|
||||
|
||||
Size getWindowSize() {
|
||||
final double windowWidth = _preferences.getDouble('windowWidth') ?? 450.0;
|
||||
final double windowHeight = _preferences.getDouble('windowHeight') ?? 800.0;
|
||||
return Size(windowWidth, windowHeight);
|
||||
}
|
||||
|
||||
Future<void> onWindowResize() async {
|
||||
// Save the window size to shared preferences
|
||||
await _preferences.setDouble(
|
||||
'windowWidth',
|
||||
(await windowManager.getSize()).width,
|
||||
);
|
||||
await _preferences.setDouble(
|
||||
'windowHeight',
|
||||
(await windowManager.getSize()).height,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,12 @@ import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/models/authenticator/auth_entity.dart';
|
||||
import 'package:ente_auth/models/authenticator/local_auth_entity.dart';
|
||||
import 'package:ente_auth/utils/directory_utils.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
|
||||
class AuthenticatorDB {
|
||||
static const _databaseName = "ente.authenticator.db";
|
||||
@@ -25,6 +27,16 @@ class AuthenticatorDB {
|
||||
}
|
||||
|
||||
Future<Database> _initDatabase() async {
|
||||
if (Platform.isWindows || Platform.isLinux) {
|
||||
var databaseFactory = databaseFactoryFfi;
|
||||
return await databaseFactory.openDatabase(
|
||||
await DirectoryUtils.getDatabasePath(_databaseName),
|
||||
options: OpenDatabaseOptions(
|
||||
version: _databaseVersion,
|
||||
onCreate: _onCreate,
|
||||
),
|
||||
);
|
||||
}
|
||||
final Directory documentsDirectory =
|
||||
await getApplicationDocumentsDirectory();
|
||||
final String path = join(documentsDirectory.path, _databaseName);
|
||||
@@ -166,7 +178,7 @@ class AuthenticatorDB {
|
||||
batch.delete(entityTable, where: whereID, whereArgs: [id]);
|
||||
}
|
||||
}
|
||||
await batch.commit();
|
||||
final _ = await batch.commit();
|
||||
debugPrint("Done");
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,11 @@ import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/models/authenticator/auth_entity.dart';
|
||||
import 'package:ente_auth/models/authenticator/local_auth_entity.dart';
|
||||
import 'package:ente_auth/utils/directory_utils.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
|
||||
class OfflineAuthenticatorDB {
|
||||
static const _databaseName = "ente.offline_authenticator.db";
|
||||
@@ -26,6 +27,16 @@ class OfflineAuthenticatorDB {
|
||||
}
|
||||
|
||||
Future<Database> _initDatabase() async {
|
||||
if (Platform.isWindows || Platform.isLinux) {
|
||||
var databaseFactory = databaseFactoryFfi;
|
||||
return await databaseFactory.openDatabase(
|
||||
await DirectoryUtils.getDatabasePath(_databaseName),
|
||||
options: OpenDatabaseOptions(
|
||||
version: _databaseVersion,
|
||||
onCreate: _onCreate,
|
||||
),
|
||||
);
|
||||
}
|
||||
final Directory documentsDirectory =
|
||||
await getApplicationDocumentsDirectory();
|
||||
final String path = join(documentsDirectory.path, _databaseName);
|
||||
@@ -152,7 +163,7 @@ class OfflineAuthenticatorDB {
|
||||
batch.delete(entityTable, where: whereID, whereArgs: [id]);
|
||||
}
|
||||
}
|
||||
await batch.commit();
|
||||
final _ = await batch.commit();
|
||||
debugPrint("Done");
|
||||
}
|
||||
|
||||
|
||||
@@ -204,6 +204,7 @@ const Color _warning700 = Color.fromRGBO(234, 63, 63, 1);
|
||||
const Color _warning500 = Color.fromRGBO(255, 101, 101, 1);
|
||||
const Color _warning800 = Color(0xFFF53434);
|
||||
const Color warning500 = Color.fromRGBO(255, 101, 101, 1);
|
||||
// ignore: unused_element
|
||||
const Color _warning400 = Color.fromRGBO(255, 111, 111, 1);
|
||||
|
||||
const Color _caution500 = Color.fromRGBO(255, 194, 71, 1);
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:ente_auth/utils/email_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ChangeEmailDialog extends StatefulWidget {
|
||||
const ChangeEmailDialog({Key? key}) : super(key: key);
|
||||
const ChangeEmailDialog({super.key});
|
||||
|
||||
@override
|
||||
State<ChangeEmailDialog> createState() => _ChangeEmailDialogState();
|
||||
|
||||
@@ -7,15 +7,15 @@ import 'package:ente_auth/services/local_authentication_service.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/ui/common/dialogs.dart';
|
||||
import 'package:ente_auth/ui/common/gradient_button.dart';
|
||||
import 'package:ente_auth/utils/crypto_util.dart';
|
||||
import 'package:ente_auth/utils/email_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
|
||||
class DeleteAccountPage extends StatelessWidget {
|
||||
const DeleteAccountPage({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -150,6 +150,8 @@ class DeleteAccountPage extends StatelessWidget {
|
||||
l10n.initiateAccountDeleteTitle,
|
||||
);
|
||||
|
||||
await PlatformUtil.refocusWindows();
|
||||
|
||||
if (hasAuthenticated) {
|
||||
final choice = await showChoiceDialogOld(
|
||||
context,
|
||||
@@ -164,8 +166,10 @@ class DeleteAccountPage extends StatelessWidget {
|
||||
return;
|
||||
}
|
||||
final decryptChallenge = CryptoUtil.openSealSync(
|
||||
Sodium.base642bin(response.encryptedChallenge),
|
||||
Sodium.base642bin(Configuration.instance.getKeyAttributes()!.publicKey),
|
||||
CryptoUtil.base642bin(response.encryptedChallenge),
|
||||
CryptoUtil.base642bin(
|
||||
Configuration.instance.getKeyAttributes()!.publicKey,
|
||||
),
|
||||
Configuration.instance.getSecretKey()!,
|
||||
);
|
||||
final challengeResponseStr = utf8.decode(decryptChallenge);
|
||||
|
||||
@@ -5,7 +5,7 @@ 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/platform_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -14,7 +14,7 @@ import 'package:step_progress_indicator/step_progress_indicator.dart';
|
||||
import "package:styled_text/styled_text.dart";
|
||||
|
||||
class EmailEntryPage extends StatefulWidget {
|
||||
const EmailEntryPage({Key? key}) : super(key: key);
|
||||
const EmailEntryPage({super.key});
|
||||
|
||||
@override
|
||||
State<EmailEntryPage> createState() => _EmailEntryPageState();
|
||||
@@ -190,6 +190,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||
child: TextFormField(
|
||||
keyboardType: TextInputType.text,
|
||||
textInputAction: TextInputAction.next,
|
||||
controller: _passwordController1,
|
||||
obscureText: !_password1Visible,
|
||||
enableSuggestions: true,
|
||||
@@ -427,15 +428,10 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
tags: {
|
||||
'u-terms': StyledTextActionTag(
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.termsOfServicesTitle,
|
||||
"https://ente.io/terms",
|
||||
);
|
||||
},
|
||||
),
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.termsOfServicesTitle,
|
||||
"https://ente.io/terms",
|
||||
),
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
@@ -443,15 +439,10 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
),
|
||||
'u-policy': StyledTextActionTag(
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.privacyPolicyTitle,
|
||||
"https://ente.io/privacy",
|
||||
);
|
||||
},
|
||||
),
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.privacyPolicyTitle,
|
||||
"https://ente.io/privacy",
|
||||
),
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
@@ -494,15 +485,10 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
tags: {
|
||||
'underline': StyledTextActionTag(
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.encryption,
|
||||
"https://ente.io/architecture",
|
||||
);
|
||||
},
|
||||
),
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.encryption,
|
||||
"https://ente.io/architecture",
|
||||
),
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
|
||||
@@ -6,13 +6,13 @@ import 'package:ente_auth/models/api/user/srp.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/ui/account/login_pwd_verification_page.dart';
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import 'package:ente_auth/ui/common/web_page.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import "package:styled_text/styled_text.dart";
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
const LoginPage({Key? key}) : super(key: key);
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
State<LoginPage> createState() => _LoginPageState();
|
||||
@@ -25,6 +25,36 @@ class _LoginPageState extends State<LoginPage> {
|
||||
Color? _emailInputFieldColor;
|
||||
final Logger _logger = Logger('_LoginPageState');
|
||||
|
||||
Future<void> onPressed() async {
|
||||
await UserService.instance.setEmail(_email!);
|
||||
Configuration.instance.resetVolatilePassword();
|
||||
SrpAttributes? attr;
|
||||
bool isEmailVerificationEnabled = true;
|
||||
try {
|
||||
attr = await UserService.instance.getSrpAttributes(_email!);
|
||||
isEmailVerificationEnabled = attr.isEmailMFAEnabled;
|
||||
} catch (e) {
|
||||
if (e is! SrpSetupNotCompleteError) {
|
||||
_logger.severe('Error getting SRP attributes', e);
|
||||
}
|
||||
}
|
||||
if (attr != null && !isEmailVerificationEnabled) {
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return LoginPasswordVerificationPage(
|
||||
srpAttributes: attr!,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await UserService.instance
|
||||
.sendOtt(context, _email!, isCreateAccountScreen: false);
|
||||
}
|
||||
FocusScope.of(context).unfocus();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_email = _config.getEmail();
|
||||
@@ -60,36 +90,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
isKeypadOpen: isKeypadOpen,
|
||||
isFormValid: _emailIsValid,
|
||||
buttonText: context.l10n.logInLabel,
|
||||
onPressedFunction: () async {
|
||||
await UserService.instance.setEmail(_email!);
|
||||
Configuration.instance.resetVolatilePassword();
|
||||
SrpAttributes? attr;
|
||||
bool isEmailVerificationEnabled = true;
|
||||
try {
|
||||
attr = await UserService.instance.getSrpAttributes(_email!);
|
||||
isEmailVerificationEnabled = attr.isEmailMFAEnabled;
|
||||
} catch (e) {
|
||||
if (e is! SrpSetupNotCompleteError) {
|
||||
_logger.severe('Error getting SRP attributes', e);
|
||||
}
|
||||
}
|
||||
if (attr != null && !isEmailVerificationEnabled) {
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return LoginPasswordVerificationPage(
|
||||
srpAttributes: attr!,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await UserService.instance
|
||||
.sendOtt(context, _email!, isCreateAccountScreen: false);
|
||||
}
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
onPressedFunction: onPressed,
|
||||
),
|
||||
floatingActionButtonLocation: fabLocation(),
|
||||
floatingActionButtonAnimator: NoScalingAnimation(),
|
||||
@@ -116,6 +117,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
|
||||
child: TextFormField(
|
||||
autofillHints: const [AutofillHints.email],
|
||||
onFieldSubmitted:
|
||||
_emailIsValid ? (value) => onPressed() : null,
|
||||
decoration: InputDecoration(
|
||||
fillColor: _emailInputFieldColor,
|
||||
filled: true,
|
||||
@@ -179,15 +182,10 @@ class _LoginPageState extends State<LoginPage> {
|
||||
tags: {
|
||||
'u-terms': StyledTextActionTag(
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.termsOfServicesTitle,
|
||||
"https://ente.io/terms",
|
||||
);
|
||||
},
|
||||
),
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.termsOfServicesTitle,
|
||||
"https://ente.io/terms",
|
||||
),
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
@@ -195,15 +193,10 @@ class _LoginPageState extends State<LoginPage> {
|
||||
),
|
||||
'u-policy': StyledTextActionTag(
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.privacyPolicyTitle,
|
||||
"https://ente.io/privacy",
|
||||
);
|
||||
},
|
||||
),
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.privacyPolicyTitle,
|
||||
"https://ente.io/privacy",
|
||||
),
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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";
|
||||
@@ -9,6 +8,7 @@ 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:ente_crypto_dart/ente_crypto_dart.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:logging/logging.dart";
|
||||
|
||||
@@ -19,8 +19,7 @@ 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({super.key, required this.srpAttributes});
|
||||
|
||||
@override
|
||||
State<LoginPasswordVerificationPage> createState() =>
|
||||
@@ -36,6 +35,11 @@ class _LoginPasswordVerificationPageState
|
||||
bool _passwordInFocus = false;
|
||||
bool _passwordVisible = false;
|
||||
|
||||
Future<void> onPressed() async {
|
||||
FocusScope.of(context).unfocus();
|
||||
await verifyPassword(context, _passwordController.text);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -77,10 +81,7 @@ class _LoginPasswordVerificationPageState
|
||||
isKeypadOpen: isKeypadOpen,
|
||||
isFormValid: _passwordController.text.isNotEmpty,
|
||||
buttonText: context.l10n.logInLabel,
|
||||
onPressedFunction: () async {
|
||||
FocusScope.of(context).unfocus();
|
||||
await verifyPassword(context, _passwordController.text);
|
||||
},
|
||||
onPressedFunction: onPressed,
|
||||
),
|
||||
floatingActionButtonLocation: fabLocation(),
|
||||
floatingActionButtonAnimator: NoScalingAnimation(),
|
||||
@@ -101,7 +102,7 @@ class _LoginPasswordVerificationPageState
|
||||
password,
|
||||
dialog,
|
||||
);
|
||||
} on DioError catch (e, s) {
|
||||
} on DioException catch (e, s) {
|
||||
await dialog.hide();
|
||||
if (e.response != null && e.response!.statusCode == 401) {
|
||||
_logger.severe('server reject, failed verify SRP login', e, s);
|
||||
@@ -112,7 +113,7 @@ class _LoginPasswordVerificationPageState
|
||||
);
|
||||
} else {
|
||||
_logger.severe('API failure during SRP login', e, s);
|
||||
if (e.type == DioErrorType.other) {
|
||||
if (e.type == DioExceptionType.unknown) {
|
||||
await _showContactSupportDialog(
|
||||
context,
|
||||
context.l10n.noInternetConnection,
|
||||
@@ -229,6 +230,9 @@ class _LoginPasswordVerificationPageState
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
|
||||
child: TextFormField(
|
||||
onFieldSubmitted: _passwordController.text.isNotEmpty
|
||||
? (_) => onPressed()
|
||||
: null,
|
||||
key: const ValueKey("passwordInputField"),
|
||||
autofillHints: const [AutofillHints.password],
|
||||
decoration: InputDecoration(
|
||||
|
||||
@@ -17,8 +17,8 @@ class OTTVerificationPage extends StatefulWidget {
|
||||
this.isChangeEmail = false,
|
||||
this.isCreateAccountScreen = false,
|
||||
this.isResetPasswordScreen = false,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<OTTVerificationPage> createState() => _OTTVerificationPageState();
|
||||
@@ -27,6 +27,23 @@ class OTTVerificationPage extends StatefulWidget {
|
||||
class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
||||
final _verificationCodeController = TextEditingController();
|
||||
|
||||
Future<void> onPressed() async {
|
||||
if (widget.isChangeEmail) {
|
||||
await UserService.instance.changeEmail(
|
||||
context,
|
||||
widget.email,
|
||||
_verificationCodeController.text,
|
||||
);
|
||||
} else {
|
||||
await UserService.instance.verifyEmail(
|
||||
context,
|
||||
_verificationCodeController.text,
|
||||
isResettingPasswordScreen: widget.isResetPasswordScreen,
|
||||
);
|
||||
}
|
||||
FocusScope.of(context).unfocus();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
@@ -68,22 +85,9 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
||||
body: _getBody(),
|
||||
floatingActionButton: DynamicFAB(
|
||||
isKeypadOpen: isKeypadOpen,
|
||||
isFormValid: !(_verificationCodeController.text.isEmpty),
|
||||
isFormValid: _verificationCodeController.text.isNotEmpty,
|
||||
buttonText: l10n.verify,
|
||||
onPressedFunction: () {
|
||||
if (widget.isChangeEmail) {
|
||||
UserService.instance.changeEmail(
|
||||
context,
|
||||
widget.email,
|
||||
_verificationCodeController.text,
|
||||
);
|
||||
} else {
|
||||
UserService.instance
|
||||
.verifyEmail(context, _verificationCodeController.text,
|
||||
isResettingPasswordScreen: widget.isResetPasswordScreen,);
|
||||
}
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
onPressedFunction: onPressed,
|
||||
),
|
||||
floatingActionButtonLocation: fabLocation(),
|
||||
floatingActionButtonAnimator: NoScalingAnimation(),
|
||||
@@ -160,6 +164,9 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
||||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 16),
|
||||
child: TextFormField(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
onFieldSubmitted: _verificationCodeController.text.isNotEmpty
|
||||
? (_) => onPressed()
|
||||
: null,
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
hintText: l10n.tapToEnterCode,
|
||||
|
||||
@@ -4,11 +4,11 @@ import 'package:ente_auth/models/key_gen_result.dart';
|
||||
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';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -25,7 +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, super.key});
|
||||
|
||||
@override
|
||||
State<PasswordEntryPage> createState() => _PasswordEntryPageState();
|
||||
@@ -149,227 +149,239 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: AutofillGroup(
|
||||
child: ListView(
|
||||
children: [
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||
child: Text(
|
||||
buttonTextAndHeading,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
child: FocusTraversalGroup(
|
||||
policy: OrderedTraversalPolicy(),
|
||||
child: ListView(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 30,
|
||||
horizontal: 20,
|
||||
),
|
||||
child: Text(
|
||||
buttonTextAndHeading,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
widget.mode == PasswordEntryMode.set
|
||||
? context.l10n.enterPasswordToEncrypt
|
||||
: context.l10n.enterNewPasswordToEncrypt,
|
||||
textAlign: TextAlign.start,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(fontSize: 14),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
widget.mode == PasswordEntryMode.set
|
||||
? context.l10n.enterPasswordToEncrypt
|
||||
: context.l10n.enterNewPasswordToEncrypt,
|
||||
textAlign: TextAlign.start,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(fontSize: 14),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(8)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: StyledText(
|
||||
text: context.l10n.passwordWarning,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(fontSize: 14),
|
||||
tags: {
|
||||
'underline': StyledTextTag(
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
const Padding(padding: EdgeInsets.all(8)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: StyledText(
|
||||
text: context.l10n.passwordWarning,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(fontSize: 14),
|
||||
tags: {
|
||||
'underline': StyledTextTag(
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(12)),
|
||||
Visibility(
|
||||
// hidden textForm for suggesting auto-fill service for saving
|
||||
// password
|
||||
visible: false,
|
||||
child: TextFormField(
|
||||
autofillHints: const [
|
||||
AutofillHints.email,
|
||||
],
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
initialValue: email,
|
||||
textInputAction: TextInputAction.next,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||
child: TextFormField(
|
||||
autofillHints: const [AutofillHints.newPassword],
|
||||
onFieldSubmitted: (_) {
|
||||
do {
|
||||
FocusScope.of(context).nextFocus();
|
||||
} while (FocusScope.of(context).focusedChild!.context ==
|
||||
null);
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
fillColor:
|
||||
_isPasswordValid ? _validFieldValueColor : null,
|
||||
filled: true,
|
||||
hintText: context.l10n.password,
|
||||
contentPadding: const EdgeInsets.all(20),
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
suffixIcon: _password1InFocus
|
||||
? IconButton(
|
||||
icon: Icon(
|
||||
_password1Visible
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_password1Visible = !_password1Visible;
|
||||
});
|
||||
},
|
||||
)
|
||||
: _isPasswordValid
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Theme.of(context)
|
||||
.inputDecorationTheme
|
||||
.focusedBorder!
|
||||
.borderSide
|
||||
.color,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(12)),
|
||||
Visibility(
|
||||
// hidden textForm for suggesting auto-fill service for saving
|
||||
// password
|
||||
visible: false,
|
||||
child: TextFormField(
|
||||
autofillHints: const [
|
||||
AutofillHints.email,
|
||||
],
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
initialValue: email,
|
||||
textInputAction: TextInputAction.next,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||
child: TextFormField(
|
||||
autofillHints: const [AutofillHints.newPassword],
|
||||
decoration: InputDecoration(
|
||||
fillColor:
|
||||
_isPasswordValid ? _validFieldValueColor : null,
|
||||
filled: true,
|
||||
hintText: context.l10n.password,
|
||||
contentPadding: const EdgeInsets.all(20),
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
suffixIcon: _password1InFocus
|
||||
? IconButton(
|
||||
icon: Icon(
|
||||
_password1Visible
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_password1Visible = !_password1Visible;
|
||||
});
|
||||
},
|
||||
)
|
||||
: _isPasswordValid
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Theme.of(context)
|
||||
.inputDecorationTheme
|
||||
.focusedBorder!
|
||||
.borderSide
|
||||
.color,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
obscureText: !_password1Visible,
|
||||
controller: _passwordController1,
|
||||
autofocus: false,
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
onChanged: (password) {
|
||||
setState(() {
|
||||
_passwordInInputBox = password;
|
||||
_passwordStrength = estimatePasswordStrength(password);
|
||||
_isPasswordValid =
|
||||
_passwordStrength >= kMildPasswordStrengthThreshold;
|
||||
_passwordsMatch = _passwordInInputBox ==
|
||||
_passwordInInputConfirmationBox;
|
||||
});
|
||||
},
|
||||
textInputAction: TextInputAction.next,
|
||||
focusNode: _password1FocusNode,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||
child: TextFormField(
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller: _passwordController2,
|
||||
obscureText: !_password2Visible,
|
||||
autofillHints: const [AutofillHints.newPassword],
|
||||
onEditingComplete: () => TextInput.finishAutofillContext(),
|
||||
decoration: InputDecoration(
|
||||
fillColor: _passwordsMatch ? _validFieldValueColor : null,
|
||||
filled: true,
|
||||
hintText: context.l10n.confirmPassword,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 20,
|
||||
),
|
||||
suffixIcon: _password2InFocus
|
||||
? IconButton(
|
||||
icon: Icon(
|
||||
_password2Visible
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_password2Visible = !_password2Visible;
|
||||
});
|
||||
},
|
||||
)
|
||||
: _passwordsMatch
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Theme.of(context)
|
||||
.inputDecorationTheme
|
||||
.focusedBorder!
|
||||
.borderSide
|
||||
.color,
|
||||
)
|
||||
: null,
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
),
|
||||
focusNode: _password2FocusNode,
|
||||
onChanged: (cnfPassword) {
|
||||
setState(() {
|
||||
_passwordInInputConfirmationBox = cnfPassword;
|
||||
if (_passwordInInputBox != '') {
|
||||
obscureText: !_password1Visible,
|
||||
controller: _passwordController1,
|
||||
autofocus: false,
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
onChanged: (password) {
|
||||
setState(() {
|
||||
_passwordInInputBox = password;
|
||||
_passwordStrength =
|
||||
estimatePasswordStrength(password);
|
||||
_isPasswordValid = _passwordStrength >=
|
||||
kMildPasswordStrengthThreshold;
|
||||
_passwordsMatch = _passwordInInputBox ==
|
||||
_passwordInInputConfirmationBox;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
Opacity(
|
||||
opacity:
|
||||
(_passwordInInputBox != '') && _password1InFocus ? 1 : 0,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
||||
child: Text(
|
||||
context.l10n.passwordStrength(passwordStrengthText),
|
||||
style: TextStyle(
|
||||
color: passwordStrengthColor,
|
||||
),
|
||||
});
|
||||
},
|
||||
textInputAction: TextInputAction.next,
|
||||
focusNode: _password1FocusNode,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.howItWorks,
|
||||
"https://ente.io/architecture",
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: context.l10n.howItWorks,
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||
child: TextFormField(
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller: _passwordController2,
|
||||
obscureText: !_password2Visible,
|
||||
autofillHints: const [AutofillHints.newPassword],
|
||||
onEditingComplete: () =>
|
||||
TextInput.finishAutofillContext(),
|
||||
decoration: InputDecoration(
|
||||
fillColor:
|
||||
_passwordsMatch ? _validFieldValueColor : null,
|
||||
filled: true,
|
||||
hintText: context.l10n.confirmPassword,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 20,
|
||||
),
|
||||
suffixIcon: _password2InFocus
|
||||
? IconButton(
|
||||
icon: Icon(
|
||||
_password2Visible
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_password2Visible = !_password2Visible;
|
||||
});
|
||||
},
|
||||
)
|
||||
: _passwordsMatch
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Theme.of(context)
|
||||
.inputDecorationTheme
|
||||
.focusedBorder!
|
||||
.borderSide
|
||||
.color,
|
||||
)
|
||||
: null,
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
),
|
||||
focusNode: _password2FocusNode,
|
||||
onChanged: (cnfPassword) {
|
||||
setState(() {
|
||||
_passwordInInputConfirmationBox = cnfPassword;
|
||||
if (_passwordInInputBox != '') {
|
||||
_passwordsMatch = _passwordInInputBox ==
|
||||
_passwordInInputConfirmationBox;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
Opacity(
|
||||
opacity: (_passwordInInputBox != '') && _password1InFocus
|
||||
? 1
|
||||
: 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Text(
|
||||
context.l10n.passwordStrength(passwordStrengthText),
|
||||
style: TextStyle(
|
||||
color: passwordStrengthColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(20)),
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.howItWorks,
|
||||
"https://ente.io/architecture",
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: context.l10n.howItWorks,
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(20)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -463,6 +475,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
showGenericErrorDialog(context: context);
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(
|
||||
context,
|
||||
|
||||
@@ -9,14 +9,14 @@ import 'package:ente_auth/ui/account/recovery_page.dart';
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/ui/home_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_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class PasswordReentryPage extends StatefulWidget {
|
||||
const PasswordReentryPage({Key? key}) : super(key: key);
|
||||
const PasswordReentryPage({super.key});
|
||||
|
||||
@override
|
||||
State<PasswordReentryPage> createState() => _PasswordReentryPageState();
|
||||
@@ -261,8 +261,8 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.spaceBetween,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
@@ -275,13 +275,17 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
context.l10n.forgotPassword,
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
context.l10n.forgotPassword,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
@@ -297,13 +301,17 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||
Navigator.of(context)
|
||||
.popUntil((route) => route.isFirst);
|
||||
},
|
||||
child: Text(
|
||||
context.l10n.changeEmail,
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
context.l10n.changeEmail,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io' as io;
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
@@ -7,7 +8,10 @@ import 'package:ente_auth/core/constants.dart';
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/ui/common/gradient_button.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/share_utils.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:share_plus/share_plus.dart';
|
||||
@@ -27,7 +31,7 @@ class RecoveryKeyPage extends StatefulWidget {
|
||||
const RecoveryKeyPage(
|
||||
this.recoveryKey,
|
||||
this.doneText, {
|
||||
Key? key,
|
||||
super.key,
|
||||
this.showAppBar,
|
||||
this.onDone,
|
||||
this.isDismissible,
|
||||
@@ -35,7 +39,7 @@ class RecoveryKeyPage extends StatefulWidget {
|
||||
this.text,
|
||||
this.subText,
|
||||
this.showProgressBar = false,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
State<RecoveryKeyPage> createState() => _RecoveryKeyPageState();
|
||||
@@ -44,7 +48,7 @@ class RecoveryKeyPage extends StatefulWidget {
|
||||
class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
bool _hasTriedToSave = false;
|
||||
final _recoveryKeyFile = io.File(
|
||||
Configuration.instance.getTempDirectory() + "ente-recovery-key.txt",
|
||||
"${Configuration.instance.getTempDirectory()}ente-recovery-key.txt",
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -61,6 +65,21 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
? 32
|
||||
: 120;
|
||||
|
||||
Future<void> copy() async {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(
|
||||
text: recoveryKey,
|
||||
),
|
||||
);
|
||||
showShortToast(
|
||||
context,
|
||||
context.l10n.recoveryKeyCopiedToClipboard,
|
||||
);
|
||||
setState(() {
|
||||
_hasTriedToSave = true;
|
||||
});
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: widget.showProgressBar
|
||||
? AppBar(
|
||||
@@ -113,62 +132,73 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const Padding(padding: EdgeInsets.only(top: 24)),
|
||||
DottedBorder(
|
||||
color: const Color.fromARGB(255, 105, 17, 127),
|
||||
//color of dotted/dash line
|
||||
strokeWidth: 1,
|
||||
//thickness of dash/dots
|
||||
dashPattern: const [6, 6],
|
||||
radius: const Radius.circular(8),
|
||||
//dash patterns, 10 is dash width, 6 is space width
|
||||
child: SizedBox(
|
||||
//inner container
|
||||
// height: 120, //height of inner container
|
||||
width: double
|
||||
.infinity, //width to 100% match to parent container.
|
||||
// ignore: prefer_const_literals_to_create_immutables
|
||||
child: Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(text: recoveryKey),
|
||||
);
|
||||
showShortToast(
|
||||
context,
|
||||
context.l10n.recoveryKeyCopiedToClipboard,
|
||||
);
|
||||
setState(() {
|
||||
_hasTriedToSave = true;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: const Color.fromRGBO(
|
||||
49,
|
||||
155,
|
||||
86,
|
||||
.2,
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(1),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Color(0x8E9610D6),
|
||||
Color(0x8E9F4FC6),
|
||||
],
|
||||
stops: [0.0, 0.9753],
|
||||
),
|
||||
),
|
||||
child: DottedBorder(
|
||||
padding: EdgeInsets.zero,
|
||||
borderType: BorderType.RRect,
|
||||
strokeWidth: 1,
|
||||
color: const Color(0xFF6B6B6B),
|
||||
dashPattern: const [6, 6],
|
||||
radius: const Radius.circular(8),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Builder(
|
||||
builder: (context) {
|
||||
final content = Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
width: double.infinity,
|
||||
child: Text(
|
||||
recoveryKey,
|
||||
textAlign: TextAlign.justify,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge,
|
||||
),
|
||||
);
|
||||
|
||||
if (PlatformUtil.isMobile()) {
|
||||
return GestureDetector(
|
||||
onTap: () async => await copy(),
|
||||
child: content,
|
||||
);
|
||||
} else {
|
||||
return SelectableRegion(
|
||||
focusNode: FocusNode(),
|
||||
selectionControls:
|
||||
PlatformUtil.selectionControls,
|
||||
child: content,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(2),
|
||||
),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.recoveryKeyBoxColor,
|
||||
),
|
||||
padding: const EdgeInsets.all(20),
|
||||
width: double.infinity,
|
||||
child: Text(
|
||||
recoveryKey,
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyLarge,
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
child: PlatformCopy(
|
||||
onPressed: copy,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -193,7 +223,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
),
|
||||
),
|
||||
],
|
||||
), // columnEnds
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -207,12 +237,15 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
final List<Widget> childrens = [];
|
||||
if (!_hasTriedToSave) {
|
||||
childrens.add(
|
||||
ElevatedButton(
|
||||
style: Theme.of(context).colorScheme.optionalActionButtonStyle,
|
||||
onPressed: () async {
|
||||
await _saveKeys();
|
||||
},
|
||||
child: Text(context.l10n.doThisLater),
|
||||
SizedBox(
|
||||
height: 56,
|
||||
child: ElevatedButton(
|
||||
style: Theme.of(context).colorScheme.optionalActionButtonStyle,
|
||||
onPressed: () async {
|
||||
await _saveKeys();
|
||||
},
|
||||
child: Text(context.l10n.doThisLater),
|
||||
),
|
||||
),
|
||||
);
|
||||
childrens.add(const SizedBox(height: 10));
|
||||
@@ -221,19 +254,32 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
childrens.add(
|
||||
GradientButton(
|
||||
onTap: () async {
|
||||
await _shareRecoveryKey(recoveryKey);
|
||||
await shareDialog(
|
||||
context,
|
||||
context.l10n.recoveryKey,
|
||||
saveAction: () async {
|
||||
await _saveRecoveryKey(recoveryKey);
|
||||
},
|
||||
sendAction: () async {
|
||||
await _shareRecoveryKey(recoveryKey);
|
||||
},
|
||||
);
|
||||
},
|
||||
text: context.l10n.saveKey,
|
||||
),
|
||||
);
|
||||
|
||||
if (_hasTriedToSave) {
|
||||
childrens.add(const SizedBox(height: 10));
|
||||
childrens.add(
|
||||
ElevatedButton(
|
||||
child: Text(widget.doneText),
|
||||
onPressed: () async {
|
||||
await _saveKeys();
|
||||
},
|
||||
SizedBox(
|
||||
height: 56,
|
||||
child: ElevatedButton(
|
||||
child: Text(widget.doneText),
|
||||
onPressed: () async {
|
||||
await _saveKeys();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -241,11 +287,34 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
return childrens;
|
||||
}
|
||||
|
||||
Future _saveRecoveryKey(String recoveryKey) async {
|
||||
final bytes = utf8.encode(recoveryKey);
|
||||
final time = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
await PlatformUtil.shareFile(
|
||||
"ente_recovery_key_$time",
|
||||
"txt",
|
||||
bytes,
|
||||
MimeType.text,
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
showToast(
|
||||
context,
|
||||
context.l10n.recoveryKeySaved,
|
||||
);
|
||||
setState(() {
|
||||
_hasTriedToSave = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future _shareRecoveryKey(String recoveryKey) async {
|
||||
if (_recoveryKeyFile.existsSync()) {
|
||||
await _recoveryKeyFile.delete();
|
||||
}
|
||||
_recoveryKeyFile.writeAsStringSync(recoveryKey);
|
||||
// ignore: deprecated_member_use
|
||||
await Share.shareFiles([_recoveryKeyFile.path]);
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (mounted) {
|
||||
@@ -264,3 +333,24 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
widget.onDone!();
|
||||
}
|
||||
}
|
||||
|
||||
class PlatformCopy extends StatelessWidget {
|
||||
const PlatformCopy({
|
||||
super.key,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
final void Function() onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
onPressed: () => onPressed(),
|
||||
visualDensity: VisualDensity.compact,
|
||||
icon: const Icon(
|
||||
Icons.copy,
|
||||
size: 16,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/ui/account/password_entry_page.dart';
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
@@ -10,7 +7,7 @@ import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RecoveryPage extends StatefulWidget {
|
||||
const RecoveryPage({Key? key}) : super(key: key);
|
||||
const RecoveryPage({super.key});
|
||||
|
||||
@override
|
||||
State<RecoveryPage> createState() => _RecoveryPageState();
|
||||
@@ -19,6 +16,36 @@ class RecoveryPage extends StatefulWidget {
|
||||
class _RecoveryPageState extends State<RecoveryPage> {
|
||||
final _recoveryKey = TextEditingController();
|
||||
|
||||
Future<void> onPressed() async {
|
||||
FocusScope.of(context).unfocus();
|
||||
final dialog = createProgressDialog(context, "Decrypting...");
|
||||
await dialog.show();
|
||||
try {
|
||||
await Configuration.instance.recover(_recoveryKey.text.trim());
|
||||
await dialog.hide();
|
||||
showToast(context, "Recovery successful!");
|
||||
await Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return const PopScope(
|
||||
canPop: false,
|
||||
child: PasswordEntryPage(
|
||||
mode: PasswordEntryMode.reset,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
String errMessage = 'The recovery key you entered is incorrect';
|
||||
if (e is AssertionError) {
|
||||
errMessage = '$errMessage : ${e.message}';
|
||||
}
|
||||
await showErrorDialog(context, "Incorrect recovery key", errMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
|
||||
@@ -46,37 +73,7 @@ class _RecoveryPageState extends State<RecoveryPage> {
|
||||
isKeypadOpen: isKeypadOpen,
|
||||
isFormValid: _recoveryKey.text.isNotEmpty,
|
||||
buttonText: 'Recover',
|
||||
onPressedFunction: () async {
|
||||
FocusScope.of(context).unfocus();
|
||||
final dialog = createProgressDialog(context, "Decrypting...");
|
||||
await dialog.show();
|
||||
try {
|
||||
await Configuration.instance.recover(_recoveryKey.text.trim());
|
||||
await dialog.hide();
|
||||
showToast(context, "Recovery successful!");
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: const PasswordEntryPage(
|
||||
mode: PasswordEntryMode.reset,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
String errMessage = 'The recovery key you entered is incorrect';
|
||||
if (e is AssertionError) {
|
||||
errMessage = '$errMessage : ${e.message}';
|
||||
}
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(context, "Incorrect recovery key", errMessage);
|
||||
}
|
||||
},
|
||||
onPressedFunction: onPressed,
|
||||
),
|
||||
floatingActionButtonLocation: fabLocation(),
|
||||
floatingActionButtonAnimator: NoScalingAnimation(),
|
||||
@@ -89,7 +86,7 @@ class _RecoveryPageState extends State<RecoveryPage> {
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||
child: Text(
|
||||
'Forgot password',
|
||||
context.l10n.forgotPassword,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
),
|
||||
@@ -140,12 +137,14 @@ class _RecoveryPageState extends State<RecoveryPage> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Center(
|
||||
child: Text(
|
||||
"No recovery key?",
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
context.l10n.noRecoveryKeyTitle,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -5,10 +5,9 @@ import 'package:ente_auth/core/configuration.dart';
|
||||
import "package:ente_auth/l10n/l10n.dart";
|
||||
import "package:ente_auth/theme/ente_theme.dart";
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import "package:ente_auth/utils/crypto_util.dart";
|
||||
import "package:ente_auth/utils/dialog_util.dart";
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:flutter_sodium/flutter_sodium.dart";
|
||||
import "package:logging/logging.dart";
|
||||
|
||||
typedef OnPasswordVerifiedFn = Future<void> Function(Uint8List bytes);
|
||||
@@ -17,8 +16,11 @@ class RequestPasswordVerificationPage extends StatefulWidget {
|
||||
final OnPasswordVerifiedFn onPasswordVerified;
|
||||
final Function? onPasswordError;
|
||||
|
||||
const RequestPasswordVerificationPage(
|
||||
{super.key, required this.onPasswordVerified, this.onPasswordError,});
|
||||
const RequestPasswordVerificationPage({
|
||||
super.key,
|
||||
required this.onPasswordVerified,
|
||||
this.onPasswordError,
|
||||
});
|
||||
|
||||
@override
|
||||
State<RequestPasswordVerificationPage> createState() =>
|
||||
@@ -82,15 +84,15 @@ class _RequestPasswordVerificationPageState
|
||||
try {
|
||||
final attributes = Configuration.instance.getKeyAttributes()!;
|
||||
final Uint8List keyEncryptionKey = await CryptoUtil.deriveKey(
|
||||
utf8.encode(_passwordController.text) as Uint8List,
|
||||
Sodium.base642bin(attributes.kekSalt),
|
||||
utf8.encode(_passwordController.text),
|
||||
CryptoUtil.base642bin(attributes.kekSalt),
|
||||
attributes.memLimit,
|
||||
attributes.opsLimit,
|
||||
);
|
||||
CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(attributes.encryptedKey),
|
||||
CryptoUtil.base642bin(attributes.encryptedKey),
|
||||
keyEncryptionKey,
|
||||
Sodium.base642bin(attributes.keyDecryptionNonce),
|
||||
CryptoUtil.base642bin(attributes.keyDecryptionNonce),
|
||||
);
|
||||
await dialog.show();
|
||||
// pop
|
||||
|
||||
@@ -11,7 +11,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class SessionsPage extends StatefulWidget {
|
||||
const SessionsPage({Key? key}) : super(key: key);
|
||||
const SessionsPage({super.key});
|
||||
|
||||
@override
|
||||
State<SessionsPage> createState() => _SessionsPageState();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
@@ -12,12 +10,13 @@ import 'package:ente_auth/ui/common/gradient_button.dart';
|
||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class VerifyRecoveryPage extends StatefulWidget {
|
||||
const VerifyRecoveryPage({Key? key}) : super(key: key);
|
||||
const VerifyRecoveryPage({super.key});
|
||||
|
||||
@override
|
||||
State<VerifyRecoveryPage> createState() => _VerifyRecoveryPageState();
|
||||
@@ -34,14 +33,14 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
|
||||
try {
|
||||
final String inputKey = _recoveryKey.text.trim();
|
||||
final String recoveryKey =
|
||||
Sodium.bin2hex(Configuration.instance.getRecoveryKey());
|
||||
CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey());
|
||||
final String recoveryKeyWords = bip39.entropyToMnemonic(recoveryKey);
|
||||
if (inputKey == recoveryKey || inputKey == recoveryKeyWords) {
|
||||
try {
|
||||
await UserRemoteFlagService.instance.markRecoveryVerificationAsDone();
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
if (e is DioError && e.type == DioErrorType.other) {
|
||||
if (e is DioException && e.type == DioExceptionType.unknown) {
|
||||
await showErrorDialog(
|
||||
context,
|
||||
"No internet connection",
|
||||
@@ -88,12 +87,14 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
|
||||
context,
|
||||
"Please authenticate to view your recovery key",
|
||||
);
|
||||
await PlatformUtil.refocusWindows();
|
||||
|
||||
if (hasAuthenticated) {
|
||||
String recoveryKey;
|
||||
try {
|
||||
recoveryKey = Sodium.bin2hex(Configuration.instance.getRecoveryKey());
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(
|
||||
recoveryKey =
|
||||
CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey());
|
||||
await routeToPage(
|
||||
context,
|
||||
RecoveryKeyPage(
|
||||
recoveryKey,
|
||||
|
||||
@@ -6,12 +6,12 @@ class CodeTimerProgress extends StatefulWidget {
|
||||
final int period;
|
||||
|
||||
CodeTimerProgress({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.period,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
_CodeTimerProgressState createState() => _CodeTimerProgressState();
|
||||
State createState() => _CodeTimerProgressState();
|
||||
}
|
||||
|
||||
class _CodeTimerProgressState extends State<CodeTimerProgress>
|
||||
|
||||
@@ -14,8 +14,10 @@ import 'package:ente_auth/store/code_store.dart';
|
||||
import 'package:ente_auth/ui/code_timer_progress.dart';
|
||||
import 'package:ente_auth/ui/utils/icon_utils.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:ente_auth/utils/totp_util.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -24,7 +26,7 @@ import 'package:move_to_background/move_to_background.dart';
|
||||
class CodeWidget extends StatefulWidget {
|
||||
final Code code;
|
||||
|
||||
const CodeWidget(this.code, {Key? key}) : super(key: key);
|
||||
const CodeWidget(this.code, {super.key});
|
||||
|
||||
@override
|
||||
State<CodeWidget> createState() => _CodeWidgetState();
|
||||
@@ -133,35 +135,59 @@ class _CodeWidgetState extends State<CodeWidget> {
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.codeCardBackgroundColor,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
customBorder: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
return RawGestureDetector(
|
||||
gestures: {
|
||||
PanGestureRecognizer:
|
||||
GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
|
||||
() => PanGestureRecognizer(
|
||||
debugOwner: this,
|
||||
// This recognizer accepts any button press made with a secondary button.
|
||||
allowedButtonsFilter: (int buttons) =>
|
||||
buttons & kSecondaryButton != 0,
|
||||
),
|
||||
(PanGestureRecognizer instance) {
|
||||
instance
|
||||
..dragStartBehavior = DragStartBehavior.down
|
||||
..onEnd = (DragEndDetails details) {
|
||||
Slidable.of(context)?.openEndActionPane();
|
||||
};
|
||||
},
|
||||
),
|
||||
},
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.codeCardBackgroundColor,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
customBorder: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
onTap: () {
|
||||
_copyCurrentOTPToClipboard();
|
||||
},
|
||||
onDoubleTap: isMaskingEnabled
|
||||
? () {
|
||||
setState(
|
||||
() {
|
||||
_hideCode = !_hideCode;
|
||||
},
|
||||
);
|
||||
}
|
||||
: null,
|
||||
onLongPress: () {
|
||||
_copyCurrentOTPToClipboard();
|
||||
},
|
||||
child: _getCardContents(l10n),
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
_copyCurrentOTPToClipboard();
|
||||
},
|
||||
onDoubleTap: isMaskingEnabled
|
||||
? () {
|
||||
setState(
|
||||
() {
|
||||
_hideCode = !_hideCode;
|
||||
},
|
||||
);
|
||||
}
|
||||
: null,
|
||||
onLongPress: () {
|
||||
_copyCurrentOTPToClipboard();
|
||||
},
|
||||
child: _getCardContents(l10n),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -373,9 +399,10 @@ class _CodeWidgetState extends State<CodeWidget> {
|
||||
}
|
||||
|
||||
Future<void> _onEditPressed(_) async {
|
||||
bool _isAuthSuccessful = await LocalAuthenticationService.instance
|
||||
bool isAuthSuccessful = await LocalAuthenticationService.instance
|
||||
.requestLocalAuthentication(context, context.l10n.editCodeAuthMessage);
|
||||
if (!_isAuthSuccessful) {
|
||||
await PlatformUtil.refocusWindows();
|
||||
if (!isAuthSuccessful) {
|
||||
return;
|
||||
}
|
||||
final Code? code = await Navigator.of(context).push(
|
||||
@@ -391,9 +418,10 @@ class _CodeWidgetState extends State<CodeWidget> {
|
||||
}
|
||||
|
||||
Future<void> _onShowQrPressed(_) async {
|
||||
bool _isAuthSuccessful = await LocalAuthenticationService.instance
|
||||
bool isAuthSuccessful = await LocalAuthenticationService.instance
|
||||
.requestLocalAuthentication(context, context.l10n.showQRAuthMessage);
|
||||
if (!_isAuthSuccessful) {
|
||||
await PlatformUtil.refocusWindows();
|
||||
if (!isAuthSuccessful) {
|
||||
return;
|
||||
}
|
||||
// ignore: unused_local_variable
|
||||
@@ -407,14 +435,15 @@ class _CodeWidgetState extends State<CodeWidget> {
|
||||
}
|
||||
|
||||
void _onDeletePressed(_) async {
|
||||
bool _isAuthSuccessful =
|
||||
bool isAuthSuccessful =
|
||||
await LocalAuthenticationService.instance.requestLocalAuthentication(
|
||||
context,
|
||||
context.l10n.deleteCodeAuthMessage,
|
||||
);
|
||||
if (!_isAuthSuccessful) {
|
||||
if (!isAuthSuccessful) {
|
||||
return;
|
||||
}
|
||||
FocusScope.of(context).requestFocus();
|
||||
final l10n = context.l10n;
|
||||
await showChoiceActionSheet(
|
||||
context,
|
||||
@@ -451,7 +480,7 @@ class _CodeWidgetState extends State<CodeWidget> {
|
||||
code = code.replaceAll(RegExp(r'\d'), '•');
|
||||
}
|
||||
if (code.length == 6) {
|
||||
return code.substring(0, 3) + " " + code.substring(3, 6);
|
||||
return "${code.substring(0, 3)} ${code.substring(3, 6)}";
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@ import 'package:flutter/material.dart';
|
||||
class BottomShadowWidget extends StatelessWidget {
|
||||
final double offsetDy;
|
||||
final Color? shadowColor;
|
||||
const BottomShadowWidget({this.offsetDy = 28, this.shadowColor, Key? key})
|
||||
: super(key: key);
|
||||
const BottomShadowWidget({this.offsetDy = 28, this.shadowColor, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DividerWithPadding extends StatelessWidget {
|
||||
final double left, top, right, bottom, thinckness;
|
||||
const DividerWithPadding({
|
||||
Key? key,
|
||||
super.key,
|
||||
this.left = 0,
|
||||
this.top = 0,
|
||||
this.right = 0,
|
||||
this.bottom = 0,
|
||||
this.thinckness = 0.5,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -10,12 +10,12 @@ class DynamicFAB extends StatelessWidget {
|
||||
final Function? onPressedFunction;
|
||||
|
||||
const DynamicFAB({
|
||||
Key? key,
|
||||
super.key,
|
||||
this.isKeypadOpen,
|
||||
this.buttonText,
|
||||
this.isFormValid,
|
||||
this.onPressedFunction,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -60,6 +60,7 @@ class DynamicFAB extends StatelessWidget {
|
||||
} else {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
height: 56,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: OutlinedButton(
|
||||
onPressed:
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class GradientButton extends StatelessWidget {
|
||||
@@ -15,17 +13,21 @@ class GradientButton extends StatelessWidget {
|
||||
// padding between the text and icon
|
||||
final double paddingValue;
|
||||
|
||||
// used when two icons are in row
|
||||
final bool reversedGradient;
|
||||
|
||||
const GradientButton({
|
||||
Key? key,
|
||||
super.key,
|
||||
this.linearGradientColors = const [
|
||||
Color.fromARGB(255, 133, 44, 210),
|
||||
Color.fromARGB(255, 187, 26, 93),
|
||||
],
|
||||
this.reversedGradient = false,
|
||||
this.onTap,
|
||||
this.text = '',
|
||||
this.iconData,
|
||||
this.paddingValue = 0.0,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -71,7 +73,9 @@ class GradientButton extends StatelessWidget {
|
||||
gradient: LinearGradient(
|
||||
begin: const Alignment(0.1, -0.9),
|
||||
end: const Alignment(-0.6, 0.9),
|
||||
colors: linearGradientColors,
|
||||
colors: reversedGradient
|
||||
? linearGradientColors.reversed.toList()
|
||||
: linearGradientColors,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
|
||||
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LinearProgressDialog extends StatefulWidget {
|
||||
final String message;
|
||||
|
||||
const LinearProgressDialog(this.message, {Key? key}) : super(key: key);
|
||||
const LinearProgressDialog(this.message, {super.key});
|
||||
|
||||
@override
|
||||
LinearProgressDialogState createState() => LinearProgressDialogState();
|
||||
@@ -29,8 +27,8 @@ class LinearProgressDialogState extends State<LinearProgressDialog> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
child: AlertDialog(
|
||||
title: Text(
|
||||
widget.message,
|
||||
|
||||
@@ -11,8 +11,8 @@ class EnteLoadingWidget extends StatelessWidget {
|
||||
this.size = 14,
|
||||
this.padding = 5,
|
||||
this.alignment = Alignment.center,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -153,8 +153,8 @@ class ProgressDialog {
|
||||
barrierColor: _barrierColor,
|
||||
builder: (BuildContext context) {
|
||||
_dismissingContext = context;
|
||||
return WillPopScope(
|
||||
onWillPop: () async => _barrierDismissible,
|
||||
return PopScope(
|
||||
canPop: _barrierDismissible,
|
||||
child: Dialog(
|
||||
backgroundColor: _backgroundColor,
|
||||
insetAnimationCurve: _insetAnimCurve,
|
||||
@@ -198,6 +198,7 @@ class _Body extends StatefulWidget {
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
// ignore: no_logic_in_create_state
|
||||
return _dialog;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,7 @@ class RenameDialog extends StatefulWidget {
|
||||
final String type;
|
||||
final int maxLength;
|
||||
|
||||
const RenameDialog(this.name, this.type, {Key? key, this.maxLength = 100})
|
||||
: super(key: key);
|
||||
const RenameDialog(this.name, this.type, {super.key, this.maxLength = 100});
|
||||
|
||||
@override
|
||||
State<RenameDialog> createState() => _RenameDialogState();
|
||||
|
||||
@@ -6,7 +6,7 @@ class WebPage extends StatefulWidget {
|
||||
final String title;
|
||||
final String url;
|
||||
|
||||
const WebPage(this.title, this.url, {Key? key}) : super(key: key);
|
||||
const WebPage(this.title, this.url, {super.key});
|
||||
|
||||
@override
|
||||
State<WebPage> createState() => _WebPageState();
|
||||
@@ -28,9 +28,9 @@ class _WebPageState extends State<WebPage> {
|
||||
),
|
||||
backgroundColor: Colors.black,
|
||||
body: InAppWebView(
|
||||
initialUrlRequest: URLRequest(url: Uri.parse(widget.url)),
|
||||
initialOptions: InAppWebViewGroupOptions(
|
||||
crossPlatform: InAppWebViewOptions(transparentBackground: true),
|
||||
initialUrlRequest: URLRequest(url: WebUri(widget.url)),
|
||||
initialSettings: InAppWebViewSettings(
|
||||
transparentBackground: true,
|
||||
),
|
||||
onLoadStop: (c, url) {
|
||||
setState(() {
|
||||
|
||||
@@ -57,7 +57,7 @@ class ButtonWidget extends StatelessWidget {
|
||||
final ValueNotifier<String>? progressStatus;
|
||||
|
||||
const ButtonWidget({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.buttonType,
|
||||
this.buttonSize = ButtonSize.large,
|
||||
this.icon,
|
||||
@@ -71,7 +71,7 @@ class ButtonWidget extends StatelessWidget {
|
||||
this.shouldSurfaceExecutionStates = true,
|
||||
this.progressStatus,
|
||||
this.shouldShowSuccessConfirmation = false,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -155,7 +155,7 @@ class ButtonChildWidget extends StatefulWidget {
|
||||
final bool shouldShowSuccessConfirmation;
|
||||
|
||||
const ButtonChildWidget({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.buttonStyle,
|
||||
required this.buttonType,
|
||||
required this.isDisabled,
|
||||
@@ -168,7 +168,7 @@ class ButtonChildWidget extends StatefulWidget {
|
||||
this.labelText,
|
||||
this.icon,
|
||||
this.buttonAction,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
State<ButtonChildWidget> createState() => _ButtonChildWidgetState();
|
||||
|
||||
@@ -17,7 +17,7 @@ class IconButtonWidget extends StatefulWidget {
|
||||
final Color? pressedColor;
|
||||
final Color? iconColor;
|
||||
const IconButtonWidget({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.iconButtonType,
|
||||
this.disableGestureDetector = false,
|
||||
@@ -25,7 +25,7 @@ class IconButtonWidget extends StatefulWidget {
|
||||
this.defaultColor,
|
||||
this.pressedColor,
|
||||
this.iconColor,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
State<IconButtonWidget> createState() => _IconButtonWidgetState();
|
||||
|
||||
@@ -13,8 +13,8 @@ class CaptionedTextWidget extends StatelessWidget {
|
||||
this.textStyle,
|
||||
this.makeTextBold = false,
|
||||
this.textColor,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -14,12 +14,12 @@ class DividerWidget extends StatelessWidget {
|
||||
final bool divColorHasBlur;
|
||||
final EdgeInsets? padding;
|
||||
const DividerWidget({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.dividerType,
|
||||
this.bgColor = Colors.transparent,
|
||||
this.divColorHasBlur = true,
|
||||
this.padding,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -13,8 +13,8 @@ class ExpandableMenuItemWidget extends StatefulWidget {
|
||||
required this.title,
|
||||
required this.selectionOptionsWidget,
|
||||
required this.leadingIcon,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ExpandableMenuItemWidget> createState() =>
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/events/opened_settings_event.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class HomeHeaderWidget extends StatefulWidget {
|
||||
final Widget centerWidget;
|
||||
const HomeHeaderWidget({required this.centerWidget, Key? key})
|
||||
: super(key: key);
|
||||
const HomeHeaderWidget({required this.centerWidget, super.key});
|
||||
|
||||
@override
|
||||
State<HomeHeaderWidget> createState() => _HomeHeaderWidgetState();
|
||||
@@ -16,7 +13,7 @@ class HomeHeaderWidget extends StatefulWidget {
|
||||
class _HomeHeaderWidgetState extends State<HomeHeaderWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasNotch = window.viewPadding.top > 65;
|
||||
final hasNotch = View.of(context).viewPadding.top > 65;
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(4, hasNotch ? 4 : 8, 4, 4),
|
||||
child: Row(
|
||||
|
||||
@@ -12,7 +12,7 @@ class TrailingWidget extends StatefulWidget {
|
||||
final double trailingExtraMargin;
|
||||
final bool showExecutionStates;
|
||||
const TrailingWidget({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.executionStateNotifier,
|
||||
this.trailingIcon,
|
||||
this.trailingIconColor,
|
||||
@@ -20,7 +20,7 @@ class TrailingWidget extends StatefulWidget {
|
||||
required this.trailingIconIsMuted,
|
||||
required this.trailingExtraMargin,
|
||||
required this.showExecutionStates,
|
||||
}) : super(key: key);
|
||||
});
|
||||
@override
|
||||
State<TrailingWidget> createState() => _TrailingWidgetState();
|
||||
}
|
||||
@@ -101,11 +101,11 @@ class ExpansionTrailingIcon extends StatelessWidget {
|
||||
final IconData? trailingIcon;
|
||||
final Color? trailingIconColor;
|
||||
const ExpansionTrailingIcon({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.isExpanded,
|
||||
this.trailingIcon,
|
||||
this.trailingIconColor,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -138,12 +138,12 @@ class LeadingWidget extends StatelessWidget {
|
||||
// leadIconSize deafult value is 20.
|
||||
final double leadingIconSize;
|
||||
const LeadingWidget({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.leadingIconSize,
|
||||
this.leadingIcon,
|
||||
this.leadingIconColor,
|
||||
this.leadingIconWidget,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -86,8 +86,8 @@ class MenuItemWidget extends StatefulWidget {
|
||||
this.showOnlyLoadingState = false,
|
||||
this.surfaceExecutionStates = true,
|
||||
this.alwaysShowSuccessState = false,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MenuItemWidget> createState() => _MenuItemWidgetState();
|
||||
|
||||
@@ -3,8 +3,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
class MenuSectionDescriptionWidget extends StatelessWidget {
|
||||
final String content;
|
||||
const MenuSectionDescriptionWidget({Key? key, required this.content})
|
||||
: super(key: key);
|
||||
const MenuSectionDescriptionWidget({super.key, required this.content});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user