Compare commits
367 Commits
cli-v0.2.3
...
famfix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
daa09136ef | ||
|
|
82ebcf66a5 | ||
|
|
490a7221e7 | ||
|
|
1b98f782f1 | ||
|
|
a46a64e386 | ||
|
|
492ea61bb7 | ||
|
|
9447f1c767 | ||
|
|
76b2a73f9a | ||
|
|
4bbe1ae0d2 | ||
|
|
b15b707600 | ||
|
|
454363d772 | ||
|
|
196fa2c8a4 | ||
|
|
4c3ca8a565 | ||
|
|
15aea42b96 | ||
|
|
29c7f587f6 | ||
|
|
5f0bb21491 | ||
|
|
c882ce0f98 | ||
|
|
02dde7f6a2 | ||
|
|
d268e1f309 | ||
|
|
245e78ac42 | ||
|
|
fa55bd88a2 | ||
|
|
11538236c0 | ||
|
|
09996f77ea | ||
|
|
3f512bc959 | ||
|
|
8b0990bd6c | ||
|
|
70ff886252 | ||
|
|
4314d42ab4 | ||
|
|
8abe6957d7 | ||
|
|
d6ec6809c2 | ||
|
|
8fe9b9571a | ||
|
|
d667cc4f98 | ||
|
|
5806eb6e60 | ||
|
|
8830deb619 | ||
|
|
5e32e975df | ||
|
|
8633dabd92 | ||
|
|
c256f0a8c4 | ||
|
|
b150bbe15e | ||
|
|
7f69fa5d65 | ||
|
|
3968dd93e9 | ||
|
|
557bdd142b | ||
|
|
b89da99c96 | ||
|
|
e79050a3b6 | ||
|
|
5e4707b695 | ||
|
|
fd0c2866a2 | ||
|
|
69eee661d1 | ||
|
|
c78578fce5 | ||
|
|
a0f103be9b | ||
|
|
04ede4326a | ||
|
|
7cb9bc3eb7 | ||
|
|
488402156f | ||
|
|
e80e602786 | ||
|
|
5d553afea7 | ||
|
|
9a25356abf | ||
|
|
5625733429 | ||
|
|
d2dd08391a | ||
|
|
9d06db2b6b | ||
|
|
80049b11ba | ||
|
|
28160b04b9 | ||
|
|
02441239d5 | ||
|
|
760b50b417 | ||
|
|
d118e0e63e | ||
|
|
49f9caac90 | ||
|
|
ea875730dd | ||
|
|
e3b03db06f | ||
|
|
b030c4e182 | ||
|
|
1a39846d25 | ||
|
|
e44020f93a | ||
|
|
435621496c | ||
|
|
8379162716 | ||
|
|
e8d9f4f6cf | ||
|
|
6724527c27 | ||
|
|
6b65a974b5 | ||
|
|
1b90fa93ee | ||
|
|
f907303c8b | ||
|
|
4317f819d8 | ||
|
|
587da41f53 | ||
|
|
3214031a0c | ||
|
|
0b1eee6c8e | ||
|
|
7c679cd38b | ||
|
|
3669b6be09 | ||
|
|
88b0ecf472 | ||
|
|
ee2f03adca | ||
|
|
3e6c253a24 | ||
|
|
776b7488d3 | ||
|
|
371377d4d1 | ||
|
|
aad42b3c00 | ||
|
|
9ce0b43bfe | ||
|
|
f5ea565aa8 | ||
|
|
d41e177b3c | ||
|
|
bc742f20e1 | ||
|
|
898658f0ef | ||
|
|
7743a4af98 | ||
|
|
d2764fe7e1 | ||
|
|
35601956d2 | ||
|
|
bc699b8f37 | ||
|
|
82df23a3b2 | ||
|
|
00028e3a10 | ||
|
|
c5dab37dfa | ||
|
|
d20615002c | ||
|
|
b44f844513 | ||
|
|
09d390bd38 | ||
|
|
e439e4a5f7 | ||
|
|
3147800486 | ||
|
|
0a7984a0d2 | ||
|
|
189a3ebc40 | ||
|
|
c37deecb96 | ||
|
|
d2a4634f02 | ||
|
|
c0eeb7dd2f | ||
|
|
4f271887fc | ||
|
|
f46f063beb | ||
|
|
2275a47438 | ||
|
|
80ab0a308f | ||
|
|
ddb867d21f | ||
|
|
c63cf362b6 | ||
|
|
270a628478 | ||
|
|
3f358b9511 | ||
|
|
b5f850b3be | ||
|
|
77d16e275d | ||
|
|
098a4526ad | ||
|
|
957c333cf3 | ||
|
|
c480dd71f1 | ||
|
|
101a9d4b5d | ||
|
|
e3ef1e4628 | ||
|
|
fd133d4023 | ||
|
|
cdfdc83083 | ||
|
|
8618babc11 | ||
|
|
ca28a3c595 | ||
|
|
5eba06a269 | ||
|
|
91017969b3 | ||
|
|
d25e37e2ad | ||
|
|
7b902a607a | ||
|
|
68bc6fac38 | ||
|
|
82fdae9253 | ||
|
|
d78ffced78 | ||
|
|
7035d3ca90 | ||
|
|
8920462b54 | ||
|
|
56f9f2a028 | ||
|
|
94a77b7df1 | ||
|
|
7fc42bed64 | ||
|
|
8eb34503ac | ||
|
|
2fe6df5d21 | ||
|
|
d59d3c3b07 | ||
|
|
503c2506aa | ||
|
|
e44405b46f | ||
|
|
cdaeec0e8e | ||
|
|
29671aa154 | ||
|
|
370299d433 | ||
|
|
38d42c67fb | ||
|
|
802ad184d2 | ||
|
|
55cff6f174 | ||
|
|
e5448685ca | ||
|
|
bde8a17cb4 | ||
|
|
fc3200af73 | ||
|
|
d21ea0a5a6 | ||
|
|
a1fda786f5 | ||
|
|
bc0980eb8d | ||
|
|
2cfc5d9c59 | ||
|
|
682710a8a8 | ||
|
|
ed50e5a36c | ||
|
|
f31a6f2401 | ||
|
|
239f08b516 | ||
|
|
8544f5e109 | ||
|
|
1eaaafb8df | ||
|
|
149196e7dd | ||
|
|
38a31b7492 | ||
|
|
b14ad92b91 | ||
|
|
872455cce2 | ||
|
|
65a5248338 | ||
|
|
fcf06cff57 | ||
|
|
13752654cd | ||
|
|
d5e8777e0d | ||
|
|
7de2a47c51 | ||
|
|
20bc84ca96 | ||
|
|
fd7c25029e | ||
|
|
764add95c8 | ||
|
|
8c3fc0a879 | ||
|
|
37c467eb86 | ||
|
|
931d7c8513 | ||
|
|
132962b92f | ||
|
|
a5c4d9cc18 | ||
|
|
39c31779a4 | ||
|
|
d09613a946 | ||
|
|
88e50982b2 | ||
|
|
ce5a8f0457 | ||
|
|
8c81a377c0 | ||
|
|
a0025ab09b | ||
|
|
324c156ea1 | ||
|
|
4b87c9f3ac | ||
|
|
dfda0c2c32 | ||
|
|
c1a53bdfce | ||
|
|
0c8dc3af95 | ||
|
|
b3100f098b | ||
|
|
0e157a4e33 | ||
|
|
04a5372f6e | ||
|
|
f47837f550 | ||
|
|
1af8d7481d | ||
|
|
af91adeb72 | ||
|
|
8fd90651b1 | ||
|
|
c3da41eee2 | ||
|
|
ac94dccb90 | ||
|
|
89881975f2 | ||
|
|
e715d582ac | ||
|
|
808c611a92 | ||
|
|
fe5146ead8 | ||
|
|
d398838742 | ||
|
|
717dc0996f | ||
|
|
00db3c0335 | ||
|
|
6ccca2114e | ||
|
|
0e3708ffdc | ||
|
|
36e7dae2ee | ||
|
|
9d76d93254 | ||
|
|
aab4bff6ff | ||
|
|
12a96b68ba | ||
|
|
a851caf78f | ||
|
|
4bbac0ca66 | ||
|
|
bb2bbb5655 | ||
|
|
d36934ec0d | ||
|
|
f922df304e | ||
|
|
f6d949db38 | ||
|
|
e937027667 | ||
|
|
84aeb79412 | ||
|
|
6140f35e69 | ||
|
|
0eba6b9c98 | ||
|
|
fe86075868 | ||
|
|
e9d63dfea9 | ||
|
|
1c322a9c62 | ||
|
|
dc0450b155 | ||
|
|
8e90541d87 | ||
|
|
8df04b2363 | ||
|
|
43c0d8a6ad | ||
|
|
dd52ee7763 | ||
|
|
387e4ae826 | ||
|
|
098ff4e664 | ||
|
|
00a04f18e4 | ||
|
|
b8304f0ec5 | ||
|
|
979fa5e6da | ||
|
|
91f356ceda | ||
|
|
a019aaf5fc | ||
|
|
a9df48ea5d | ||
|
|
be6ce6d639 | ||
|
|
9d421e79a7 | ||
|
|
d1d8144fd1 | ||
|
|
7302f1d4ab | ||
|
|
39788341db | ||
|
|
6c86fe0d53 | ||
|
|
30ed06cfda | ||
|
|
21788c28cf | ||
|
|
2969b5c9a5 | ||
|
|
aa74948f4a | ||
|
|
1bdbfe0580 | ||
|
|
cacf4212c7 | ||
|
|
8f540f23dc | ||
|
|
17d76e50da | ||
|
|
db26923d68 | ||
|
|
4670be9bba | ||
|
|
f8c2f4b9dd | ||
|
|
396065e80c | ||
|
|
5a755d851a | ||
|
|
286a968f65 | ||
|
|
efff97bc71 | ||
|
|
2a73de848c | ||
|
|
c7c8fd65b6 | ||
|
|
8d7eef99ad | ||
|
|
1605b44c6e | ||
|
|
578a92d4bc | ||
|
|
bf3ed6f478 | ||
|
|
92a9698df5 | ||
|
|
342ac3258a | ||
|
|
e4427d7605 | ||
|
|
6f729c01e1 | ||
|
|
0d7c319903 | ||
|
|
6d552f5190 | ||
|
|
06450a0ce0 | ||
|
|
72d6789739 | ||
|
|
3d2d0cc345 | ||
|
|
884246d2ab | ||
|
|
cf25cc40e4 | ||
|
|
7138510e48 | ||
|
|
15e7e0ae9d | ||
|
|
9dcced260f | ||
|
|
2d5dc734aa | ||
|
|
1d93d44180 | ||
|
|
0aeb9f0c82 | ||
|
|
183bbdd145 | ||
|
|
8d701d4fd5 | ||
|
|
c6f6041d24 | ||
|
|
f49ece10e6 | ||
|
|
d0f206741f | ||
|
|
87ff5c5c0b | ||
|
|
b931dac18b | ||
|
|
2b52616ba5 | ||
|
|
e66ee5bcb1 | ||
|
|
f18bcc71d3 | ||
|
|
43a7cb1223 | ||
|
|
ceb25651f2 | ||
|
|
8a8934eacd | ||
|
|
20fea517ce | ||
|
|
0d32bd55dd | ||
|
|
20bbdb131d | ||
|
|
1980cb035e | ||
|
|
bd00c27dc6 | ||
|
|
e8fa86e2ad | ||
|
|
baa72202b2 | ||
|
|
46658a26f3 | ||
|
|
6653b36764 | ||
|
|
c17d0d0087 | ||
|
|
b823a8d6a1 | ||
|
|
e06b20a566 | ||
|
|
8218bfba04 | ||
|
|
8df5831944 | ||
|
|
6e774d6758 | ||
|
|
981c74d3f1 | ||
|
|
18ee3b19f7 | ||
|
|
aa27191ddc | ||
|
|
0883fe1d05 | ||
|
|
17e59de59c | ||
|
|
bdb30d64f0 | ||
|
|
57881f34c3 | ||
|
|
6ef3c01030 | ||
|
|
d4ddc0f919 | ||
|
|
4736ec7e0a | ||
|
|
0840c66a34 | ||
|
|
eb2f6aec68 | ||
|
|
45074f85d9 | ||
|
|
c46c27d21d | ||
|
|
3ff8d04d7b | ||
|
|
437eb246b0 | ||
|
|
5e383f3844 | ||
|
|
9bce8dc878 | ||
|
|
a447d615e0 | ||
|
|
239e6a3158 | ||
|
|
5a72d62555 | ||
|
|
7aa8f6f00f | ||
|
|
5b168021f4 | ||
|
|
a407b1baad | ||
|
|
3589cc5bbf | ||
|
|
0cef0656f3 | ||
|
|
3b3ba721a2 | ||
|
|
d899be6eac | ||
|
|
17c713d3de | ||
|
|
0e9153f4ab | ||
|
|
5484a95bf4 | ||
|
|
2a1c1a30e9 | ||
|
|
f902b7e75c | ||
|
|
ac9f4e3181 | ||
|
|
b68b1a97b5 | ||
|
|
b8de2bf736 | ||
|
|
d35975b26e | ||
|
|
c2ca87d3af | ||
|
|
a41c359ae4 | ||
|
|
e00cdee92b | ||
|
|
b1ce7b6edb | ||
|
|
15d58e3446 | ||
|
|
677a473d7d | ||
|
|
9e12f35650 | ||
|
|
a7f31119fe | ||
|
|
b729b8f0ea | ||
|
|
b933a89336 | ||
|
|
016a476895 | ||
|
|
4ee6ef408e | ||
|
|
79712182af | ||
|
|
38a35696a3 | ||
|
|
bf4807da5b | ||
|
|
dc3f074588 | ||
|
|
8da160b834 | ||
|
|
2947ca2e3c | ||
|
|
59e26779b9 |
2
.github/workflows/auth-internal-release.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
- name: Build PlayStore AAB
|
||||
run: |
|
||||
flutter build appbundle --release --flavor playstore --dart-define=app.flavor=playstore
|
||||
flutter build appbundle --dart-define=cronetHttpNoPlay=true --release --flavor playstore
|
||||
env:
|
||||
SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_auth_key.jks"
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
|
||||
2
.github/workflows/auth-release.yml
vendored
@@ -68,7 +68,7 @@ jobs:
|
||||
|
||||
- name: Build independent APK
|
||||
run: |
|
||||
flutter build apk --release --flavor independent --dart-define=app.flavor=independent
|
||||
flutter build apk --dart-define=cronetHttpNoPlay=true --release --flavor independent
|
||||
mv build/app/outputs/flutter-apk/app-independent-release.apk artifacts/ente-${{ github.ref_name }}.apk
|
||||
env:
|
||||
SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_auth_key.jks"
|
||||
|
||||
25
.github/workflows/server-publish.yml
vendored
@@ -1,27 +1,24 @@
|
||||
name: "Publish ghcr (server)"
|
||||
|
||||
on:
|
||||
# Run manually, providing it the commit.
|
||||
#
|
||||
# To obtain the commit from the currently deployed museum, do:
|
||||
# curl -s https://api.ente.io/ping | jq -r '.id'
|
||||
#
|
||||
# See server/docs/publish.md for more details.
|
||||
# Run automatically on 15th of every month, at 05:00 UTC.
|
||||
schedule:
|
||||
- cron: '0 5 15 * *'
|
||||
# Run manually if needed to publish out of schedule.
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
commit:
|
||||
description: "Commit to publish the image from"
|
||||
type: string
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Determine commit from prod museum
|
||||
run: |
|
||||
echo "museum_commit=$(curl -s https://api.ente.io/ping | jq -r .id)" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.commit }}
|
||||
ref: ${{ env.museum_commit }}
|
||||
|
||||
- name: Build and push
|
||||
uses: mr-smithers-excellent/docker-build-push@v6
|
||||
@@ -34,8 +31,8 @@ jobs:
|
||||
enableBuildKit: true
|
||||
multiPlatform: true
|
||||
platform: linux/amd64,linux/arm64
|
||||
buildArgs: GIT_COMMIT=${{ inputs.commit }}
|
||||
tags: ${{ inputs.commit }}, latest
|
||||
buildArgs: GIT_COMMIT=${{ env.museum_commit }}
|
||||
tags: ${{ env.museum_commit }}, latest
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
@@ -35,9 +35,18 @@
|
||||
{
|
||||
"title": "Amazon"
|
||||
},
|
||||
{
|
||||
"title": "Ankama",
|
||||
"slug": "ankama"
|
||||
},
|
||||
{
|
||||
"title": "Anycoin Direct",
|
||||
"slug": "anycoindirect"
|
||||
},
|
||||
{
|
||||
"title": "Aruba",
|
||||
"slug": "aruba",
|
||||
"hex": "ef8a33"
|
||||
},
|
||||
{
|
||||
"title": "AscendEX"
|
||||
@@ -199,6 +208,10 @@
|
||||
{
|
||||
"title": "Bugzilla"
|
||||
},
|
||||
{
|
||||
"title": "ButterflyMX",
|
||||
"slug": "butterflymx"
|
||||
},
|
||||
{
|
||||
"title": "Bybit"
|
||||
},
|
||||
@@ -297,6 +310,9 @@
|
||||
{
|
||||
"title": "Discourse"
|
||||
},
|
||||
{
|
||||
"title": "Deloitte"
|
||||
},
|
||||
{
|
||||
"title": "DMarket"
|
||||
},
|
||||
@@ -352,6 +368,14 @@
|
||||
{
|
||||
"title": "Estateguru"
|
||||
},
|
||||
{
|
||||
"title": "EVEOnline",
|
||||
"slug": "eve_online",
|
||||
"altNames": [
|
||||
"EVE Online"
|
||||
],
|
||||
"hex": "858585"
|
||||
},
|
||||
{
|
||||
"title": "Fastmail"
|
||||
},
|
||||
@@ -376,9 +400,17 @@
|
||||
{
|
||||
"title": "ForUsAll"
|
||||
},
|
||||
{
|
||||
"title": "FreeTaxUSA",
|
||||
"slug": "freetaxusa"
|
||||
},
|
||||
{
|
||||
"title": "G2A"
|
||||
},
|
||||
{
|
||||
"title": "Gate.io",
|
||||
"slug": "gateio.svg"
|
||||
},
|
||||
{
|
||||
"title": "GitHub"
|
||||
},
|
||||
@@ -760,6 +792,11 @@
|
||||
"altNames": [
|
||||
"欧易"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "OnShape",
|
||||
"slug": "onshape",
|
||||
"hex": "7abb5e"
|
||||
},
|
||||
{
|
||||
"title": "Parqet",
|
||||
@@ -857,6 +894,11 @@
|
||||
{
|
||||
"title": "RealMe",
|
||||
"slug": "realme"
|
||||
},
|
||||
{
|
||||
"title": "RealVNC",
|
||||
"slug": "realvnc",
|
||||
"hex": "488aec"
|
||||
},
|
||||
{
|
||||
"title": "Registro br",
|
||||
@@ -901,6 +943,10 @@
|
||||
{
|
||||
"title": "Samsung"
|
||||
},
|
||||
{
|
||||
"title": "Seafile",
|
||||
"slug": "seafile"
|
||||
},
|
||||
{
|
||||
"title": "Sendgrid"
|
||||
},
|
||||
|
||||
5
auth/assets/custom-icons/icons/ankama.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 720" width="720" height="720">
|
||||
<title>ankama</title>
|
||||
<path class="s0" d="m572.3 253.3c-0.3-1.4-0.5-3-1.1-4.4-15.1-46-59.7-76.2-107.9-71.5-31.2 3-55.6 18.9-73.4 44.7-3.8 5.7-7.1 12-10.1 18.4-9.4 19.5-19.5 38.7-24.7 59.7-0.5 1.6-1.1 3.5-1.9 4.9-2.5 4.6-5.5 6.3-10.1 4.1-11.7-5.7-23.8-5.2-36.1-4.4-20.3 1.1-37.9-5.7-52.9-19.5-36.1-33.1-35.3-91 1.9-123 26.8-23.3 43.3-52.1 49.3-86.8 3-16.5 3.3-33.4 0-49.9-0.5-2.7-0.8-5.5-1.4-8.7 7.4-1.4 14.3 0 20.8 2.2 42.8 12.4 69.3 40.6 81.6 82.7 0.5 2.2 1.1 4.4 1.6 6.8 0.8 3 2.7 4.9 5.7 5.5 3.3 0.5 6.3-0.5 7.9-3.5 3-6.3 5.7-12.7 8.2-19.2 3.3-9.7 3.8-10.5 14.3-10.6 58.3-0.8 111.7 15.4 159.4 49 60.5 42.9 98.4 101.4 115.7 173.4 0.8 3.5 0.8 7.6 0.5 11.6-4.6 68-26.8 129.6-68.8 183.5-49 63.1-112.4 104.4-190.7 121.4-72.6 15.8-141.7 4.9-207.1-30.1-1.4-0.8-2.7-1.6-3.8-2.5-0.3 0-0.5-0.8-1.6-2.2 8.7 3 17 5.5 24.7 8.6 13.9 5.7 28.5 8.7 43.6 8.6 14.3 0 28.2 0.8 42.5 0 63.9-3.8 119.5-27.4 166.2-70.7 23.6-21.9 32.8-50.7 30.7-82.4-1.9-32.3-16.5-58.6-42.2-78.3-18.9-14.6-32-33.1-38-56.4-4.1-15.8-4.6-32-0.8-48.2 5.7-24.7 27.7-41.7 52.3-40.6 17 0.8 29.6 9 39.5 22.5 1.4 1.9 2.7 4.1 4.1 6.3 0.5 0 0.8-0.5 1.4-0.8l0.8 0.3v-0.5zm-263.5-55.1c-14.7 0-26.3 12.4-26.3 27.7 0 15.3 11.6 27.4 26.6 27.4 15 0 26-12.4 26-27.1 0-14.7-12-27.9-26.3-27.9v-0.2z"/>
|
||||
<path class="s0" d="m168.2 314.5c7.4 1.6 14.7 3.3 22.5 4.9 10.9 2.2 14.3 6.8 12 18.1-1.6 8.2-3 16.2-4.9 24.1-0.8 3.5 0 5.7 2.5 8.2 22.8 23.6 50.1 38.4 83.8 43.6-2.2-1.4-3-2.2-4.1-2.7-19.5-10.1-27.7-25.2-25.2-46.6 1.1-9.8 0-19.2-6-27.4-3.8-5.5-9-9.7-13.9-14.3-1.6-1.6-3.8-2.5-6-4.1 5.2-3.3 10.5-3.3 15.4-3 7.4 0.3 14.7 1.4 22.2 3 11.6 2.5 21.7 8.2 30.4 16.2 6.5 6 12.4 12.4 18.7 18.4 13.9 13.6 25.2 12.8 37.2-2.7 7.6-9.8 12.4-21.4 15.8-33.1 3.8-12.7 8.2-24.9 15.4-36.1 7.4-11.6 16.5-21.7 27.4-30.1 9.7-7.4 19.7-6.8 30.7-3.3v9.7c-1.1 27.1 5.2 52.6 19.5 75.9 1.1 1.9 2.2 4.1 3.5 6 4.1 6.3 3.3 12-0.5 18.1-5.5 8.7-13.5 13.2-23.8 13.5h-6.3c-12.7 0-23.3 8.2-26.8 20.6-3 10.9 1.9 23.3 11.7 29.6 10.9 6.8 24.1 5.7 33.4-3 10.1-9.8 16.5-21.7 18.9-35.7 0.3-1.9 0.8-4.1 1.1-6 17.3-0.3 39.8 16.6 48.2 37.2 10.1 24.1 6.3 46.6-10.1 68.8-0.8-5.2-1.4-9.4-2.2-12.8-2.2-9.7-8.6-15.7-18.1-17.7-6.8-1.6-9.8 0-13.6 5.7-1.9 3-3.5 6.3-5.2 9.7-2.5 4.9-4.6 9.8-7.1 14.6-13.6 27.1-34.9 45.8-63.2 55.6-27.1 9.7-55.2 13.2-83.5 10.1-16.5-1.6-29.3-10.5-39.8-23.6 1.9-1.4 3.3-2.7 4.9-3.8 7.4-5.5 12.8-12.7 16.6-20.8 1.1-2.2 1.6-4.9 1.9-7.4 0.5-5.2-2.7-9.4-7.6-9.8-4.6-0.5-8.7 2.5-10.1 7.6-0.8 3.3-1.1 6.5-2.5 9.7-3.8 7.1-9.7 12.8-16.5 17-7.4 4.4-14.6 3.8-20-1.4-5.5-5.5-6-11.7-1.9-19.7 0.3-0.5 0.5-1.4 1.4-3-2.5 1.1-4.1 1.4-5.5 2.2-17.7 10.6-25.8 31.5-19.2 51.5 18.7 56.7-6 119.5-54 151-3.3 2.2-7.1 4.1-11.6 6.8 0-3-0.5-5.2-0.5-7.4-2.5-57.8-30.1-98.9-82.4-122.8-32.8-14.7-53.7-39.5-62.1-74-12.4-50.1 13.9-102.2 61.2-122.8 1.9-0.8 4.1-1.4 7.6-2.7-15.1 28.8-18.7 57.2-9.4 86.8 5.5 17.3 14.7 32 29 45.5 0.3-12.4-0.8-23.6 7.6-33.4 2.7 6.3 5.5 12 8.2 17.7 4.9 10.9 12.7 19.5 23.3 24.9 12 6.3 24.1 6.8 36.5 1.1 8.6-3.8 15.8-9.4 22.2-16.2 8.6-9 8.6-24.9 0.3-35.7-5.5-7.1-8.6-14.7-7.4-23.6 1.9-15.4 15.1-26.8 32-28.2 4.6-0.3 9.4 0 13.6 0.5 4.1 0.8 8.2 2.5 12.7 3.3-3.5-3.8-7.9-6.5-12.7-8.2-8.2-2.7-16.6-3.3-25.2-1.6-14.7 3.3-24.9 16.2-26.3 33.4-0.5 6.3 0 12.8 1.1 19.2 3 16.5-0.8 30.4-13.2 42.8-1.9-1.6-3.8-2.7-5.7-4.4-43.9-37.6-69.6-84.6-75.6-142.2-10.1-95.9 42.5-184.9 130.9-223 28.5-12.4 46-33.8 53.4-63.9 0.3-1.4 0.8-2.7 1.9-3.8 1.9 10.1 1.4 20.3-1.1 30.1-6 24.1-19.5 42.8-41.4 55.3-21.4 12.4-38.4 29-51.5 50.1-2.5 4.1-2.7 7.1 0 10.9 2.7 3.5 5.2 7.6 7.6 11.6 4.4 7.4 3 13.9-4.1 18.9-6.3 4.4-13.2 8.2-19.5 12.7-1.6 1.1-3.5 3.3-3.8 5.2-1.6 18.7-0.5 36.9 5.5 55.3l1.4-0.3-0.2-0.2zm192.8 132c14.3 0 25.8-11.3 25.5-25.2 0-13.6-12-25.8-25.5-25.8-13.5 0-25.2 11.3-25.5 25.5 0 14.3 11.3 25.5 25.2 25.5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
4
auth/assets/custom-icons/icons/aruba.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#FF8300" fill-rule="evenodd" d="M12.1099561,17.3015551 C9.03598293,17.3015551 6.50849391,14.8423766 6.50849391,11.836714 C6.50849391,8.83105139 9.03598293,6.37187289 12.1099561,6.37187289 C15.1839292,6.37187289 17.7114182,8.83105139 17.7114182,11.836714 C17.7114182,14.8423766 15.1839292,17.3015551 12.1099561,17.3015551 L12.1099561,17.3015551 Z M12.1099561,2 C6.50849391,2 2,6.4401834 2,11.836714 C2,17.3015551 6.50849391,21.673428 12.1099561,21.673428 C14.4325135,21.673428 16.5501395,20.9220123 18.2579023,19.6241126 C19.28256,21.3318754 22.2199121,21.673428 22.2199121,21.673428 L22.2199121,11.836714 C22.2199121,6.4401834 17.7114182,2 12.1099561,2 L12.1099561,2 Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 901 B |
27
auth/assets/custom-icons/icons/butterflymx.svg
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="462px" height="404px" viewBox="0 0 462 404" version="1.1">
|
||||
<defs>
|
||||
<linearGradient id="linear0" gradientUnits="userSpaceOnUse" x1="464.529999" y1="-2595.189941" x2="1224.150024" y2="-2986.919922" gradientTransform="matrix(0.249968,0,0,-0.249968,150.980393,-561.725816)">
|
||||
<stop offset="0" style="stop-color:rgb(100%,92.156863%,18.039216%);stop-opacity:1;"/>
|
||||
<stop offset="0.92" style="stop-color:rgb(99.607843%,56.470591%,18.82353%);stop-opacity:1;"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linear1" gradientUnits="userSpaceOnUse" x1="-580.880005" y1="-2987.179932" x2="121.110001" y2="-2623.179932" gradientTransform="matrix(0.249968,0,0,-0.249968,150.980393,-561.725816)">
|
||||
<stop offset="0.06" style="stop-color:rgb(0%,40.784314%,89.803922%);stop-opacity:1;"/>
|
||||
<stop offset="1" style="stop-color:rgb(3.921569%,85.09804%,100%);stop-opacity:1;"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linear2" gradientUnits="userSpaceOnUse" x1="1063.689941" y1="-3644.949951" x2="436.269989" y2="-3207.530029" gradientTransform="matrix(0.249968,0,0,-0.249968,150.980393,-561.725816)">
|
||||
<stop offset="0" style="stop-color:rgb(93.725491%,3.921569%,21.176471%);stop-opacity:1;"/>
|
||||
<stop offset="1" style="stop-color:rgb(100%,59.215689%,54.11765%);stop-opacity:1;"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="linear3" gradientUnits="userSpaceOnUse" x1="-389.76001" y1="-3622.02002" x2="266.369995" y2="-3164.639893" gradientTransform="matrix(0.249968,0,0,-0.249968,150.980393,-561.725816)">
|
||||
<stop offset="0" style="stop-color:rgb(47.450981%,7.843138%,93.725491%);stop-opacity:1;"/>
|
||||
<stop offset="1" style="stop-color:rgb(85.882354%,41.176471%,100%);stop-opacity:1;"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:url(#linear0);" d="M 437.566406 160.457031 C 437.566406 192.628906 411.488281 218.730469 379.324219 218.730469 L 239.71875 218.730469 C 239.71875 116.257812 317.625 31.976562 417.445312 21.886719 C 428.53125 20.769531 437.585938 29.886719 437.585938 41.027344 Z M 437.566406 160.457031 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:url(#linear1);" d="M 24.640625 160.457031 C 24.640625 192.628906 50.722656 218.730469 82.882812 218.730469 L 222.492188 218.730469 C 222.492188 116.257812 144.589844 31.976562 44.777344 21.886719 C 33.695312 20.769531 24.640625 29.886719 24.640625 41.027344 Z M 24.640625 160.457031 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:url(#linear2);" d="M 269.425781 338.792969 C 249.960938 308.039062 239.65625 272.375 239.71875 235.976562 L 381.667969 235.976562 C 412.507812 235.976562 437.574219 260.675781 437.574219 291.0625 C 437.566406 380.972656 317.730469 415.246094 269.425781 338.792969 Z M 269.425781 338.792969 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:url(#linear3);" d="M 192.78125 338.792969 C 212.25 308.039062 222.554688 272.375 222.492188 235.976562 L 80.546875 235.976562 C 49.707031 235.976562 24.640625 260.675781 24.640625 291.0625 C 24.640625 380.972656 144.480469 415.246094 192.78125 338.792969 Z M 192.78125 338.792969 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
15
auth/assets/custom-icons/icons/deloitte.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1545 1333" width="1545" height="1333">
|
||||
<title>Deloitte-svg</title>
|
||||
<style>
|
||||
.s0 { fill: #86bc24 }
|
||||
.s1 { fill: #0f0b0b }
|
||||
</style>
|
||||
<g id="layer1">
|
||||
<g id="g3359">
|
||||
<g id="g3371">
|
||||
<path id="path3356" class="s0" d="m1354.4 1332.5c-105.1 0-190-84.8-190-189.6 0-104.9 84.9-189.6 190-189.6 105 0 189.9 84.7 189.9 189.6 0 104.8-84.9 189.6-189.9 189.6z"/>
|
||||
<path id="path3360" fill-rule="evenodd" class="s1" d="m1089.4 628.2q0 328.2-176.7 505.8-176.8 177.6-497.1 177.6h-414.9v-1311.1h443.9q308.8 0 476.8 161.4c112 107.6 168 263 168 466.3zm-359.7 12.5q0-180.1-69.7-267.2c-46.6-58-117.1-87-211.9-87h-100.9v734.5h77.2c105.3 0 182.5-31.2 231.6-93.8 49.1-62.4 73.7-157.9 73.7-286.5z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 785 B |
3
auth/assets/custom-icons/icons/eve_online.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="198.4" height="198.4" xml:space="preserve">
|
||||
<path transform="translate(0, 60)" d="M 0,0 0,13.88 10.97,13.88 10.97,10.31 60.69,10.31 60.69,0 0,0 z M 65.84,0 99.22,58.09 132.6,0 120.7,0 C 120.7,0 100.5,34.91 99.22,37.16 97.92,34.91 77.75,0 77.75,0 L 65.84,0 z M 137.8,0 137.8,13.88 148.7,13.88 148.7,10.31 198.4,10.31 198.4,0 137.8,0 z M 0,19.12 0,29.47 60.69,29.47 60.69,19.12 0,19.12 z M 137.8,19.12 137.8,29.47 198.4,29.47 198.4,19.12 137.8,19.12 z M 0,34.66 0,48.59 60.69,48.59 60.69,38.25 10.97,38.25 10.97,34.66 0,34.66 z M 137.8,34.66 137.8,48.59 198.4,48.59 198.4,38.25 148.7,38.25 148.7,34.66 137.8,34.66 z M 42.19,69.72 C 41.32,69.72 40.71,69.89 40.41,70.19 40.1,70.49 39.97,71.03 39.97,71.84 L 39.97,76.56 C 39.97,77.38 40.1,77.93 40.41,78.22 40.71,78.52 41.32,78.66 42.19,78.66 L 48.72,78.66 C 49.59,78.66 50.19,78.52 50.5,78.22 50.8,77.93 50.97,77.38 50.97,76.56 L 50.97,71.84 C 50.97,71.03 50.8,70.49 50.5,70.19 50.19,69.89 49.59,69.72 48.72,69.72 L 42.19,69.72 z M 64.37,69.72 64.37,78.66 66.25,78.66 66.25,73.84 C 66.25,73.66 66.23,73.43 66.22,73.19 66.2,72.94 66.18,72.69 66.16,72.41 66.26,72.53 66.38,72.67 66.5,72.78 66.62,72.89 66.75,73.01 66.91,73.16 L 73.47,78.66 74.88,78.66 74.88,69.72 73.03,69.72 73.03,74.41 C 73.03,74.52 73.05,74.7 73.06,74.91 73.07,75.11 73.09,75.47 73.12,75.97 72.99,75.81 72.82,75.66 72.66,75.5 72.49,75.35 72.31,75.18 72.09,75 L 65.81,69.72 64.37,69.72 z M 88.53,69.72 88.53,78.66 97.31,78.66 97.31,77 90.59,77 90.59,69.72 88.53,69.72 z M 109.4,69.72 109.4,78.66 111.5,78.66 111.5,69.72 109.4,69.72 z M 125.1,69.72 125.1,78.66 127,78.66 127,73.84 C 127,73.66 127,73.43 126.9,73.19 126.9,72.94 126.9,72.69 126.9,72.41 127,72.53 127.1,72.67 127.2,72.78 127.3,72.89 127.5,73.01 127.6,73.16 L 134.2,78.66 135.6,78.66 135.6,69.72 133.8,69.72 133.8,74.41 C 133.8,74.52 133.8,74.7 133.8,74.91 133.8,75.11 133.8,75.47 133.8,75.97 133.7,75.81 133.6,75.66 133.4,75.5 133.2,75.35 133,75.18 132.8,75 L 126.5,69.72 125.1,69.72 z M 149.3,69.72 149.3,78.66 158.5,78.66 158.5,77 151.3,77 151.3,74.78 155.4,74.78 155.4,73.25 151.3,73.25 151.3,71.25 158.4,71.25 158.4,69.72 149.3,69.72 z M 42.03,71.31 48.87,71.31 48.87,77 42.03,77 42.03,71.31 z" /></svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
1
auth/assets/custom-icons/icons/freetaxusa.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 560 400" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(2.51518 0 0 2.51518 30 162.272)"><path d="m136.108 5.893 6.356 23.513h-4.7l-1.218-5.334h-6.587l-1.284 5.334h-4.545l6.52-23.513h5.468zm-2.833 4.107-2.436 10.242h4.806zm-65.8 1.7c2.02 0 3.508.522 4.463 1.563s1.432 2.674 1.432 4.9v11.263h-3.95v-1.9c-.505.746-1.114 1.323-1.828 1.73-.706.405-1.508.615-2.322.61-1.405 0-2.502-.45-3.294-1.35s-1.176-2.182-1.176-3.786c0-1.734.577-3.1 1.73-4.1s2.804-1.586 4.956-1.762l1.78-.132v-1.02c0-1.03-.153-1.788-.462-2.27s-.8-.725-1.482-.725c-.615 0-1.086.187-1.415.56s-.56.966-.7 1.78l-3.887-.33c.264-1.713.9-2.98 1.9-3.804s2.427-1.235 4.25-1.235zm1.78 9.78-1.317.132c-.988.088-1.752.373-2.3.856s-.808 1.13-.808 1.943c0 .724.153 1.28.463 1.662s.756.577 1.35.577c.748.022 1.459-.331 1.894-.939.472-.626.708-1.476.708-2.553v-1.68zm-25.26-9.78c2.064 0 3.617.714 4.66 2.14s1.563 3.535 1.563 6.323v1.68h-8.825c.044 3.338.9 5.005 2.536 5.005.66 0 1.152-.202 1.482-.608s.548-1.092.66-2.06h4.084c-.177 1.888-.797 3.322-1.86 4.298s-2.542 1.465-4.43 1.465c-2.282 0-3.972-.73-5.07-2.2s-1.647-3.716-1.647-6.768c0-3.03.58-5.334 1.745-6.915s2.869-2.36 5.107-2.36zm-.133 3.03c-.8 0-1.372.346-1.744 1.038s-.604 1.84-.7 3.442h4.676c0-1.56-.18-2.695-.544-3.4s-.927-1.07-1.696-1.07zm-14.29-3.03c2.064 0 3.617.714 4.66 2.14s1.563 3.535 1.563 6.323v1.68h-8.825c.044 3.338.9 5.005 2.536 5.005.66 0 1.152-.202 1.482-.608s.55-1.092.66-2.06h4.084c-.177 1.888-.797 3.322-1.86 4.298s-2.542 1.465-4.43 1.465c-2.282 0-3.972-.73-5.07-2.2s-1.647-3.716-1.647-6.768c0-3.03.58-5.334 1.745-6.915s2.867-2.37 5.105-2.37zm-.133 3.03c-.8 0-1.372.346-1.744 1.038s-.604 1.84-.7 3.442h4.676c0-1.56-.18-2.695-.544-3.4s-.927-1.07-1.696-1.07zm-29.439-8.8v23.513h4.645v-9.55h7.3v-4.15h-7.3v-5.665h7.837v-4.148zm13.7 23.477v-17.2h4.215v2.306c.46-.834 1-1.477 1.647-1.927s1.305-.675 2-.675c.352 0 .747.054 1.185.165l-.493 3.952c-.33-.087-.757-.132-1.285-.132-.792-.018-1.554.306-2.09.89-.56.594-.84 1.33-.84 2.206v10.406h-4.339zm39.813 0v-19.364h-5.335v-4.15h15.315v4.15h-5.336v19.365zm30.792 0-2.832-5.665-2.7 5.665h-4.28l4.84-8.727-4.545-8.464h4.6l2.47 5.204 2.536-5.204h4.282l-4.676 8.234 4.908 8.957zm17.652-23.514h4.644v15.083c0 2.92-.68 5.155-2.04 6.702s-3.328 2.322-5.897 2.322-4.533-.773-5.896-2.322-2.04-3.78-2.04-6.702v-15.083h4.644v15.413c0 1.537.27 2.68.807 3.424s1.366 1.12 2.486 1.12 1.948-.373 2.487-1.12.807-1.887.807-3.424v-15.413zm12.118 13.14c-1.865-.548-3.237-1.377-4.116-2.486s-1.318-2.564-1.318-4.364c0-2.085.682-3.754 2.042-5.005s3.163-1.878 5.4-1.878c2.13 0 3.788.538 4.973 1.614s1.93 2.733 2.24 4.972l-4.414.594c-.22-1.23-.548-2.103-.987-2.62s-1.087-.774-1.943-.774-1.515.247-1.977.74-.7 1.224-.7 2.2c0 .813.175 1.444.527 1.894s.955.806 1.812 1.07l2.503.8c1.338.418 2.415.94 3.227 1.564.783.594 1.397 1.384 1.78 2.29.376.9.56 1.977.56 3.228 0 2.196-.675 3.936-2.025 5.22s-3.179 1.908-5.507 1.908c-4.897 0-7.5-2.547-7.84-7.64h4.612c.1 1.34.422 2.316.938 2.93s1.3.922 2.355.922c.9 0 1.597-.27 2.1-.807s.74-1.312.74-2.322c0-.9-.203-1.614-.6-2.14s-1.048-.92-1.926-1.185l-2.438-.724z" fill="#212f63"/><path d="m140.638 5.893 1.207 4.25h49.07v-4.25zm2.7 9.463 1.213 4.25 46.373-.007v-4.25zm2.79 9.802 1.208 4.25h43.6v-4.25z" fill="#bf2032"/><path d="m195.992 2.462h.33c.364 0 .546-.143.546-.432.005-.113-.036-.223-.115-.304-.076-.076-.212-.115-.406-.115h-.354v.85zm-.52 1.295v-2.577h.964c.27 0 .502.06.698.183s.29.336.29.64c0 .153-.042.294-.126.425s-.2.223-.343.273l.584 1.053h-.584l-.444-.89h-.52v.89h-.52zm.913.774c.255.001.508-.049.743-.147.232-.097.433-.234.6-.412.174-.185.31-.402.4-.64.1-.265.15-.547.147-.831.002-.28-.048-.558-.147-.819-.098-.25-.23-.465-.4-.648-.169-.181-.374-.326-.601-.425-.234-.1-.48-.153-.743-.153-.257-.002-.512.05-.748.153-.224.099-.426.244-.591.425-.169.191-.302.41-.394.648-.097.25-.145.522-.145.82-.004.284.046.565.145.831.098.25.23.462.394.64s.363.316.59.412c.237.1.491.15.748.147zm0 .47c-.318.003-.634-.06-.927-.184-.288-.122-.538-.293-.754-.514-.221-.227-.396-.494-.515-.787-.127-.304-.19-.643-.19-1.015s.062-.71.19-1.016c.12-.293.295-.56.515-.787.215-.22.471-.394.754-.514.288-.123.596-.184.927-.184.32-.002.637.061.933.184.293.123.545.293.76.514s.388.483.515.787.2.643.2 1.016-.063.71-.2 1.015-.298.568-.515.787-.468.392-.76.514c-.295.124-.613.186-.933.184z" fill="#212f63"/></g></svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
15
auth/assets/custom-icons/icons/gateio.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns:xodm="http://www.corel.com/coreldraw/odm/2003" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 2500 2500" style="enable-background:new 0 0 2500 2500;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;}
|
||||
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#2354E6;}
|
||||
.st2{fill-rule:evenodd;clip-rule:evenodd;fill:#17E6A1;}
|
||||
</style>
|
||||
<g id="Layer_x0020_1">
|
||||
<rect y="0" class="st0" width="2500" height="2500"></rect>
|
||||
<g id="_2500576017504">
|
||||
<path id="Fill-3" class="st1" d="M1250,1937.5c-379.7,0-687.5-307.8-687.5-687.5c0-379.7,307.8-687.5,687.5-687.5V0 C559.6,0,0,559.6,0,1250c0,690.3,559.6,1250,1250,1250c690.3,0,1250-559.6,1250-1250h-562.5 C1937.5,1629.7,1629.7,1937.5,1250,1937.5z"></path>
|
||||
<polygon id="Fill-4" class="st2" points="1250,1250 1937.5,1250 1937.5,562.5 1250,562.5 "></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 965 B |
1
auth/assets/custom-icons/icons/realvnc.svg
Normal file
|
After Width: | Height: | Size: 18 KiB |
13
auth/assets/custom-icons/icons/seafile.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 120" width="180" height="120">
|
||||
<title>seafile</title>
|
||||
<defs>
|
||||
<linearGradient id="g1" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0,114.369,-177.525,0,89.989,2.834)">
|
||||
<stop offset="0" stop-color="#fad956"/>
|
||||
<stop offset="1" stop-color="#ffa10f"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<style>
|
||||
.s0 { fill: url(#g1) }
|
||||
</style>
|
||||
<path class="s0" d="m1.2 52.8c0-3 2.4-5.4 5.4-5.4 1.4 0 2.7 0.6 3.6 1.5q0-0.7 0-1.4c0-9.9 8-17.9 17.9-17.9 2.5 0 4.9 0.5 7.1 1.5q0-0.8 0-1.5c0-14.8 12-26.8 26.8-26.8 14.7 0 26.6 11.9 26.8 26.6-4.8 4.2-8.7 9.6-11.2 15.7-4.8-3-10.4-4.8-16.5-4.8-12.4 0-23.2 7.1-28.3 17.8h-19.1-7.1c-3 0-5.4-2.4-5.4-5.3zm141.2-16c-6.6-6.7-15.8-10.8-25.9-10.8-18.5 0-33.8 13.7-36.3 31.5-4.5-6.1-11.8-10-20-10-13.8 0-25 11.2-25 25 0 4 0.9 7.8 2.6 11.2-8.7 1.7-15.1 8.5-15.1 16.5 0 9.4 8.8 17 19.7 17 4.7 0 9.1-1.5 12.6-4l40.2-39.5c4.4-4.1 10.3-6.6 16.8-6.6 13.6 0 24.7 10.9 25.1 24.4q0 0-0.1-0.1c0.2 4-1.8 8.1-5.7 10.3-5.3 3.1-12 1.4-15-3.7-2.9-5.1-1-11.7 4.4-14.8q1.9-1.1 3.9-1.4-1.8-0.4-3.6-0.4c-9.9 0-17.9 8-17.9 17.9 0 9.9 8 17.9 17.9 17.9q0.6 0 1.3-0.1l0.5-0.1h35.1v0.2c10.7-0.5 20.9-10.4 20.9-22.5 0-12.3-10.6-22.4-22.9-22.4q-0.1 0-0.1 0c-2 3.6-4.4 5.7-7.1 7.9 2.8-5.2 4.5-11.2 4.5-17.6-0.1-10.1-4.2-19.2-10.8-25.8z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -8,6 +8,7 @@ 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:native_dio_adapter/native_dio_adapter.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
int kConnectTimeout = 15000;
|
||||
@@ -50,6 +51,10 @@ class Network {
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
_dio.httpClientAdapter = NativeAdapter();
|
||||
_enteDio.httpClientAdapter = NativeAdapter();
|
||||
|
||||
_setupInterceptors(endpoint);
|
||||
|
||||
Bus.instance.on<EndpointUpdatedEvent>().listen((event) {
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import 'package:ente_auth/events/event.dart';
|
||||
|
||||
class OpenedSettingsEvent extends Event {}
|
||||
@@ -1,17 +0,0 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import "package:json_annotation/json_annotation.dart";
|
||||
|
||||
class Uint8ListConverter implements JsonConverter<Uint8List, List<int>> {
|
||||
const Uint8ListConverter();
|
||||
|
||||
@override
|
||||
Uint8List fromJson(List<int>? json) {
|
||||
return json == null ? Uint8List(0) : Uint8List.fromList(json);
|
||||
}
|
||||
|
||||
@override
|
||||
List<int> toJson(Uint8List object) {
|
||||
return object.toList();
|
||||
}
|
||||
}
|
||||
@@ -88,6 +88,8 @@
|
||||
"useRecoveryKey": "Usa la clau de recuperació",
|
||||
"incorrectPasswordTitle": "Contrasenya incorrecta",
|
||||
"welcomeBack": "Benvingut de nou!",
|
||||
"emailAlreadyRegistered": "El correu electrònic ja està registrat.",
|
||||
"emailNotRegistered": "El correu electrònic no està registrat.",
|
||||
"madeWithLoveAtPrefix": "fet amb ❤️ a ",
|
||||
"supportDevs": "Subscriu-te a <bold-green>ente</bold-green> per donar-nos suport",
|
||||
"supportDiscount": "Usa el codi de descompte \"AUTH\" per obtenir un 10% de descompte el primer any",
|
||||
@@ -502,5 +504,13 @@
|
||||
"deselectAll": "Desselecciona-ho tot",
|
||||
"selectAll": "Seleccionar-ho tot",
|
||||
"deleteDuplicates": "Elimina duplicats",
|
||||
"plainHTML": "HTML pla"
|
||||
"plainHTML": "HTML pla",
|
||||
"tellUsWhatYouThink": "Digueu-nos què us sembla",
|
||||
"dropReview": "Deixa una ressenya a l'App/Play Store",
|
||||
"supportEnte": "Donar suport a <bold-green>ente</bold-green>",
|
||||
"giveUsAStarOnGithub": "Dona'ns una estrella a Github",
|
||||
"free5GB": "5 GB gratuïts a <bold-green>ente</bold-green> Photos",
|
||||
"loginWithAuthAccount": "Inicieu sessió amb el vostre compte Auth",
|
||||
"freeStorageOffer": "10% de descompte a <bold-green>ente</bold-green> photos",
|
||||
"freeStorageOfferDescription": "Utilitzeu el codi \"AUTH\" per obtenir un 10% de descompte el primer any"
|
||||
}
|
||||
@@ -88,6 +88,8 @@
|
||||
"useRecoveryKey": "Wiederherstellungsschlüssel verwenden",
|
||||
"incorrectPasswordTitle": "Falsches Passwort",
|
||||
"welcomeBack": "Willkommen zurück!",
|
||||
"emailAlreadyRegistered": "E-Mail ist bereits registriert.",
|
||||
"emailNotRegistered": "E-Mail-Adresse nicht registriert.",
|
||||
"madeWithLoveAtPrefix": "gemacht mit ❤️ bei ",
|
||||
"supportDevs": "Bei <bold-green>ente</bold-green> registrieren, um das Projekt zu unterstützen",
|
||||
"supportDiscount": "Benutzen Sie den Rabattcode \"AUTH\" für 10 % Rabatt im ersten Jahr",
|
||||
@@ -145,6 +147,7 @@
|
||||
"leaveFamily": "Familie verlassen",
|
||||
"leaveFamilyMessage": "Sind Sie sicher, dass Sie den Familien-Plan verlassen wollen?",
|
||||
"inFamilyPlanMessage": "Sie haben einen Familien-Plan!",
|
||||
"hintForDesktop": "Klicken Sie mit der rechten Maustaste auf einen Code zum Bearbeiten oder Entfernen.",
|
||||
"scan": "Scannen",
|
||||
"scanACode": "Scan einen Code",
|
||||
"verify": "Überprüfen Sie",
|
||||
@@ -154,6 +157,7 @@
|
||||
"twoFactorAuthTitle": "Zwei-Faktor-Authentifizierung",
|
||||
"passkeyAuthTitle": "Passkey Authentifizierung",
|
||||
"verifyPasskey": "Passkey verifizieren",
|
||||
"loginWithTOTP": "Mit TOTP anmelden",
|
||||
"recoverAccount": "Konto wiederherstellen",
|
||||
"enterRecoveryKeyHint": "Geben Sie Ihren Wiederherstellungsschlüssel ein",
|
||||
"recover": "Wiederherstellen",
|
||||
@@ -255,6 +259,10 @@
|
||||
"areYouSureYouWantToLogout": "Sind sie sicher, dass sie sich ausloggen möchten?",
|
||||
"yesLogout": "Ja ausloggen",
|
||||
"exit": "Schließen",
|
||||
"theme": "Theme",
|
||||
"lightTheme": "Hell",
|
||||
"darkTheme": "Dunkel",
|
||||
"systemTheme": "System",
|
||||
"verifyingRecoveryKey": "Verifiziere Wiederherstellungsschlüssel...",
|
||||
"recoveryKeyVerified": "Wiederherstellungsschlüssel verifiziert",
|
||||
"recoveryKeySuccessBody": "Großartig! Ihr Wiederherstellungsschlüssel ist gültig. Vielen Dank für die Verifizierung.\n\nBitte denken sie daran, dass sie ihren Wiederherstellungsschlüssel sicher aufbewahren.",
|
||||
@@ -325,6 +333,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"manualSort": "Benutzerdefiniert",
|
||||
"editOrder": "Reihenfolge bearbeiten",
|
||||
"mostFrequentlyUsed": "Häufig verwendet",
|
||||
"mostRecentlyUsed": "Zuletzt verwendet",
|
||||
"activeSessions": "Aktive Sitzungen",
|
||||
"somethingWentWrongPleaseTryAgain": "Ein Fehler ist aufgetreten, bitte versuche es erneut",
|
||||
"thisWillLogYouOutOfThisDevice": "Dadurch wirst du von diesem Gerät abgemeldet!",
|
||||
@@ -444,6 +456,7 @@
|
||||
"customEndpoint": "Mit {endpoint} verbunden",
|
||||
"pinText": "Anpinnen",
|
||||
"unpinText": "Lösen",
|
||||
"pinned": "Angeheftet",
|
||||
"tags": "Tags",
|
||||
"createNewTag": "Neuen Tag erstellen",
|
||||
"tag": "Tag",
|
||||
@@ -478,5 +491,17 @@
|
||||
"setNewPin": "Neue PIN festlegen",
|
||||
"importFailureDescNew": "Die ausgewählte Datei konnte nicht verarbeitet werden.",
|
||||
"appLockNotEnabled": "App-Sperre nicht aktiviert",
|
||||
"appLockNotEnabledDescription": "Bitte aktivieren Sie die App-Sperre über Security > App-Sperre"
|
||||
"appLockNotEnabledDescription": "Bitte aktivieren Sie die App-Sperre über Security > App-Sperre",
|
||||
"authToViewPasskey": "Bitte authentifizieren, um deinen Passkey zu sehen",
|
||||
"duplicateCodes": "Doppelte Codes",
|
||||
"noDuplicates": "✨ Keine Duplikate",
|
||||
"youveNoDuplicateCodesThatCanBeCleared": "Sie haben keine doppelten Codes, die gelöscht werden können",
|
||||
"deselectAll": "Alle abwählen",
|
||||
"selectAll": "Alles auswählen",
|
||||
"deleteDuplicates": "Duplikate löschen",
|
||||
"plainHTML": "Reines HTML",
|
||||
"tellUsWhatYouThink": "Sagen Sie uns, was Sie denken",
|
||||
"dropReview": "Eine Bewertung im App/Play Store ablegen",
|
||||
"giveUsAStarOnGithub": "Gib uns einen Stern auf Github",
|
||||
"loginWithAuthAccount": "Mit Ihrem Auth Account anmelden"
|
||||
}
|
||||
@@ -504,5 +504,12 @@
|
||||
"deselectAll": "Deseleccionar todo",
|
||||
"selectAll": "Seleccionar todo",
|
||||
"deleteDuplicates": "Eliminar duplicados",
|
||||
"plainHTML": "HTML plano"
|
||||
"plainHTML": "HTML plano",
|
||||
"tellUsWhatYouThink": "Cuéntanos cuál es su opinión",
|
||||
"dropReview": "Danos una reseña en la App/Play Store",
|
||||
"supportEnte": "Apoya a <bold-green>ente</bold-green>",
|
||||
"giveUsAStarOnGithub": "Danos una estrella en GitHub",
|
||||
"free5GB": "5 GB gratis en <bold-green>ente</bold-green> Fotos",
|
||||
"freeStorageOffer": "10% de descuento en <bold-green>ente</bold-green> fotos",
|
||||
"freeStorageOfferDescription": "Usa el cupón \"AUTH\" para obtener un 10% de descuento en el primer año"
|
||||
}
|
||||
@@ -504,5 +504,13 @@
|
||||
"deselectAll": "Összes kijelölés megszüntetése",
|
||||
"selectAll": "Összes kijelölése",
|
||||
"deleteDuplicates": "Ismétlődések törlése",
|
||||
"plainHTML": "Sima HTML kód"
|
||||
"plainHTML": "Sima HTML kód",
|
||||
"tellUsWhatYouThink": "Mondja el mit gondol",
|
||||
"dropReview": "Írjon véleményt az App/Play Store-ban",
|
||||
"supportEnte": "Támogassa <bold-green>ente <bold-green>",
|
||||
"giveUsAStarOnGithub": "Adj nekünk egy csillagot a Githubon",
|
||||
"free5GB": "5GB ingyen <bold-green>ente <bold-green> Photos",
|
||||
"loginWithAuthAccount": "Jelentkezzen be Auth fiókjával",
|
||||
"freeStorageOffer": "10% kedvezmény on <bold-green>ente<bold-green> photos",
|
||||
"freeStorageOfferDescription": "Használja az \"AUTH\" kódot, hogy 10% kedvezményt kapjon az első évben"
|
||||
}
|
||||
@@ -499,7 +499,18 @@
|
||||
"appLockOfflineModeWarning": "バックアップなしで進むことを選択しました。アプリロックを忘れると、データにアクセスできなくなります。",
|
||||
"duplicateCodes": "重複コード",
|
||||
"noDuplicates": "✨ 重複なし",
|
||||
"youveNoDuplicateCodesThatCanBeCleared": "削除できる重複コードはありません",
|
||||
"deduplicateCodes": "重複コード",
|
||||
"deselectAll": "すべての選択を解除",
|
||||
"selectAll": "すべて選択",
|
||||
"deleteDuplicates": "重複を削除",
|
||||
"plainHTML": "Plain HTML",
|
||||
"tellUsWhatYouThink": "ご意見をお聞かせください",
|
||||
"loginWithAuthAccount": "認証アカウントでログイン"
|
||||
"dropReview": "App/Playストアにレビューを投稿する",
|
||||
"supportEnte": "<bold-green>ente</bold-green>をサポートする",
|
||||
"giveUsAStarOnGithub": "Githubで星をつける",
|
||||
"free5GB": "<bold-green>ente</bold-green>フォトで5GB無料",
|
||||
"loginWithAuthAccount": "認証アカウントでログイン",
|
||||
"freeStorageOffer": "<bold-green>ente</bold-green>の写真が10%オフ",
|
||||
"freeStorageOfferDescription": "クーポンコード \"AUTH\" の使用で初年度が10%オフになります"
|
||||
}
|
||||
@@ -505,5 +505,10 @@
|
||||
"selectAll": "Pasirinkti viską",
|
||||
"deleteDuplicates": "Ištrinti dublikatus",
|
||||
"plainHTML": "Grynasis HTML",
|
||||
"tellUsWhatYouThink": "Pasakykite mums, ką manote",
|
||||
"giveUsAStarOnGithub": "Suteikite mums žvaigždutę platformoje „Github“",
|
||||
"free5GB": "5 GB nemokami programai „<bold-green>ente</bold-green>“ nuotraukos",
|
||||
"loginWithAuthAccount": "Prisijungti su jūsų „Auth“ paskyra",
|
||||
"freeStorageOffer": "10 % nuolaida programai „<bold-green>ente</bold-green>“ nuotraukos",
|
||||
"freeStorageOfferDescription": "Naudokite kodą „AUTH“, kad gautumėte 10 % nuolaida pirmiesiems metams. "
|
||||
}
|
||||
@@ -1 +1,28 @@
|
||||
{}
|
||||
{
|
||||
"blog": "ബ്ലോഗ്",
|
||||
"verifyPassword": "പാസ്വേഡ് സ്ഥിരീകരിക്കുക",
|
||||
"recreatePassword": "പാസ്വേഡ് പുനഃസൃഷ്ടിക്കുക",
|
||||
"incorrectPasswordTitle": "തെറ്റായ പാസ്വേഡ്",
|
||||
"welcomeBack": "വീണ്ടും സ്വാഗതം!",
|
||||
"emailAlreadyRegistered": "ഇമെയിൽ ഇതിനകം രജിസ്റ്റർ ചെയ്തിട്ടുണ്ട്.",
|
||||
"emailNotRegistered": "ഇമെയിൽ രജിസ്റ്റർ ചെയ്തിട്ടില്ല.",
|
||||
"changeEmail": "ഇമെയിൽ മാറ്റുക",
|
||||
"changePassword": "പാസ്സ്വേർഡ് മാറ്റുക",
|
||||
"ok": "ശരി",
|
||||
"cancel": "റദ്ദാക്കുക",
|
||||
"yes": "അതെ",
|
||||
"no": "അല്ല",
|
||||
"email": "ഇമെയിൽ",
|
||||
"somethingWentWrongMessage": "എന്തോ കുഴപ്പമുണ്ടായി, ദയവായി വീണ്ടും ശ്രമിക്കുക",
|
||||
"inFamilyPlanMessage": "നിങ്ങൾ ഒരു ഫാമിലി പ്ലാനിലാണ്!",
|
||||
"scan": "സ്കാൻ ചെയ്യുക",
|
||||
"scanACode": "കോഡ് സ്കാൻ ചെയ്യുക",
|
||||
"verify": "പരിശോധിക്കുക",
|
||||
"verifyEmail": "ഇമെയിൽ സ്ഥിരീകരിക്കുക",
|
||||
"enterCodeHint": "നിങ്ങളുടെ ഓതന്റിക്കേറ്റർ ആപ്പിൽ നിന്നുള്ള 6 അക്ക കോഡ് നൽകുക",
|
||||
"twoFactorAuthTitle": "ടു-ഫാക്ടർ ആധികാരികത",
|
||||
"createNewAccount": "പുതിയ അക്കൗണ്ട് സൃഷ്ടിക്കുക",
|
||||
"confirmPassword": "പാസ്വേഡ് സ്ഥിരീകരിക്കുക",
|
||||
"language": "ഭാഷ",
|
||||
"security": "സുരക്ഷ"
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
const visibilityVisible = 0;
|
||||
const visibilityArchive = 1;
|
||||
|
||||
const magicKeyVisibility = 'visibility';
|
||||
|
||||
const pubMagicKeyEditedTime = 'editedTime';
|
||||
const pubMagicKeyEditedName = 'editedName';
|
||||
|
||||
class MagicMetadata {
|
||||
// 0 -> visible
|
||||
// 1 -> archived
|
||||
// 2 -> hidden etc?
|
||||
int visibility;
|
||||
|
||||
MagicMetadata({required this.visibility});
|
||||
|
||||
factory MagicMetadata.fromEncodedJson(String encodedJson) =>
|
||||
MagicMetadata.fromJson(jsonDecode(encodedJson));
|
||||
|
||||
factory MagicMetadata.fromJson(dynamic json) => MagicMetadata.fromMap(json);
|
||||
|
||||
static fromMap(Map<String, dynamic>? map) {
|
||||
if (map == null) return null;
|
||||
return MagicMetadata(
|
||||
visibility: map[magicKeyVisibility] ?? visibilityVisible,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PubMagicMetadata {
|
||||
int? editedTime;
|
||||
String? editedName;
|
||||
|
||||
PubMagicMetadata({this.editedTime, this.editedName});
|
||||
|
||||
factory PubMagicMetadata.fromEncodedJson(String encodedJson) =>
|
||||
PubMagicMetadata.fromJson(jsonDecode(encodedJson));
|
||||
|
||||
factory PubMagicMetadata.fromJson(dynamic json) =>
|
||||
PubMagicMetadata.fromMap(json);
|
||||
|
||||
static fromMap(Map<String, dynamic>? map) {
|
||||
if (map == null) return null;
|
||||
return PubMagicMetadata(
|
||||
editedTime: map[pubMagicKeyEditedTime],
|
||||
editedName: map[pubMagicKeyEditedName],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CollectionMagicMetadata {
|
||||
// 0 -> visible
|
||||
// 1 -> archived
|
||||
// 2 -> hidden etc?
|
||||
int visibility;
|
||||
|
||||
CollectionMagicMetadata({required this.visibility});
|
||||
|
||||
factory CollectionMagicMetadata.fromEncodedJson(String encodedJson) =>
|
||||
CollectionMagicMetadata.fromJson(jsonDecode(encodedJson));
|
||||
|
||||
factory CollectionMagicMetadata.fromJson(dynamic json) =>
|
||||
CollectionMagicMetadata.fromMap(json);
|
||||
|
||||
static fromMap(Map<String, dynamic>? map) {
|
||||
if (map == null) return null;
|
||||
return CollectionMagicMetadata(
|
||||
visibility: map[magicKeyVisibility] ?? visibilityVisible,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
class PublicKey {
|
||||
final String email;
|
||||
final String publicKey;
|
||||
|
||||
PublicKey(this.email, this.publicKey);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class FeatureFlagService {
|
||||
FeatureFlagService._privateConstructor();
|
||||
static final FeatureFlagService instance =
|
||||
FeatureFlagService._privateConstructor();
|
||||
|
||||
static final _internalUserIDs = const String.fromEnvironment(
|
||||
"internal_user_ids",
|
||||
defaultValue: "1,2,3,4,191,125,1580559962388044,1580559962392434,10000025",
|
||||
).split(",").map((element) {
|
||||
return int.parse(element);
|
||||
}).toSet();
|
||||
|
||||
bool isInternalUserOrDebugBuild() {
|
||||
final String? email = Configuration.instance.getEmail();
|
||||
final userID = Configuration.instance.getUserID();
|
||||
return (email != null && email.endsWith("@ente.io")) ||
|
||||
_internalUserIDs.contains(userID) ||
|
||||
kDebugMode;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class UserStore {
|
||||
UserStore._privateConstructor();
|
||||
|
||||
// ignore: unused_field
|
||||
late SharedPreferences _preferences;
|
||||
|
||||
static final UserStore instance = UserStore._privateConstructor();
|
||||
|
||||
Future<void> init() async {
|
||||
_preferences = await SharedPreferences.getInstance();
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,7 @@ class _LoginPasswordVerificationPageState
|
||||
);
|
||||
} else {
|
||||
_logger.severe('API failure during SRP login', e, s);
|
||||
if (e.type == DioExceptionType.unknown) {
|
||||
if (e.type == DioExceptionType.connectionError) {
|
||||
await _showContactSupportDialog(
|
||||
context,
|
||||
context.l10n.noInternetConnection,
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BottomShadowWidget extends StatelessWidget {
|
||||
final double offsetDy;
|
||||
final Color? shadowColor;
|
||||
const BottomShadowWidget({this.offsetDy = 28, this.shadowColor, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: shadowColor ?? Theme.of(context).colorScheme.surface,
|
||||
spreadRadius: 42,
|
||||
blurRadius: 42,
|
||||
offset: Offset(0, offsetDy), // changes position of shadow
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LinearProgressDialog extends StatefulWidget {
|
||||
final String message;
|
||||
|
||||
const LinearProgressDialog(this.message, {super.key});
|
||||
|
||||
@override
|
||||
LinearProgressDialogState createState() => LinearProgressDialogState();
|
||||
}
|
||||
|
||||
class LinearProgressDialogState extends State<LinearProgressDialog> {
|
||||
double? _progress;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_progress = 0;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void setProgress(double progress) {
|
||||
setState(() {
|
||||
_progress = progress;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
child: AlertDialog(
|
||||
title: Text(
|
||||
widget.message,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
content: LinearProgressIndicator(
|
||||
value: _progress,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Theme.of(context).colorScheme.alternativeColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
|
||||
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RenameDialog extends StatefulWidget {
|
||||
final String name;
|
||||
final String type;
|
||||
final int maxLength;
|
||||
|
||||
const RenameDialog(this.name, this.type, {super.key, this.maxLength = 100});
|
||||
|
||||
@override
|
||||
State<RenameDialog> createState() => _RenameDialogState();
|
||||
}
|
||||
|
||||
class _RenameDialogState extends State<RenameDialog> {
|
||||
String? _newName;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_newName = widget.name;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text("Enter a new name"),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TextFormField(
|
||||
decoration: InputDecoration(
|
||||
hintText: '${widget.type} name',
|
||||
hintStyle: const TextStyle(
|
||||
color: Colors.white30,
|
||||
),
|
||||
contentPadding: const EdgeInsets.all(12),
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_newName = value;
|
||||
});
|
||||
},
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.text,
|
||||
initialValue: _newName,
|
||||
autofocus: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text(
|
||||
"Cancel",
|
||||
style: TextStyle(
|
||||
color: Colors.redAccent,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(null);
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
"Rename",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
if (_newName!.trim().isEmpty) {
|
||||
showErrorDialog(
|
||||
context,
|
||||
"Empty name",
|
||||
"${widget.type} name cannot be empty",
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (_newName!.trim().length > widget.maxLength) {
|
||||
showErrorDialog(
|
||||
context,
|
||||
"Name too large",
|
||||
"${widget.type} name should be less than ${widget.maxLength} characters",
|
||||
);
|
||||
return;
|
||||
}
|
||||
Navigator.of(context).pop(_newName!.trim());
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
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, super.key});
|
||||
|
||||
@override
|
||||
State<HomeHeaderWidget> createState() => _HomeHeaderWidgetState();
|
||||
}
|
||||
|
||||
class _HomeHeaderWidgetState extends State<HomeHeaderWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasNotch = View.of(context).viewPadding.top > 65;
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(4, hasNotch ? 4 : 8, 4, 4),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
IconButton(
|
||||
visualDensity: const VisualDensity(horizontal: -2, vertical: -2),
|
||||
onPressed: () {
|
||||
Scaffold.of(context).openDrawer();
|
||||
Bus.instance.fire(OpenedSettingsEvent());
|
||||
},
|
||||
splashColor: Colors.transparent,
|
||||
icon: const Icon(
|
||||
Icons.menu_outlined,
|
||||
),
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
child: widget.centerWidget,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
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/account/delete_account_page.dart';
|
||||
import 'package:ente_auth/ui/components/captioned_text_widget.dart';
|
||||
import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart';
|
||||
import 'package:ente_auth/ui/components/menu_item_widget.dart';
|
||||
import 'package:ente_auth/ui/settings/common_settings.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DangerSectionWidget extends StatelessWidget {
|
||||
const DangerSectionWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ExpandableMenuItemWidget(
|
||||
title: context.l10n.exit,
|
||||
selectionOptionsWidget: _getSectionOptions(context),
|
||||
leadingIcon: Icons.logout_outlined,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getSectionOptions(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: context.l10n.logout,
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
_onLogoutTapped(context);
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: context.l10n.deleteAccount,
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(context, const DeleteAccountPage());
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _onLogoutTapped(BuildContext context) {
|
||||
showChoiceActionSheet(
|
||||
context,
|
||||
title: context.l10n.areYouSureYouWantToLogout,
|
||||
firstButtonLabel: context.l10n.yesLogout,
|
||||
isCritical: true,
|
||||
firstButtonOnTap: () async {
|
||||
await UserService.instance.logout(context);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/ui/settings/common_settings.dart';
|
||||
import 'package:ente_auth/ui/settings/settings_section_title.dart';
|
||||
import 'package:ente_auth/ui/settings/settings_text_item.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DebugSectionWidget extends StatelessWidget {
|
||||
const DebugSectionWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// This is a debug only section not shown to end users, so these strings are
|
||||
// not translated.
|
||||
return ExpandablePanel(
|
||||
header: const SettingsSectionTitle("Debug"),
|
||||
collapsed: Container(),
|
||||
expanded: _getSectionOptions(context),
|
||||
theme: getExpandableTheme(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getSectionOptions(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () async {
|
||||
_showKeyAttributesDialog(context);
|
||||
},
|
||||
child: const SettingsTextItem(
|
||||
text: "Key attributes",
|
||||
icon: Icons.navigate_next,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _showKeyAttributesDialog(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final keyAttributes = Configuration.instance.getKeyAttributes()!;
|
||||
final AlertDialog alert = AlertDialog(
|
||||
title: const Text("key attributes"),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
const Text(
|
||||
"Key",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(CryptoUtil.bin2base64(Configuration.instance.getKey()!)),
|
||||
const Padding(padding: EdgeInsets.all(12)),
|
||||
const Text(
|
||||
"Encrypted Key",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(keyAttributes.encryptedKey),
|
||||
const Padding(padding: EdgeInsets.all(12)),
|
||||
const Text(
|
||||
"Key Decryption Nonce",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(keyAttributes.keyDecryptionNonce),
|
||||
const Padding(padding: EdgeInsets.all(12)),
|
||||
const Text(
|
||||
"KEK Salt",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(keyAttributes.kekSalt),
|
||||
const Padding(padding: EdgeInsets.all(12)),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(l10n.ok),
|
||||
onPressed: () {
|
||||
Navigator.of(context, rootNavigator: true).pop('dialog');
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return alert;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class MadeWithLoveWidget extends StatelessWidget {
|
||||
const MadeWithLoveWidget({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
launchUrl(Uri.parse("https://ente.io"));
|
||||
},
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: l10n.madeWithLoveAtPrefix,
|
||||
style: DefaultTextStyle.of(context).style,
|
||||
children: const <TextSpan>[
|
||||
TextSpan(
|
||||
text: 'ente.io',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.green,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SettingsTextItem extends StatelessWidget {
|
||||
final String text;
|
||||
final IconData icon;
|
||||
const SettingsTextItem({
|
||||
super.key,
|
||||
required this.text,
|
||||
required this.icon,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(padding: EdgeInsets.all(Platform.isIOS ? 4 : 6)),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(text, style: Theme.of(context).textTheme.titleMedium),
|
||||
),
|
||||
Icon(icon),
|
||||
],
|
||||
),
|
||||
Padding(padding: EdgeInsets.all(Platform.isIOS ? 4 : 6)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
import 'package:dotted_border/dotted_border.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/subscription.dart';
|
||||
import 'package:ente_auth/services/billing_service.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:styled_text/styled_text.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class SupportDevWidget extends StatelessWidget {
|
||||
const SupportDevWidget({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
|
||||
// fetch
|
||||
if (Configuration.instance.hasConfiguredAccount()) {
|
||||
return FutureBuilder<Subscription>(
|
||||
future: BillingService.instance.getSubscription(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
final subscription = snapshot.data;
|
||||
if (subscription != null && subscription.productID == "free") {
|
||||
return buildWidget(l10n, context);
|
||||
}
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return buildWidget(l10n, context);
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildWidget(AppLocalizations l10n, BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
launchUrl(Uri.parse("https://ente.io"));
|
||||
},
|
||||
child: DottedBorder(
|
||||
borderType: BorderType.RRect,
|
||||
radius: const Radius.circular(12),
|
||||
padding: const EdgeInsets.all(6),
|
||||
dashPattern: const <double>[3, 3],
|
||||
color: getEnteColorScheme(context).primaryGreen,
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 6),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
StyledText(
|
||||
text: l10n.supportDevs,
|
||||
style: getEnteTextTheme(context).large,
|
||||
tags: {
|
||||
'bold-green': StyledTextTag(
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: getEnteColorScheme(context).primaryGreen,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(6)),
|
||||
Text(
|
||||
l10n.supportDiscount,
|
||||
style: const TextStyle(
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
// TODO Implement this library.
|
||||
@@ -113,12 +113,12 @@ String parseErrorForUI(
|
||||
if (dioError.response?.data["code"] != null) {
|
||||
errorInfo = "Reason: ${dioError.response!.data["code"]}";
|
||||
} else {
|
||||
errorInfo = "Reason: ${dioError.response!.data}";
|
||||
errorInfo = "Reason: ${dioError.response!.data.toString()}";
|
||||
}
|
||||
} else if (dioError.type == DioExceptionType.unknown) {
|
||||
errorInfo = "Reason: $dioError.error";
|
||||
} else if (dioError.type == DioExceptionType.badCertificate) {
|
||||
errorInfo = "Reason: ${dioError.error.toString()}";
|
||||
} else {
|
||||
errorInfo = "Reason: $dioError.type";
|
||||
errorInfo = "Reason: ${dioError.type.toString()}";
|
||||
}
|
||||
} else {
|
||||
if (kDebugMode) {
|
||||
|
||||
@@ -250,10 +250,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: collection
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
version: "1.19.0"
|
||||
confetti:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -286,6 +286,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
cronet_http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cronet_http
|
||||
sha256: "3af9c4d57bf07ef4b307e77b22be4ad61bea19ee6ff65e62184863f3a09f1415"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -310,6 +318,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
cupertino_http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cupertino_http
|
||||
sha256: "6fcf79586ad872ddcd6004d55c8c2aab3cdf0337436e8f99837b1b6c30665d0c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -346,10 +362,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260"
|
||||
sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.7.0"
|
||||
version: "5.8.0+1"
|
||||
dio_web_adapter:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -861,6 +877,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
http_profile:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_profile
|
||||
sha256: "7e679e355b09aaee2ab5010915c932cce3f2d1c11c3b2dc177891687014ffa78"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -885,6 +909,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
jni:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jni
|
||||
sha256: f377c585ea9c08d48b427dc2e03780af2889d1bb094440da853c6883c1acba4b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.1"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1061,6 +1093,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
native_dio_adapter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: native_dio_adapter
|
||||
sha256: "7420bc9517b2abe09810199a19924617b45690a44ecfb0616ac9babc11875c03"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1077,6 +1117,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
objective_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: objective_c
|
||||
sha256: "62e79ab8c3ed6f6a340ea50dd48d65898f5d70425d404f0d99411f6e56e04584"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
otp:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -21,7 +21,7 @@ dependencies:
|
||||
connectivity_plus: ^6.0.5
|
||||
convert: ^3.1.1
|
||||
device_info_plus: ^9.1.1
|
||||
dio: ^5.4.0
|
||||
dio: ^5.8.0+1
|
||||
dotted_border: ^2.0.0+2
|
||||
dropdown_button2: ^2.3.9
|
||||
email_validator: ^3.0.0
|
||||
@@ -72,6 +72,7 @@ dependencies:
|
||||
logging: ^1.0.1
|
||||
modal_bottom_sheet: ^3.0.0
|
||||
move_to_background: ^1.0.2
|
||||
native_dio_adapter: ^1.4.0
|
||||
otp: ^3.1.1
|
||||
package_info_plus: ^8.0.2
|
||||
password_strength: ^0.2.0
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
## v1.7.10 (Unreleased)
|
||||
|
||||
- Speed up selection for large libraries.
|
||||
- Support Japanese translations.
|
||||
- Fix video thumbnail generation on drag and drop.
|
||||
- .
|
||||
|
||||
## v1.7.9
|
||||
|
||||
@@ -362,8 +362,18 @@ const createMainWindow = () => {
|
||||
// do it (Step 2) unconditionally (i.e., on macOS too).
|
||||
//
|
||||
// https://www.electronjs.org/docs/latest/tutorial/custom-title-bar#create-a-custom-title-bar
|
||||
//
|
||||
// Note that by default on Windows, the color of the WCO title bar
|
||||
// overlay (three buttons - minimize, maximize, close - on the top
|
||||
// right) is static, and unlike Linux, doesn't adapt to the theme /
|
||||
// content. Explicitly choosing a dark background, while it won't work
|
||||
// always (if the user's theme is light), is better than picking a light
|
||||
// background since the main image viewer is always dark.
|
||||
titleBarStyle: "hidden",
|
||||
titleBarOverlay: true,
|
||||
titleBarOverlay:
|
||||
process.platform == "win32"
|
||||
? { color: "black", symbolColor: "#cdcdcd" }
|
||||
: true,
|
||||
// The color to show in the window until the web content gets loaded.
|
||||
// https://www.electronjs.org/docs/latest/api/browser-window#setting-the-backgroundcolor-property
|
||||
//
|
||||
|
||||
@@ -135,8 +135,12 @@ export const sidebar = [
|
||||
link: "/photos/faq/hidden-and-archive",
|
||||
},
|
||||
{
|
||||
text: "Machine Learning",
|
||||
link: "/photos/faq/machine-learning",
|
||||
text: "Face recognition",
|
||||
link: "/photos/faq/face-recognition",
|
||||
},
|
||||
{
|
||||
text: "Video streaming",
|
||||
link: "/photos/faq/video-streaming",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
title: Machine Learning FAQ
|
||||
title: Face recognition
|
||||
description:
|
||||
Frequently asked questions about several features of Ente's ML suite
|
||||
Frequently asked questions about Ente's face recognition
|
||||
---
|
||||
|
||||
# Machine Learning
|
||||
# Face recognition
|
||||
|
||||
## Can I merge or de-merge persons recognized by the app?
|
||||
|
||||
@@ -19,7 +19,7 @@ instead of typing the name again, tap on the already given name that should now
|
||||
be listed.
|
||||
|
||||
De-merging a certain grouping can be done by going to the person, pressing
|
||||
`review suggestions` and then the top right `history icon`. Now press on the
|
||||
`Review suggestions` and then the top right `History icon`. Now press on the
|
||||
`minus icon` beside the group you want to de-merge.
|
||||
|
||||
### Desktop
|
||||
@@ -29,6 +29,16 @@ selecting an existing person, and use the "Review suggestions" sheet to de-merge
|
||||
previously merged persons (click the top right history icon on the suggestion
|
||||
sheet to see the previous merges, and if necessary, undo them).
|
||||
|
||||
## How can I remove an incorrectly grouped face from a person?
|
||||
|
||||
On our mobile app, open up the person from the People section, click on the
|
||||
three dots to open up overflow menu, and click on Edit. Now you will be
|
||||
presented with the list of all photos that were merged to create this person.
|
||||
|
||||
You can click on the merged photos and select the photos you think are
|
||||
incorrectly grouped (by long-pressing on them) and select "Remove" from the
|
||||
action bar that pops up to remove any incorrect faces.
|
||||
|
||||
## How do I change the cover for a recognized person?
|
||||
|
||||
### Mobile
|
||||
@@ -1,12 +1,11 @@
|
||||
---
|
||||
title: Metadata
|
||||
description: Handling of metadata, in particular creation dates, in Ente Photos
|
||||
description: Handling of metadata in Ente Photos
|
||||
---
|
||||
|
||||
# Metadata
|
||||
|
||||
This document describes Ente's handling of metadata, in particular photo
|
||||
creation dates.
|
||||
This document describes Ente's handling of metadata
|
||||
|
||||
## Import
|
||||
|
||||
@@ -46,7 +45,7 @@ importing that folder into Ente**. This way, we will be able to always correctly
|
||||
map, for example, `flower.jpeg` and `flower.json` and show the same date for
|
||||
`flower.jpeg` that you would've seen within Google Photos.
|
||||
|
||||
### Screenshots
|
||||
### File name
|
||||
|
||||
In case the photo does not have a date in the Exif data (and it is not a Google
|
||||
takeout), for example, for screenshots or Whatsapp forwards, Ente will still try
|
||||
@@ -57,6 +56,28 @@ and deduce the correct date for the file from the name of the file.
|
||||
> This process works great most of the time, but it is inherently based on
|
||||
> heuristics and is not exact.
|
||||
|
||||
If we are unable to decipher the creation time from these 3 sources, we will set
|
||||
the upload time as the photo's creation time.
|
||||
|
||||
## Modifications
|
||||
|
||||
Ente supports modifications to the following metadata:
|
||||
- File name
|
||||
- Date & time
|
||||
- Location
|
||||
|
||||
The first two options are available on both mobile and desktop, while the
|
||||
ability to update location is only available within our mobile apps.
|
||||
|
||||
### Bulk modifications
|
||||
|
||||
You can bulk-edit creation time of photos from our desktop app, by
|
||||
multi-selecting items and selecting the "Fix time" option from the action bar.
|
||||
|
||||
You can bulk-edit location coordinates of photos from our mobile app, by
|
||||
multi-selecting items and selecting the "Edit location" option from the action
|
||||
bar.
|
||||
|
||||
## Export
|
||||
|
||||
Ente guarantees that you will get back the _exact_ same original photos and
|
||||
|
||||
@@ -47,6 +47,9 @@ availability and durability. Our
|
||||
[reliability document](https://ente.io/reliability) provides in-depth
|
||||
information about our storage infrastructure and data replication strategies.
|
||||
|
||||
In short, we store 3 copies of your data, across 3 different providers, in 3
|
||||
different countries. One of them is in an underground fall-out shelter in Paris.
|
||||
|
||||
### How does Ente's encryption compare to industry standards?
|
||||
|
||||
Our encryption model goes beyond industry standards. While many services use
|
||||
|
||||
@@ -157,6 +157,21 @@ The same applies to monthly plans.
|
||||
If you prefer to have this credit refunded to your original payment method,
|
||||
please contact support@ente.io, and we'll assist you.
|
||||
|
||||
## How can I update my payment method?
|
||||
|
||||
You can view and manage your payment method by clicking on the green
|
||||
subscription card within the Ente app, and selecting the "Manage payment method"
|
||||
button.
|
||||
|
||||
You will be able to see all of your previous invoices, with details regarding
|
||||
their payment status. In case of failed payments, you will also have an option
|
||||
to retry those charges.
|
||||
|
||||
## How can I cancel my subscription?
|
||||
|
||||
You can cancel your subscription by clicking on the green subscription card
|
||||
within the Ente app, and selecting the "Cancel subscription" button.
|
||||
|
||||
## Is there an x GB plan?
|
||||
|
||||
We have experimented quite a bit and have found it hard to design a single
|
||||
|
||||
63
docs/docs/photos/faq/video-streaming.md
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: Video streaming FAQ
|
||||
description:
|
||||
Frequently asked questions about Ente's video streaming feature
|
||||
---
|
||||
|
||||
# Video streaming
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Video streaming is available in beta on mobile apps starting v0.9.98.
|
||||
|
||||
### How to enable video streaming?
|
||||
|
||||
- Open Settings -> General -> Advanced
|
||||
- Switch on the toggle for `Video streaming`
|
||||
|
||||
### What happens when I enable video streaming?
|
||||
|
||||
Enabling video streaming will start processing videos captured in the last 30
|
||||
days, generating streams for each. Both local and remote videos will be
|
||||
processed, so this may consume bandwidth for downloading of remote files and
|
||||
uploading of the generated streams.
|
||||
|
||||
### How can I view video streams?
|
||||
|
||||
Settings -> Backup > Backup status will show details regarding the processing
|
||||
status for videos. Processed videos will have a green play button next to them.
|
||||
You can open these videos by tapping on them.
|
||||
|
||||
Processed videos will show a `Play stream` button, clicking which will load and
|
||||
play the stream.
|
||||
|
||||
Clicking on the `Info` icon within the original video will show details about
|
||||
the generated stream.
|
||||
|
||||
### What is a stream?
|
||||
|
||||
Stream is an encrypted HLS file with an `.m3u8` playlist that helps play a video
|
||||
with support for seeking **without** downloading the full file.
|
||||
|
||||
Currently it converts videos into `720p` with `2mbps` bitrate in `H.264` format.
|
||||
The generated stream is single blob (encrypted with AES) while the playlist file
|
||||
(`.m3u8`) is another blob (encrypted using XChaCha20).
|
||||
|
||||
We cannot read the contents, duration or the number of chunks within the
|
||||
generated stream.
|
||||
|
||||
### Will streams consume space in my storage?
|
||||
|
||||
While this feature is in beta, we will not count the storage consumed by your
|
||||
streams against your storage quota. This may change in the future. If it does,
|
||||
we will provide an option to opt-in to one of the following:
|
||||
1. Original videos only
|
||||
2. Compressed streams only
|
||||
3. Both
|
||||
|
||||
### Something doesn't seem right, what to do?
|
||||
|
||||
As video streaming is still in beta, some things might not work correctly.
|
||||
Please create a thread within the `#feedback` channel on
|
||||
[Discord](https://discord.com/channels/948937918347608085/1121126215995113552)
|
||||
or reach out to [support@ente.io](mailto:support@ente.io).
|
||||
@@ -43,6 +43,10 @@ need to disable this "Optimize battery usage" mode in the system settings for
|
||||
Ente if you wish for Ente to automatically back up your photos in the
|
||||
background.
|
||||
|
||||
On Android versions 15 and later, if an app is in private space and the private
|
||||
space is locked, Android doesn’t allow the app to run any background processes.
|
||||
As a result, background sync will not work.
|
||||
|
||||
### Desktop
|
||||
|
||||
In addition to our mobile apps, the background sync also works on our desktop
|
||||
|
||||
@@ -50,5 +50,5 @@ end-to-end encrypted security that we use for syncing your photos.
|
||||
Note that the desktop app does not currently support modifying the face
|
||||
groupings, that is only supported by the mobile app.
|
||||
|
||||
For more information on how to use Machine Learning please check out
|
||||
[the FAQ](../faq/machine-learning).
|
||||
For more information on how to use Machine Learning for face recognition please
|
||||
check out [the FAQ](../faq/face-recognition).
|
||||
|
||||
@@ -10,3 +10,9 @@ automatically deleted from Trash after 30 days. You can manaully select photos
|
||||
to permanently delete or completely empty the trash if you wish.
|
||||
|
||||
Items in trash are included in your used storage calculation.
|
||||
|
||||
## Recovery
|
||||
|
||||
If you have deleted items accidentally, you can recover them from Trash by
|
||||
selecting these items, and clicking the "Restore" button on the action bar that
|
||||
pops up.
|
||||
|
||||
@@ -144,8 +144,6 @@ PODS:
|
||||
- Flutter
|
||||
- media_kit_libs_ios_video (1.0.4):
|
||||
- Flutter
|
||||
- media_kit_native_event_loop (1.0.0):
|
||||
- Flutter
|
||||
- media_kit_video (0.0.1):
|
||||
- Flutter
|
||||
- motion_sensors (0.0.1):
|
||||
@@ -189,8 +187,6 @@ PODS:
|
||||
- PromisesObjC (2.4.0)
|
||||
- receive_sharing_intent (1.8.0):
|
||||
- Flutter
|
||||
- screen_brightness_ios (0.1.0):
|
||||
- Flutter
|
||||
- SDWebImage (5.20.0):
|
||||
- SDWebImage/Core (= 5.20.0)
|
||||
- SDWebImage/Core (5.20.0)
|
||||
@@ -278,7 +274,6 @@ DEPENDENCIES:
|
||||
- maps_launcher (from `.symlinks/plugins/maps_launcher/ios`)
|
||||
- media_extension (from `.symlinks/plugins/media_extension/ios`)
|
||||
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
||||
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
||||
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
||||
- motion_sensors (from `.symlinks/plugins/motion_sensors/ios`)
|
||||
- motionphoto (from `.symlinks/plugins/motionphoto/ios`)
|
||||
@@ -293,7 +288,6 @@ DEPENDENCIES:
|
||||
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
|
||||
- privacy_screen (from `.symlinks/plugins/privacy_screen/ios`)
|
||||
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
|
||||
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
|
||||
- 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`)
|
||||
@@ -390,8 +384,6 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/media_extension/ios"
|
||||
media_kit_libs_ios_video:
|
||||
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
|
||||
media_kit_native_event_loop:
|
||||
:path: ".symlinks/plugins/media_kit_native_event_loop/ios"
|
||||
media_kit_video:
|
||||
:path: ".symlinks/plugins/media_kit_video/ios"
|
||||
motion_sensors:
|
||||
@@ -420,8 +412,6 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/privacy_screen/ios"
|
||||
receive_sharing_intent:
|
||||
:path: ".symlinks/plugins/receive_sharing_intent/ios"
|
||||
screen_brightness_ios:
|
||||
:path: ".symlinks/plugins/screen_brightness_ios/ios"
|
||||
sentry_flutter:
|
||||
:path: ".symlinks/plugins/sentry_flutter/ios"
|
||||
share_plus:
|
||||
@@ -472,7 +462,7 @@ SPEC CHECKSUMS:
|
||||
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
|
||||
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
|
||||
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
|
||||
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
|
||||
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
|
||||
flutter_sodium: c84426b4de738514b5b66cfdeb8a06634e72fe0b
|
||||
fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c
|
||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||
@@ -489,7 +479,6 @@ SPEC CHECKSUMS:
|
||||
maps_launcher: 2e5b6a2d664ec6c27f82ffa81b74228d770ab203
|
||||
media_extension: 6d30dc1431ebaa63f43c397c37917b1a0a597a4c
|
||||
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
||||
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
||||
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
||||
motion_sensors: 03f55b7c637a7e365a0b5f9697a449f9059d5d91
|
||||
motionphoto: d4a432b8c8f22fb3ad966258597c0103c9c5ff16
|
||||
@@ -502,14 +491,13 @@ SPEC CHECKSUMS:
|
||||
onnxruntime-objc: b6fab0f1787aa6f7190c2013f03037df4718bd8b
|
||||
open_mail_app: 794172f6a22cd16319d3ddaf45e945b2f74952b0
|
||||
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
||||
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
photo_manager: ff695c7a1dd5bc379974953a2b5c0a293f7c4c8a
|
||||
privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
receive_sharing_intent: df9c334dc9feadcbd3266e5cb49c8443405e1c9f
|
||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
|
||||
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
|
||||
Sentry: f8374b5415bc38dfb5645941b3ae31230fbeae57
|
||||
@@ -526,8 +514,8 @@ SPEC CHECKSUMS:
|
||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||
video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3
|
||||
video_thumbnail: c4e2a3c539e247d4de13cd545344fd2d26ffafd1
|
||||
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
||||
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
||||
volume_controller: 2e3de73d6e7e81a0067310d17fb70f2f86d71ac7
|
||||
wakelock_plus: 373cfe59b235a6dd5837d0fb88791d2f13a90d56
|
||||
|
||||
PODFILE CHECKSUM: 20e086e6008977d43a3d40260f3f9bffcac748dd
|
||||
|
||||
|
||||
@@ -315,7 +315,6 @@
|
||||
"${BUILT_PRODUCTS_DIR}/maps_launcher/maps_launcher.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/media_extension/media_extension.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/media_kit_libs_ios_video/media_kit_libs_ios_video.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/media_kit_native_event_loop/media_kit_native_event_loop.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/media_kit_video/media_kit_video.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/motion_sensors/motion_sensors.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/motionphoto/motionphoto.framework",
|
||||
@@ -329,7 +328,6 @@
|
||||
"${BUILT_PRODUCTS_DIR}/photo_manager/photo_manager.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/privacy_screen/privacy_screen.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/receive_sharing_intent/receive_sharing_intent.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/screen_brightness_ios/screen_brightness_ios.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/sentry_flutter/sentry_flutter.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/share_plus/share_plus.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/shared_preferences_foundation/shared_preferences_foundation.framework",
|
||||
@@ -412,7 +410,6 @@
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/maps_launcher.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/media_extension.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/media_kit_libs_ios_video.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/media_kit_native_event_loop.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/media_kit_video.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/motion_sensors.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/motionphoto.framework",
|
||||
@@ -426,7 +423,6 @@
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/photo_manager.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/privacy_screen.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/receive_sharing_intent.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/screen_brightness_ios.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sentry_flutter.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_foundation.framework",
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:convert';
|
||||
import "dart:io";
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import "package:ente_crypto/ente_crypto.dart";
|
||||
import "package:flutter/services.dart";
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -19,9 +20,9 @@ import 'package:photos/db/upload_locks_db.dart';
|
||||
import "package:photos/events/endpoint_updated_event.dart";
|
||||
import 'package:photos/events/signed_in_event.dart';
|
||||
import 'package:photos/events/user_logged_out_event.dart';
|
||||
import 'package:photos/models/key_attributes.dart';
|
||||
import 'package:photos/models/key_gen_result.dart';
|
||||
import 'package:photos/models/private_key_attributes.dart';
|
||||
import 'package:photos/models/api/user/key_attributes.dart';
|
||||
import 'package:photos/models/api/user/key_gen_result.dart';
|
||||
import 'package:photos/models/api/user/private_key_attributes.dart';
|
||||
import 'package:photos/services/collections_service.dart';
|
||||
import 'package:photos/services/favorites_service.dart';
|
||||
import "package:photos/services/home_widget_service.dart";
|
||||
@@ -30,7 +31,6 @@ import "package:photos/services/machine_learning/face_ml/person/person_service.d
|
||||
import 'package:photos/services/memories_service.dart';
|
||||
import 'package:photos/services/search_service.dart';
|
||||
import 'package:photos/services/sync_service.dart';
|
||||
import 'package:photos/utils/crypto_util.dart';
|
||||
import 'package:photos/utils/file_uploader.dart';
|
||||
import "package:photos/utils/lock_screen_settings.dart";
|
||||
import 'package:photos/utils/validator_util.dart';
|
||||
@@ -248,7 +248,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);
|
||||
@@ -294,7 +294,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);
|
||||
@@ -332,7 +332,7 @@ class Configuration {
|
||||
// Derive key-encryption-key from the entered password and existing
|
||||
// mem and ops limits
|
||||
keyEncryptionKey ??= await CryptoUtil.deriveKey(
|
||||
utf8.encode(password) as Uint8List,
|
||||
utf8.encode(password),
|
||||
CryptoUtil.base642bin(attributes.kekSalt),
|
||||
attributes.memLimit!,
|
||||
attributes.opsLimit!,
|
||||
|
||||
@@ -18,6 +18,7 @@ import 'package:photos/core/error-reporting/tunneled_transport.dart';
|
||||
import "package:photos/core/errors.dart";
|
||||
import 'package:photos/models/typedefs.dart';
|
||||
import "package:photos/utils/device_info.dart";
|
||||
import "package:photos/utils/ram_check_util.dart";
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
@@ -205,6 +206,12 @@ class SuperLogging {
|
||||
}),
|
||||
);
|
||||
|
||||
unawaited(
|
||||
checkDeviceTotalRAM().then((ram) {
|
||||
if (ram != null) $.info("Device RAM: ${ram}MB");
|
||||
}),
|
||||
);
|
||||
|
||||
if (appConfig.body == null) return;
|
||||
|
||||
if (enable && sentryIsEnabled) {
|
||||
|
||||
@@ -20,7 +20,7 @@ extension InvalidReasonExn on InvalidReason {
|
||||
class InvalidFileError extends ArgumentError {
|
||||
final InvalidReason reason;
|
||||
|
||||
InvalidFileError(String message, this.reason) : super(message);
|
||||
InvalidFileError(String super.message, this.reason);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
@@ -63,19 +63,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 SharingNotPermittedForFreeAccountsError extends Error {}
|
||||
|
||||
@@ -251,20 +251,20 @@ class CollectionsDB {
|
||||
Map<String, dynamic> _getRowForCollection(Collection collection) {
|
||||
final row = <String, dynamic>{};
|
||||
row[columnID] = collection.id;
|
||||
row[columnOwner] = collection.owner!.toJson();
|
||||
row[columnOwner] = collection.owner.toJson();
|
||||
row[columnEncryptedKey] = collection.encryptedKey;
|
||||
row[columnKeyDecryptionNonce] = collection.keyDecryptionNonce;
|
||||
row[columnName] = collection.name;
|
||||
row[columnEncryptedName] = collection.encryptedName;
|
||||
row[columnNameDecryptionNonce] = collection.nameDecryptionNonce;
|
||||
row[columnType] = Collection.typeToString(collection.type);
|
||||
row[columnType] = typeToString(collection.type);
|
||||
row[columnEncryptedPath] = collection.attributes.encryptedPath;
|
||||
row[columnPathDecryptionNonce] = collection.attributes.pathDecryptionNonce;
|
||||
row[columnVersion] = collection.attributes.version;
|
||||
row[columnSharees] =
|
||||
json.encode(collection.sharees?.map((x) => x?.toMap()).toList());
|
||||
json.encode(collection.sharees.map((x) => x.toMap()).toList());
|
||||
row[columnPublicURLs] =
|
||||
json.encode(collection.publicURLs?.map((x) => x?.toMap()).toList());
|
||||
json.encode(collection.publicURLs.map((x) => x.toMap()).toList());
|
||||
row[columnUpdationTime] = collection.updationTime;
|
||||
if (collection.isDeleted) {
|
||||
row[columnIsDeleted] = _sqlBoolTrue;
|
||||
@@ -290,7 +290,7 @@ class CollectionsDB {
|
||||
row[columnName],
|
||||
row[columnEncryptedName],
|
||||
row[columnNameDecryptionNonce],
|
||||
Collection.typeFromString(row[columnType]),
|
||||
typeFromString(row[columnType]),
|
||||
CollectionAttributes(
|
||||
encryptedPath: row[columnEncryptedPath],
|
||||
pathDecryptionNonce: row[columnPathDecryptionNonce],
|
||||
|
||||
@@ -1733,6 +1733,7 @@ class FilesDB {
|
||||
Future<List<EnteFile>> getAllFilesAfterDate({
|
||||
required FileType fileType,
|
||||
required DateTime beginDate,
|
||||
required int userID,
|
||||
}) async {
|
||||
final db = await instance.sqliteAsyncDB;
|
||||
final results = await db.getAll(
|
||||
@@ -1741,6 +1742,7 @@ class FilesDB {
|
||||
WHERE $columnFileType = ?
|
||||
AND $columnCreationTime > ?
|
||||
AND $columnUploadedFileID != -1
|
||||
AND $columnOwnerID = $userID
|
||||
ORDER BY $columnCreationTime DESC
|
||||
''',
|
||||
[getInt(fileType), beginDate.microsecondsSinceEpoch],
|
||||
|
||||
@@ -3,18 +3,18 @@ import "dart:math";
|
||||
import "dart:typed_data";
|
||||
|
||||
import "package:dio/dio.dart";
|
||||
import "package:ente_crypto/ente_crypto.dart";
|
||||
import "package:flutter/cupertino.dart";
|
||||
import "package:logging/logging.dart";
|
||||
import "package:photos/core/configuration.dart";
|
||||
import "package:photos/core/network/network.dart";
|
||||
import "package:photos/emergency/model.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/models/api/user/key_attributes.dart";
|
||||
import "package:photos/models/api/user/set_keys_request.dart";
|
||||
import "package:photos/models/api/user/srp.dart";
|
||||
import "package:photos/models/key_attributes.dart";
|
||||
import "package:photos/models/set_keys_request.dart";
|
||||
import "package:photos/services/user_service.dart";
|
||||
import "package:photos/ui/common/user_dialogs.dart";
|
||||
import "package:photos/utils/crypto_util.dart";
|
||||
import "package:photos/utils/dialog_util.dart";
|
||||
import "package:photos/utils/email_util.dart";
|
||||
import "package:pointycastle/pointycastle.dart";
|
||||
|
||||
@@ -7,7 +7,7 @@ import "package:photos/emergency/model.dart";
|
||||
import "package:photos/emergency/recover_others_account.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/l10n/l10n.dart";
|
||||
import "package:photos/models/key_attributes.dart";
|
||||
import "package:photos/models/api/user/key_attributes.dart";
|
||||
import "package:photos/theme/colors.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import "package:photos/ui/components/action_sheet_widget.dart";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import "dart:convert";
|
||||
|
||||
import "package:ente_crypto/ente_crypto.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -7,10 +8,9 @@ import 'package:password_strength/password_strength.dart';
|
||||
import "package:photos/emergency/emergency_service.dart";
|
||||
import "package:photos/emergency/model.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/models/key_attributes.dart";
|
||||
import "package:photos/models/set_keys_request.dart";
|
||||
import "package:photos/models/api/user/key_attributes.dart";
|
||||
import "package:photos/models/api/user/set_keys_request.dart";
|
||||
import 'package:photos/ui/common/dynamic_fab.dart';
|
||||
import "package:photos/utils/crypto_util.dart";
|
||||
import 'package:photos/utils/dialog_util.dart';
|
||||
import 'package:photos/utils/toast_util.dart';
|
||||
|
||||
@@ -325,7 +325,7 @@ class _RecoverOthersAccountState extends State<RecoverOthersAccount> {
|
||||
// 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);
|
||||
|
||||
@@ -322,22 +322,20 @@ class _AddContactPage extends State<AddContactPage> {
|
||||
final int ownerID = Configuration.instance.getUserID()!;
|
||||
existingEmails.add(Configuration.instance.getEmail()!);
|
||||
for (final c in CollectionsService.instance.getActiveCollections()) {
|
||||
if (c.owner?.id == ownerID) {
|
||||
for (final User? u in c.sharees ?? []) {
|
||||
if (u != null &&
|
||||
u.id != null &&
|
||||
if (c.owner.id == ownerID) {
|
||||
for (final User u in c.sharees) {
|
||||
if (u.id != null &&
|
||||
u.email.isNotEmpty &&
|
||||
!existingEmails.contains(u.email)) {
|
||||
existingEmails.add(u.email);
|
||||
suggestedUsers.add(u);
|
||||
}
|
||||
}
|
||||
} else if (c.owner != null &&
|
||||
c.owner!.id != null &&
|
||||
c.owner!.email.isNotEmpty &&
|
||||
!existingEmails.contains(c.owner!.email)) {
|
||||
existingEmails.add(c.owner!.email);
|
||||
suggestedUsers.add(c.owner!);
|
||||
} else if (c.owner.id != null &&
|
||||
c.owner.email.isNotEmpty &&
|
||||
!existingEmails.contains(c.owner.email)) {
|
||||
existingEmails.add(c.owner.email);
|
||||
suggestedUsers.add(c.owner);
|
||||
}
|
||||
}
|
||||
final cachedUserDetails = UserService.instance.getCachedUserDetails();
|
||||
|
||||
@@ -15,7 +15,7 @@ final lightThemeData = ThemeData(
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: Colors.black,
|
||||
secondary: Color.fromARGB(255, 163, 163, 163),
|
||||
background: Colors.white,
|
||||
surface: Colors.white,
|
||||
surfaceTint: Colors.transparent,
|
||||
),
|
||||
outlinedButtonTheme: buildOutlinedButtonThemeData(
|
||||
@@ -70,13 +70,13 @@ final lightThemeData = ThemeData(
|
||||
color: Colors.black,
|
||||
width: 2,
|
||||
),
|
||||
fillColor: MaterialStateProperty.resolveWith((states) {
|
||||
return states.contains(MaterialState.selected)
|
||||
fillColor: WidgetStateProperty.resolveWith((states) {
|
||||
return states.contains(WidgetState.selected)
|
||||
? const Color.fromRGBO(0, 0, 0, 1)
|
||||
: const Color.fromRGBO(255, 255, 255, 1);
|
||||
}),
|
||||
checkColor: MaterialStateProperty.resolveWith((states) {
|
||||
return states.contains(MaterialState.selected)
|
||||
checkColor: WidgetStateProperty.resolveWith((states) {
|
||||
return states.contains(WidgetState.selected)
|
||||
? const Color.fromRGBO(255, 255, 255, 1)
|
||||
: const Color.fromRGBO(0, 0, 0, 1);
|
||||
}),
|
||||
@@ -93,7 +93,7 @@ final darkThemeData = ThemeData(
|
||||
hintColor: const Color.fromRGBO(158, 158, 158, 1),
|
||||
colorScheme: const ColorScheme.dark(
|
||||
primary: Colors.white,
|
||||
background: Color.fromRGBO(0, 0, 0, 1),
|
||||
surface: Color.fromRGBO(0, 0, 0, 1),
|
||||
secondary: Color.fromARGB(255, 163, 163, 163),
|
||||
surfaceTint: Colors.transparent,
|
||||
),
|
||||
@@ -145,15 +145,15 @@ final darkThemeData = ThemeData(
|
||||
color: Colors.grey,
|
||||
width: 2,
|
||||
),
|
||||
fillColor: MaterialStateProperty.resolveWith((states) {
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
fillColor: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return const Color.fromRGBO(158, 158, 158, 1);
|
||||
} else {
|
||||
return const Color.fromRGBO(0, 0, 0, 1);
|
||||
}
|
||||
}),
|
||||
checkColor: MaterialStateProperty.resolveWith((states) {
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
checkColor: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return const Color.fromRGBO(0, 0, 0, 1);
|
||||
} else {
|
||||
return const Color.fromRGBO(158, 158, 158, 1);
|
||||
@@ -378,17 +378,17 @@ OutlinedButtonThemeData buildOutlinedButtonThemeData({
|
||||
fontSize: 18,
|
||||
),
|
||||
).copyWith(
|
||||
backgroundColor: MaterialStateProperty.resolveWith<Color>(
|
||||
(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
backgroundColor: WidgetStateProperty.resolveWith<Color>(
|
||||
(Set<WidgetState> states) {
|
||||
if (states.contains(WidgetState.disabled)) {
|
||||
return bgDisabled;
|
||||
}
|
||||
return bgEnabled;
|
||||
},
|
||||
),
|
||||
foregroundColor: MaterialStateProperty.resolveWith<Color>(
|
||||
(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
foregroundColor: WidgetStateProperty.resolveWith<Color>(
|
||||
(Set<WidgetState> states) {
|
||||
if (states.contains(WidgetState.disabled)) {
|
||||
return fgDisabled;
|
||||
}
|
||||
return fgEnabled;
|
||||
@@ -426,21 +426,21 @@ ElevatedButtonThemeData buildElevatedButtonThemeData({
|
||||
SwitchThemeData getSwitchThemeData(Color activeColor) {
|
||||
return SwitchThemeData(
|
||||
thumbColor:
|
||||
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
WidgetStateProperty.resolveWith<Color?>((Set<WidgetState> states) {
|
||||
if (states.contains(WidgetState.disabled)) {
|
||||
return null;
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return activeColor;
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
trackColor:
|
||||
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
WidgetStateProperty.resolveWith<Color?>((Set<WidgetState> states) {
|
||||
if (states.contains(WidgetState.disabled)) {
|
||||
return null;
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return activeColor;
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:io';
|
||||
import "package:adaptive_theme/adaptive_theme.dart";
|
||||
import 'package:background_fetch/background_fetch.dart';
|
||||
import "package:computer/computer.dart";
|
||||
import 'package:ente_crypto/ente_crypto.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -47,12 +48,10 @@ import "package:photos/services/sync_service.dart";
|
||||
import "package:photos/services/user_service.dart";
|
||||
import 'package:photos/ui/tools/app_lock.dart';
|
||||
import 'package:photos/ui/tools/lock_screen.dart';
|
||||
import 'package:photos/utils/crypto_util.dart';
|
||||
import "package:photos/utils/email_util.dart";
|
||||
import 'package:photos/utils/file_uploader.dart';
|
||||
import "package:photos/utils/lock_screen_settings.dart";
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import "package:video_player_media_kit/video_player_media_kit.dart";
|
||||
|
||||
final _logger = Logger("main");
|
||||
|
||||
@@ -238,10 +237,6 @@ Future<void> _init(bool isBackground, {String via = ''}) async {
|
||||
ServiceLocator.instance
|
||||
.init(preferences, NetworkClient.instance.enteDio, packageInfo);
|
||||
|
||||
if (!isBackground && flagService.internalUser) {
|
||||
VideoPlayerMediaKit.ensureInitialized(iOS: true);
|
||||
}
|
||||
|
||||
_logger.info("UserService init $tlog");
|
||||
await UserService.instance.init();
|
||||
_logger.info("UserService init done $tlog");
|
||||
|
||||
@@ -45,7 +45,7 @@ class CreateRequest {
|
||||
map['keyDecryptionNonce'] = keyDecryptionNonce;
|
||||
map['encryptedName'] = encryptedName;
|
||||
map['nameDecryptionNonce'] = nameDecryptionNonce;
|
||||
map['type'] = Collection.typeToString(type);
|
||||
map['type'] = typeToString(type);
|
||||
if (attributes != null) {
|
||||
map['attributes'] = attributes!.toMap();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:freezed_annotation/freezed_annotation.dart";
|
||||
|
||||
@immutable
|
||||
class EntityData {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import "dart:typed_data";
|
||||
|
||||
import 'package:photos/models/key_attributes.dart';
|
||||
import 'package:photos/models/private_key_attributes.dart';
|
||||
import 'package:photos/models/api/user/key_attributes.dart';
|
||||
import 'package:photos/models/api/user/private_key_attributes.dart';
|
||||
|
||||
class KeyGenResult {
|
||||
final KeyAttributes keyAttributes;
|
||||
@@ -8,7 +8,7 @@ import "package:photos/models/metadata/common_keys.dart";
|
||||
|
||||
class Collection {
|
||||
final int id;
|
||||
final User? owner;
|
||||
final User owner;
|
||||
final String encryptedKey;
|
||||
final String? keyDecryptionNonce;
|
||||
@Deprecated("Use collectionName instead")
|
||||
@@ -20,8 +20,8 @@ class Collection {
|
||||
final String? nameDecryptionNonce;
|
||||
final CollectionType type;
|
||||
final CollectionAttributes attributes;
|
||||
final List<User?>? sharees;
|
||||
final List<PublicURL?>? publicURLs;
|
||||
final List<User> sharees;
|
||||
final List<PublicURL> publicURLs;
|
||||
final int updationTime;
|
||||
final bool isDeleted;
|
||||
|
||||
@@ -95,12 +95,12 @@ class Collection {
|
||||
|
||||
// hasLink returns true if there's any link attached to the collection
|
||||
// including expired links
|
||||
bool get hasLink => publicURLs != null && publicURLs!.isNotEmpty;
|
||||
bool get hasLink => publicURLs.isNotEmpty;
|
||||
|
||||
bool get hasCover => (pubMagicMetadata.coverID ?? 0) > 0;
|
||||
|
||||
// hasSharees returns true if the collection is shared with other ente users
|
||||
bool get hasSharees => sharees != null && sharees!.isNotEmpty;
|
||||
bool get hasSharees => sharees.isNotEmpty;
|
||||
|
||||
bool get isPinned => (magicMetadata.order ?? 0) != 0;
|
||||
|
||||
@@ -121,52 +121,43 @@ class Collection {
|
||||
}
|
||||
|
||||
List<User> getSharees() {
|
||||
final List<User> result = [];
|
||||
if (sharees == null) {
|
||||
return result;
|
||||
}
|
||||
for (final User? u in sharees!) {
|
||||
if (u != null) {
|
||||
result.add(u);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return sharees;
|
||||
}
|
||||
|
||||
bool isOwner(int userID) {
|
||||
return (owner?.id ?? 0) == userID;
|
||||
return (owner.id ?? -100) == userID;
|
||||
}
|
||||
|
||||
bool isDownloadEnabledForPublicLink() {
|
||||
if (publicURLs == null || publicURLs!.isEmpty) {
|
||||
if (publicURLs.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
return publicURLs?.first?.enableDownload ?? true;
|
||||
return publicURLs.first.enableDownload;
|
||||
}
|
||||
|
||||
bool isCollectEnabledForPublicLink() {
|
||||
if (publicURLs == null || publicURLs!.isEmpty) {
|
||||
if (publicURLs.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
return publicURLs?.first?.enableCollect ?? false;
|
||||
return publicURLs.first.enableCollect;
|
||||
}
|
||||
|
||||
bool get isJoinEnabled {
|
||||
if (publicURLs == null || publicURLs!.isEmpty) {
|
||||
if (publicURLs.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
return publicURLs?.first?.enableJoin ?? false;
|
||||
return publicURLs.first.enableJoin;
|
||||
}
|
||||
|
||||
CollectionParticipantRole getRole(int userID) {
|
||||
if (isOwner(userID)) {
|
||||
return CollectionParticipantRole.owner;
|
||||
}
|
||||
if (sharees == null) {
|
||||
if (sharees.isEmpty) {
|
||||
return CollectionParticipantRole.unknown;
|
||||
}
|
||||
for (final User? u in sharees!) {
|
||||
if (u != null && u.id == userID) {
|
||||
for (final User u in sharees) {
|
||||
if (u.id == userID) {
|
||||
if (u.isViewer) {
|
||||
return CollectionParticipantRole.viewer;
|
||||
} else if (u.isCollaborator) {
|
||||
@@ -185,40 +176,8 @@ class Collection {
|
||||
}
|
||||
|
||||
void updateSharees(List<User> newSharees) {
|
||||
sharees?.clear();
|
||||
sharees?.addAll(newSharees);
|
||||
}
|
||||
|
||||
static CollectionType typeFromString(String type) {
|
||||
switch (type) {
|
||||
case "folder":
|
||||
return CollectionType.folder;
|
||||
case "favorites":
|
||||
return CollectionType.favorites;
|
||||
case "uncategorized":
|
||||
return CollectionType.uncategorized;
|
||||
case "album":
|
||||
return CollectionType.album;
|
||||
case "unknown":
|
||||
return CollectionType.unknown;
|
||||
}
|
||||
debugPrint("unexpected collection type $type");
|
||||
return CollectionType.unknown;
|
||||
}
|
||||
|
||||
static String typeToString(CollectionType type) {
|
||||
switch (type) {
|
||||
case CollectionType.folder:
|
||||
return "folder";
|
||||
case CollectionType.favorites:
|
||||
return "favorites";
|
||||
case CollectionType.album:
|
||||
return "album";
|
||||
case CollectionType.uncategorized:
|
||||
return "uncategorized";
|
||||
case CollectionType.unknown:
|
||||
return "unknown";
|
||||
}
|
||||
sharees.clear();
|
||||
sharees.addAll(newSharees);
|
||||
}
|
||||
|
||||
Collection copyWith({
|
||||
@@ -303,6 +262,38 @@ enum CollectionType {
|
||||
unknown,
|
||||
}
|
||||
|
||||
CollectionType typeFromString(String type) {
|
||||
switch (type) {
|
||||
case "folder":
|
||||
return CollectionType.folder;
|
||||
case "favorites":
|
||||
return CollectionType.favorites;
|
||||
case "uncategorized":
|
||||
return CollectionType.uncategorized;
|
||||
case "album":
|
||||
return CollectionType.album;
|
||||
case "unknown":
|
||||
return CollectionType.unknown;
|
||||
}
|
||||
debugPrint("unexpected collection type $type");
|
||||
return CollectionType.unknown;
|
||||
}
|
||||
|
||||
String typeToString(CollectionType type) {
|
||||
switch (type) {
|
||||
case CollectionType.folder:
|
||||
return "folder";
|
||||
case CollectionType.favorites:
|
||||
return "favorites";
|
||||
case CollectionType.album:
|
||||
return "album";
|
||||
case CollectionType.uncategorized:
|
||||
return "uncategorized";
|
||||
case CollectionType.unknown:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
extension CollectionTypeExtn on CollectionType {
|
||||
bool get canDelete =>
|
||||
this != CollectionType.favorites && this != CollectionType.uncategorized;
|
||||
|
||||
@@ -2,9 +2,8 @@ import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import "package:photos/models/api/billing/subscription.dart";
|
||||
import "package:photos/models/api/storage_bonus/bonus.dart";
|
||||
import 'package:photos/models/file/file_type.dart';
|
||||
import 'package:photos/models/subscription.dart';
|
||||
|
||||
class UserDetails {
|
||||
final String email;
|
||||
@@ -52,6 +51,10 @@ class UserDetails {
|
||||
}
|
||||
|
||||
int getFreeStorage() {
|
||||
final int? memberLimit = familyMemberStorageLimit();
|
||||
if (memberLimit != null) {
|
||||
return max(memberLimit - usage, 0);
|
||||
}
|
||||
return max(getTotalStorage() - getFamilyOrPersonalUsage(), 0);
|
||||
}
|
||||
|
||||
@@ -62,6 +65,17 @@ class UserDetails {
|
||||
storageBonus;
|
||||
}
|
||||
|
||||
// return the member storage limit if user is part of family and the admin
|
||||
// has set the storage limit for the user.
|
||||
int? familyMemberStorageLimit() {
|
||||
if (isPartOfFamily()) {
|
||||
final FamilyMember? currentUserMember = familyData!.members!
|
||||
.firstWhereOrNull((element) => element.email.trim() == email.trim());
|
||||
return currentUserMember?.storageLimit;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// This is the total storage for which user has paid for.
|
||||
int getPlanPlusAddonStorage() {
|
||||
return (isPartOfFamily() ? familyData!.storage : subscription.storage) +
|
||||
@@ -107,12 +121,14 @@ class FamilyMember {
|
||||
final int usage;
|
||||
final String id;
|
||||
final bool isAdmin;
|
||||
final int? storageLimit;
|
||||
|
||||
FamilyMember(
|
||||
this.email,
|
||||
this.usage,
|
||||
this.id,
|
||||
this.isAdmin,
|
||||
this.storageLimit,
|
||||
);
|
||||
|
||||
factory FamilyMember.fromMap(Map<String, dynamic> map) {
|
||||
@@ -121,6 +137,7 @@ class FamilyMember {
|
||||
map['usage'] as int,
|
||||
map['id'] as String,
|
||||
map['isAdmin'] as bool,
|
||||
map['storageLimit'] as int?,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -130,6 +147,7 @@ class FamilyMember {
|
||||
'usage': usage,
|
||||
'id': id,
|
||||
'isAdmin': isAdmin,
|
||||
'storageLimit': storageLimit,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -189,6 +207,10 @@ class FamilyData {
|
||||
return members!.map((e) => e.usage).toList().sum;
|
||||
}
|
||||
|
||||
FamilyMember? getMemberByID(String id) {
|
||||
return members!.firstWhereOrNull((element) => element.id == id);
|
||||
}
|
||||
|
||||
static fromMap(Map<String, dynamic>? map) {
|
||||
if (map == null) return null;
|
||||
assert(map['members'] != null && map['members'].length >= 0);
|
||||
@@ -215,19 +237,3 @@ class FamilyData {
|
||||
factory FamilyData.fromJson(String source) =>
|
||||
FamilyData.fromMap(json.decode(source));
|
||||
}
|
||||
|
||||
class FilesCount {
|
||||
final Map<FileType, int> filesCount;
|
||||
FilesCount(this.filesCount);
|
||||
|
||||
int get total =>
|
||||
images + videos + livePhotos + (filesCount[getInt(FileType.other)] ?? 0);
|
||||
|
||||
int get photos => images + livePhotos;
|
||||
|
||||
int get images => filesCount[FileType.image] ?? 0;
|
||||
|
||||
int get videos => filesCount[FileType.video] ?? 0;
|
||||
|
||||
int get livePhotos => filesCount[FileType.livePhoto] ?? 0;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import "dart:io";
|
||||
|
||||
import "package:dio/dio.dart";
|
||||
import "package:ente_crypto/ente_crypto.dart";
|
||||
import "package:ente_feature_flag/ente_feature_flag.dart";
|
||||
import "package:flutter/foundation.dart";
|
||||
import "package:logging/logging.dart";
|
||||
import "package:photos/core/constants.dart";
|
||||
import "package:photos/db/upload_locks_db.dart";
|
||||
import "package:photos/models/encryption_result.dart";
|
||||
import "package:photos/module/upload/model/multipart.dart";
|
||||
import "package:photos/module/upload/model/xml.dart";
|
||||
import "package:photos/service_locator.dart";
|
||||
import "package:photos/services/collections_service.dart";
|
||||
import "package:photos/utils/crypto_util.dart";
|
||||
|
||||
class MultiPartUploader {
|
||||
final Dio _enteDio;
|
||||
|
||||
@@ -8,8 +8,8 @@ import 'package:in_app_purchase/in_app_purchase.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:photos/core/errors.dart';
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import 'package:photos/models/billing_plan.dart';
|
||||
import 'package:photos/models/subscription.dart';
|
||||
import 'package:photos/models/api/billing/billing_plan.dart';
|
||||
import 'package:photos/models/api/billing/subscription.dart';
|
||||
import 'package:photos/models/user_details.dart';
|
||||
import 'package:photos/services/user_service.dart';
|
||||
import 'package:photos/ui/common/web_page.dart';
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:math';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:ente_crypto/ente_crypto.dart';
|
||||
import "package:fast_base58/fast_base58.dart";
|
||||
import 'package:flutter/foundation.dart';
|
||||
import "package:flutter/material.dart";
|
||||
@@ -24,11 +25,11 @@ import 'package:photos/events/local_photos_updated_event.dart';
|
||||
import 'package:photos/extensions/list.dart';
|
||||
import 'package:photos/extensions/stop_watch.dart';
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import 'package:photos/models/api/collection/collection_file_item.dart';
|
||||
import 'package:photos/models/api/collection/create_request.dart';
|
||||
import "package:photos/models/api/collection/public_url.dart";
|
||||
import "package:photos/models/api/collection/user.dart";
|
||||
import 'package:photos/models/collection/collection.dart';
|
||||
import 'package:photos/models/collection/collection_file_item.dart';
|
||||
import 'package:photos/models/collection/collection_items.dart';
|
||||
import 'package:photos/models/file/file.dart';
|
||||
import "package:photos/models/files_split.dart";
|
||||
@@ -39,7 +40,6 @@ import "package:photos/services/favorites_service.dart";
|
||||
import 'package:photos/services/file_magic_service.dart';
|
||||
import 'package:photos/services/local_sync_service.dart';
|
||||
import 'package:photos/services/remote_sync_service.dart';
|
||||
import 'package:photos/utils/crypto_util.dart';
|
||||
import "package:photos/utils/dialog_util.dart";
|
||||
import "package:photos/utils/file_key.dart";
|
||||
import "package:photos/utils/local_settings.dart";
|
||||
@@ -139,7 +139,7 @@ class CollectionsService {
|
||||
}
|
||||
}
|
||||
// remove reference for incoming collections when unshared/deleted
|
||||
if (collection.isDeleted && ownerID != collection.owner?.id) {
|
||||
if (collection.isDeleted && ownerID != collection.owner.id) {
|
||||
await _db.deleteCollection(collection.id);
|
||||
} else {
|
||||
// keep entry for deletedCollection as collectionKey may be used during
|
||||
@@ -394,7 +394,7 @@ class CollectionsService {
|
||||
final List<Collection> collections =
|
||||
getCollectionsForUI(includedShared: true);
|
||||
for (final c in collections) {
|
||||
if (c.owner!.id == Configuration.instance.getUserID()) {
|
||||
if (c.owner.id == Configuration.instance.getUserID()) {
|
||||
if (c.hasSharees || c.hasLink && !c.isQuickLinkCollection()) {
|
||||
outgoing.add(c);
|
||||
} else if (c.isQuickLinkCollection()) {
|
||||
@@ -472,8 +472,8 @@ class CollectionsService {
|
||||
if (collectionID != null) {
|
||||
final Collection? collection = getCollectionByID(collectionID);
|
||||
if (collection != null) {
|
||||
if (collection.owner?.id == userID) {
|
||||
_cachedUserIdToUser[userID] = collection.owner!;
|
||||
if (collection.owner.id == userID) {
|
||||
_cachedUserIdToUser[userID] = collection.owner;
|
||||
} else {
|
||||
final matchingUser = collection.getSharees().firstWhereOrNull(
|
||||
(u) => u.id == userID,
|
||||
@@ -698,7 +698,7 @@ class CollectionsService {
|
||||
);
|
||||
final encryptedKey = CryptoUtil.base642bin(collection.encryptedKey);
|
||||
Uint8List? collectionKey;
|
||||
if (collection.owner?.id == _config.getUserID()) {
|
||||
if (collection.owner.id == _config.getUserID()) {
|
||||
// If the collection is owned by the user, decrypt with the master key
|
||||
if (_config.getKey() == null) {
|
||||
// Possible during AppStore account migration, where SecureStorage
|
||||
@@ -730,7 +730,7 @@ class CollectionsService {
|
||||
await updateMagicMetadata(collection, {"subType": 0});
|
||||
}
|
||||
final encryptedName = CryptoUtil.encryptSync(
|
||||
utf8.encode(newName) as Uint8List,
|
||||
utf8.encode(newName),
|
||||
getCollectionKey(collection.id),
|
||||
);
|
||||
await _enteDio.post(
|
||||
@@ -767,7 +767,7 @@ class CollectionsService {
|
||||
) async {
|
||||
final int ownerID = Configuration.instance.getUserID()!;
|
||||
try {
|
||||
if (collection.owner?.id != ownerID) {
|
||||
if (collection.owner.id != ownerID) {
|
||||
throw AssertionError("cannot modify albums not owned by you");
|
||||
}
|
||||
// read the existing magic metadata and apply new updates to existing data
|
||||
@@ -781,7 +781,7 @@ class CollectionsService {
|
||||
|
||||
final key = getCollectionKey(collection.id);
|
||||
final encryptedMMd = await CryptoUtil.encryptChaCha(
|
||||
utf8.encode(jsonEncode(jsonToUpdate)) as Uint8List,
|
||||
utf8.encode(jsonEncode(jsonToUpdate)),
|
||||
key,
|
||||
);
|
||||
// for required field, the json validator on golang doesn't treat 0 as valid
|
||||
@@ -798,7 +798,7 @@ class CollectionsService {
|
||||
);
|
||||
await _enteDio.put(
|
||||
"/collections/magic-metadata",
|
||||
data: params,
|
||||
data: params.toJson(),
|
||||
);
|
||||
// update the local information so that it's reflected on UI
|
||||
collection.mMdEncodedJson = jsonEncode(jsonToUpdate);
|
||||
@@ -826,7 +826,7 @@ class CollectionsService {
|
||||
) async {
|
||||
final int ownerID = Configuration.instance.getUserID()!;
|
||||
try {
|
||||
if (collection.owner?.id != ownerID) {
|
||||
if (collection.owner.id != ownerID) {
|
||||
throw AssertionError("cannot modify albums not owned by you");
|
||||
}
|
||||
// read the existing magic metadata and apply new updates to existing data
|
||||
@@ -840,7 +840,7 @@ class CollectionsService {
|
||||
|
||||
final key = getCollectionKey(collection.id);
|
||||
final encryptedMMd = await CryptoUtil.encryptChaCha(
|
||||
utf8.encode(jsonEncode(jsonToUpdate)) as Uint8List,
|
||||
utf8.encode(jsonEncode(jsonToUpdate)),
|
||||
key,
|
||||
);
|
||||
// for required field, the json validator on golang doesn't treat 0 as valid
|
||||
@@ -857,7 +857,7 @@ class CollectionsService {
|
||||
);
|
||||
await _enteDio.put(
|
||||
"/collections/public-magic-metadata",
|
||||
data: params,
|
||||
data: params.toJson(),
|
||||
);
|
||||
// update the local information so that it's reflected on UI
|
||||
collection.mMdPubEncodedJson = jsonEncode(jsonToUpdate);
|
||||
@@ -885,7 +885,7 @@ class CollectionsService {
|
||||
) async {
|
||||
final int ownerID = Configuration.instance.getUserID()!;
|
||||
try {
|
||||
if (collection.owner?.id == ownerID) {
|
||||
if (collection.owner.id == ownerID) {
|
||||
throw AssertionError("cannot modify sharee settings for albums owned "
|
||||
"by you");
|
||||
}
|
||||
@@ -900,7 +900,7 @@ class CollectionsService {
|
||||
|
||||
final key = getCollectionKey(collection.id);
|
||||
final encryptedMMd = await CryptoUtil.encryptChaCha(
|
||||
utf8.encode(jsonEncode(jsonToUpdate)) as Uint8List,
|
||||
utf8.encode(jsonEncode(jsonToUpdate)),
|
||||
key,
|
||||
);
|
||||
// for required field, the json validator on golang doesn't treat 0 as valid
|
||||
@@ -917,7 +917,7 @@ class CollectionsService {
|
||||
);
|
||||
await _enteDio.put(
|
||||
"/collections/sharee-magic-metadata",
|
||||
data: params,
|
||||
data: params.toJson(),
|
||||
);
|
||||
// update the local information so that it's reflected on UI
|
||||
collection.sharedMmdJson = jsonEncode(jsonToUpdate);
|
||||
@@ -952,7 +952,7 @@ class CollectionsService {
|
||||
"enableJoin": true,
|
||||
},
|
||||
);
|
||||
collection.publicURLs?.add(PublicURL.fromMap(response.data["result"]));
|
||||
collection.publicURLs.add(PublicURL.fromMap(response.data["result"]));
|
||||
await _db.insert(List.from([collection]));
|
||||
_collectionIDToCollections[collection.id] = collection;
|
||||
Bus.instance.fire(
|
||||
@@ -980,8 +980,8 @@ class CollectionsService {
|
||||
data: json.encode(prop),
|
||||
);
|
||||
// remove existing url information
|
||||
collection.publicURLs?.clear();
|
||||
collection.publicURLs?.add(PublicURL.fromMap(response.data["result"]));
|
||||
collection.publicURLs.clear();
|
||||
collection.publicURLs.add(PublicURL.fromMap(response.data["result"]));
|
||||
await _db.insert(List.from([collection]));
|
||||
_collectionIDToCollections[collection.id] = collection;
|
||||
Bus.instance.fire(
|
||||
@@ -1003,7 +1003,7 @@ class CollectionsService {
|
||||
await _enteDio.delete(
|
||||
"/collections/share-url/" + collection.id.toString(),
|
||||
);
|
||||
collection.publicURLs?.clear();
|
||||
collection.publicURLs.clear();
|
||||
await _db.insert(List.from([collection]));
|
||||
_collectionIDToCollections[collection.id] = collection;
|
||||
Bus.instance.fire(
|
||||
@@ -1271,7 +1271,7 @@ class CollectionsService {
|
||||
final encryptedKeyData =
|
||||
CryptoUtil.encryptSync(collectionKey, _config.getKey()!);
|
||||
final encryptedName = CryptoUtil.encryptSync(
|
||||
utf8.encode(albumName) as Uint8List,
|
||||
utf8.encode(albumName),
|
||||
collectionKey,
|
||||
);
|
||||
final collection = await createAndCacheCollection(
|
||||
@@ -1321,7 +1321,7 @@ class CollectionsService {
|
||||
final encryptedKeyData =
|
||||
CryptoUtil.encryptSync(collectionKey, _config.getKey()!);
|
||||
final encryptedPath =
|
||||
CryptoUtil.encryptSync(utf8.encode(path) as Uint8List, collectionKey);
|
||||
CryptoUtil.encryptSync(utf8.encode(path), collectionKey);
|
||||
final collection = await createAndCacheCollection(
|
||||
CreateRequest(
|
||||
encryptedKey: CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import "package:ente_crypto/ente_crypto.dart";
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import "package:photos/core/configuration.dart";
|
||||
@@ -12,7 +13,6 @@ import "package:photos/models/api/entity/data.dart";
|
||||
import "package:photos/models/api/entity/key.dart";
|
||||
import "package:photos/models/api/entity/type.dart";
|
||||
import "package:photos/models/local_entity_data.dart";
|
||||
import "package:photos/utils/crypto_util.dart";
|
||||
import "package:photos/utils/gzip.dart";
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:ente_crypto/ente_crypto.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photos/core/configuration.dart';
|
||||
import 'package:photos/core/event_bus.dart';
|
||||
@@ -15,7 +15,6 @@ import 'package:photos/models/file/file.dart';
|
||||
import 'package:photos/services/collections_service.dart';
|
||||
import 'package:photos/services/remote_sync_service.dart';
|
||||
import 'package:photos/ui/actions/collection/collection_sharing_actions.dart';
|
||||
import 'package:photos/utils/crypto_util.dart';
|
||||
|
||||
class FavoritesService {
|
||||
late Configuration _config;
|
||||
@@ -230,7 +229,7 @@ class FavoritesService {
|
||||
if (_cachedFavoritesCollectionID == null) {
|
||||
final collections = _collectionsService.getActiveCollections();
|
||||
for (final collection in collections) {
|
||||
if (collection.owner!.id == _config.getUserID() &&
|
||||
if (collection.owner.id == _config.getUserID() &&
|
||||
collection.type == CollectionType.favorites) {
|
||||
_cachedFavoritesCollectionID = collection.id;
|
||||
return collection;
|
||||
@@ -254,7 +253,7 @@ class FavoritesService {
|
||||
final encryptedKeyResult =
|
||||
CryptoUtil.encryptSync(favoriteCollectionKey, _config.getKey()!);
|
||||
final encName = CryptoUtil.encryptSync(
|
||||
utf8.encode("Favorites") as Uint8List,
|
||||
utf8.encode("Favorites"),
|
||||
favoriteCollectionKey,
|
||||
);
|
||||
final collection = await _collectionsService.createAndCacheCollection(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:ente_crypto/ente_crypto.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:photos/core/configuration.dart';
|
||||
import 'package:photos/core/constants.dart';
|
||||
@@ -16,7 +16,6 @@ import 'package:photos/models/file/file.dart';
|
||||
import "package:photos/models/metadata/common_keys.dart";
|
||||
import "package:photos/models/metadata/file_magic.dart";
|
||||
import 'package:photos/services/remote_sync_service.dart';
|
||||
import 'package:photos/utils/crypto_util.dart';
|
||||
import "package:photos/utils/file_key.dart";
|
||||
|
||||
class FileMagicService {
|
||||
@@ -95,7 +94,7 @@ class FileMagicService {
|
||||
|
||||
final fileKey = getFileKey(file);
|
||||
final encryptedMMd = await CryptoUtil.encryptChaCha(
|
||||
utf8.encode(jsonEncode(jsonToUpdate)) as Uint8List,
|
||||
utf8.encode(jsonEncode(jsonToUpdate)),
|
||||
fileKey,
|
||||
);
|
||||
params['metadataList'].add(
|
||||
@@ -161,7 +160,7 @@ class FileMagicService {
|
||||
|
||||
final fileKey = getFileKey(file);
|
||||
final encryptedMMd = await CryptoUtil.encryptChaCha(
|
||||
utf8.encode(jsonEncode(jsonToUpdate)) as Uint8List,
|
||||
utf8.encode(jsonEncode(jsonToUpdate)),
|
||||
fileKey,
|
||||
);
|
||||
params['metadataList'].add(
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import "dart:async";
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:ente_crypto/ente_crypto.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import "package:photos/core/constants.dart";
|
||||
@@ -18,7 +18,6 @@ import "package:photos/models/metadata/collection_magic.dart";
|
||||
import "package:photos/models/metadata/common_keys.dart";
|
||||
import 'package:photos/services/collections_service.dart';
|
||||
import 'package:photos/services/file_magic_service.dart';
|
||||
import 'package:photos/utils/crypto_util.dart';
|
||||
import 'package:photos/utils/dialog_util.dart';
|
||||
|
||||
extension HiddenService on CollectionsService {
|
||||
@@ -32,7 +31,7 @@ extension HiddenService on CollectionsService {
|
||||
final int userID = config.getUserID()!;
|
||||
final allDefaultHidden = collectionIDToCollections.values
|
||||
.where(
|
||||
(element) => element.isDefaultHidden() && element.owner!.id == userID,
|
||||
(element) => element.isDefaultHidden() && element.owner.id == userID,
|
||||
)
|
||||
.toList();
|
||||
|
||||
@@ -101,7 +100,7 @@ extension HiddenService on CollectionsService {
|
||||
collectionIDToCollections.values.firstWhereOrNull(
|
||||
(element) =>
|
||||
element.type == CollectionType.uncategorized &&
|
||||
element.owner!.id == userID,
|
||||
element.owner.id == userID,
|
||||
);
|
||||
if (matchedCollection != null) {
|
||||
cachedUncategorizedCollection = matchedCollection;
|
||||
@@ -166,7 +165,9 @@ extension HiddenService on CollectionsService {
|
||||
await dialog.hide();
|
||||
} on AssertionError catch (e) {
|
||||
await dialog.hide();
|
||||
unawaited(showErrorDialog(context, S.of(context).oops, e.message as String));
|
||||
unawaited(
|
||||
showErrorDialog(context, S.of(context).oops, e.message as String),
|
||||
);
|
||||
return false;
|
||||
} catch (e, s) {
|
||||
_logger.severe("Could not hide", e, s);
|
||||
@@ -214,7 +215,7 @@ extension HiddenService on CollectionsService {
|
||||
final encKey =
|
||||
CryptoUtil.encryptSync(uncategorizedCollectionKey, config.getKey()!);
|
||||
final encName = CryptoUtil.encryptSync(
|
||||
utf8.encode("Uncategorized") as Uint8List,
|
||||
utf8.encode("Uncategorized"),
|
||||
uncategorizedCollectionKey,
|
||||
);
|
||||
final collection = await createAndCacheCollection(
|
||||
@@ -240,7 +241,7 @@ extension HiddenService on CollectionsService {
|
||||
final encryptedKeyData =
|
||||
CryptoUtil.encryptSync(collectionKey, config.getKey()!);
|
||||
final encryptedName = CryptoUtil.encryptSync(
|
||||
utf8.encode(name) as Uint8List,
|
||||
utf8.encode(name),
|
||||
collectionKey,
|
||||
);
|
||||
final jsonToUpdate = CollectionMagicMetadata(
|
||||
@@ -249,7 +250,7 @@ extension HiddenService on CollectionsService {
|
||||
).toJson();
|
||||
assert(jsonToUpdate.length == 2, "metadata should have two keys");
|
||||
final encryptedMMd = await CryptoUtil.encryptChaCha(
|
||||
utf8.encode(jsonEncode(jsonToUpdate)) as Uint8List,
|
||||
utf8.encode(jsonEncode(jsonToUpdate)),
|
||||
collectionKey,
|
||||
);
|
||||
final MetadataRequest metadataRequest = MetadataRequest(
|
||||
|
||||
@@ -57,7 +57,7 @@ class FaceDetectionRelative extends Detection {
|
||||
List<double> get rightMouth => allKeypoints[4];
|
||||
|
||||
FaceDetectionRelative({
|
||||
required double score,
|
||||
required super.score,
|
||||
required List<double> box,
|
||||
required List<List<double>> allKeypoints,
|
||||
}) : assert(
|
||||
@@ -75,8 +75,7 @@ class FaceDetectionRelative extends Detection {
|
||||
(sublist) =>
|
||||
List<double>.from(sublist.map((e) => e.clamp(0.0, 1.0))),
|
||||
)
|
||||
.toList(),
|
||||
super(score: score);
|
||||
.toList();
|
||||
|
||||
void correctForMaintainedAspectRatio(
|
||||
Dimensions originalSize,
|
||||
@@ -252,10 +251,10 @@ class FaceDetectionAbsolute extends Detection {
|
||||
List<double> get rightMouth => allKeypoints[4];
|
||||
|
||||
FaceDetectionAbsolute({
|
||||
required double score,
|
||||
required super.score,
|
||||
required this.box,
|
||||
required this.allKeypoints,
|
||||
}) : super(score: score);
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
|
||||
@@ -193,6 +193,28 @@ class MLIndexingIsolate extends SuperIsolate {
|
||||
}
|
||||
}
|
||||
|
||||
/// WARNING: This method is only for debugging purposes. It should not be used in production.
|
||||
Future<void> debugLoadSingleModel(MLModels model) {
|
||||
return _initModelLock.synchronized(() async {
|
||||
final modelInstance = model.model;
|
||||
if (modelInstance.isInitialized) {
|
||||
_logger.info("Model ${model.name} already loaded");
|
||||
return;
|
||||
}
|
||||
final modelName = modelInstance.modelName;
|
||||
final modelPath = await modelInstance.downloadModelSafe();
|
||||
if (modelPath == null) {
|
||||
_logger.severe("Could not download model, no wifi");
|
||||
return;
|
||||
}
|
||||
final address = await runInIsolate(IsolateOperation.loadModel, {
|
||||
"modelName": modelName,
|
||||
"modelPath": modelPath,
|
||||
}) as int;
|
||||
modelInstance.storeSessionAddress(address);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> cleanupLocalIndexingModels({bool delete = false}) async {
|
||||
if (!areModelsDownloaded) return;
|
||||
await _releaseModels();
|
||||
|
||||
@@ -50,7 +50,7 @@ class PreviewVideoStore {
|
||||
final cacheManager = DefaultCacheManager();
|
||||
final videoCacheManager = VideoCacheManager.instance;
|
||||
|
||||
LinkedHashSet<EnteFile> files = LinkedHashSet();
|
||||
LinkedHashSet<EnteFile> fileQueue = LinkedHashSet();
|
||||
int uploadingFileId = -1;
|
||||
|
||||
final _dio = NetworkClient.instance.enteDio;
|
||||
@@ -58,10 +58,9 @@ class PreviewVideoStore {
|
||||
void init(SharedPreferences prefs) {
|
||||
_prefs = prefs;
|
||||
|
||||
Future.delayed(
|
||||
const Duration(seconds: 10),
|
||||
PreviewVideoStore.instance.putFilesForPreviewCreation,
|
||||
);
|
||||
FileDataService.instance.syncFDStatus().then(
|
||||
(_) => _putFilesForPreviewCreation(),
|
||||
);
|
||||
}
|
||||
|
||||
late final SharedPreferences _prefs;
|
||||
@@ -74,24 +73,27 @@ class PreviewVideoStore {
|
||||
|
||||
Future<void> setIsVideoStreamingEnabled(bool value) async {
|
||||
final oneMonthBack = DateTime.now().subtract(const Duration(days: 30));
|
||||
await _prefs.setBool(_videoStreamingEnabled, value);
|
||||
await _prefs.setInt(
|
||||
_videoStreamingCutoff,
|
||||
oneMonthBack.millisecondsSinceEpoch,
|
||||
);
|
||||
_prefs.setBool(_videoStreamingEnabled, value).ignore();
|
||||
_prefs
|
||||
.setInt(
|
||||
_videoStreamingCutoff,
|
||||
oneMonthBack.millisecondsSinceEpoch,
|
||||
)
|
||||
.ignore();
|
||||
Bus.instance.fire(VideoStreamingChanged());
|
||||
|
||||
if (isVideoStreamingEnabled) {
|
||||
putFilesForPreviewCreation().ignore();
|
||||
await FileDataService.instance.syncFDStatus();
|
||||
_putFilesForPreviewCreation().ignore();
|
||||
} else {
|
||||
clearQueue();
|
||||
}
|
||||
}
|
||||
|
||||
clearQueue() {
|
||||
void clearQueue() {
|
||||
fileQueue.clear();
|
||||
_items.clear();
|
||||
Bus.instance.fire(PreviewUpdatedEvent(_items));
|
||||
files.clear();
|
||||
}
|
||||
|
||||
DateTime? get videoStreamingCutoff {
|
||||
@@ -111,47 +113,42 @@ class PreviewVideoStore {
|
||||
}
|
||||
|
||||
try {
|
||||
if (!enteFile.isUploaded) return;
|
||||
final file = await getFile(enteFile, isOrigin: true);
|
||||
if (file == null) return;
|
||||
if (!enteFile.isUploaded) {
|
||||
_removeFile(enteFile);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// check if playlist already exist
|
||||
await getPlaylist(enteFile);
|
||||
final resultUrl = await getPreviewUrl(enteFile);
|
||||
final _ = await getPreviewUrl(enteFile);
|
||||
|
||||
if (ctx != null && ctx.mounted) {
|
||||
showShortToast(ctx, 'Video preview already exists');
|
||||
}
|
||||
debugPrint("previewUrl $resultUrl");
|
||||
_items.removeWhere((key, value) => value.file == enteFile);
|
||||
Bus.instance.fire(PreviewUpdatedEvent(_items));
|
||||
_removeFile(enteFile);
|
||||
return;
|
||||
} catch (e, s) {
|
||||
if (e is DioError && e.response?.statusCode == 404) {
|
||||
if (e is DioException && e.response?.statusCode == 404) {
|
||||
_logger.info("No preview found for $enteFile");
|
||||
} else {
|
||||
_logger.warning("Failed to get playlist for $enteFile", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
final fileSize = file.lengthSync();
|
||||
FFProbeProps? props;
|
||||
|
||||
if (fileSize <= 10 * 1024 * 1024) {
|
||||
props = await getVideoPropsAsync(file);
|
||||
final videoData = List.from(props?.propData?["streams"] ?? [])
|
||||
.firstWhereOrNull((e) => e["type"] == "video");
|
||||
|
||||
final codec = videoData["codec_name"]?.toString().toLowerCase();
|
||||
final codecIsH264 = codec?.contains("h264") ?? false;
|
||||
if (codecIsH264) {
|
||||
_items.removeWhere((key, value) => value.file == enteFile);
|
||||
Bus.instance.fire(PreviewUpdatedEvent(_items));
|
||||
_retryFile(enteFile, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// elimination case for <=10 MB with H.264
|
||||
var (props, result, file) = await _checkFileForPreviewCreation(enteFile);
|
||||
if (result) {
|
||||
_removeFile(enteFile);
|
||||
return;
|
||||
}
|
||||
|
||||
// check if there is already a preview in processing
|
||||
if (uploadingFileId >= 0) {
|
||||
if (uploadingFileId == enteFile.uploadedFileID) return;
|
||||
|
||||
_items[enteFile.uploadedFileID!] = PreviewItem(
|
||||
status: PreviewItemStatus.inQueue,
|
||||
file: enteFile,
|
||||
@@ -161,9 +158,11 @@ class PreviewVideoStore {
|
||||
collectionID: enteFile.collectionID ?? 0,
|
||||
);
|
||||
Bus.instance.fire(PreviewUpdatedEvent(_items));
|
||||
files.add(enteFile);
|
||||
fileQueue.add(enteFile);
|
||||
return;
|
||||
}
|
||||
|
||||
// everything is fine, let's process
|
||||
uploadingFileId = enteFile.uploadedFileID!;
|
||||
_items[enteFile.uploadedFileID!] = PreviewItem(
|
||||
status: PreviewItemStatus.compressing,
|
||||
@@ -174,16 +173,31 @@ class PreviewVideoStore {
|
||||
);
|
||||
Bus.instance.fire(PreviewUpdatedEvent(_items));
|
||||
|
||||
// get file
|
||||
file ??= await getFile(enteFile, isOrigin: true);
|
||||
if (file == null) {
|
||||
_retryFile(enteFile, "Unable to fetch file");
|
||||
return;
|
||||
}
|
||||
|
||||
// check metadata for bitrate, codec, color space
|
||||
props ??= await getVideoPropsAsync(file);
|
||||
final fileSize = enteFile.fileSize ?? file.lengthSync();
|
||||
|
||||
final videoData = List.from(props?.propData?["streams"] ?? [])
|
||||
.firstWhereOrNull((e) => e["type"] == "video");
|
||||
|
||||
final codec = videoData["codec_name"]?.toString().toLowerCase();
|
||||
final codecIsH264 = codec?.contains("h264") ?? false;
|
||||
|
||||
final bitrate = props?.duration?.inSeconds != null
|
||||
? (fileSize * 8) / props!.duration!.inSeconds
|
||||
: null;
|
||||
|
||||
final colorSpace = videoData["color_space"]?.toString().toLowerCase();
|
||||
final isColorGood = colorSpace == "bt709";
|
||||
|
||||
// create temp file & directory for preview generation
|
||||
final String tempDir = Configuration.instance.getTempDirectory();
|
||||
final String prefix =
|
||||
"${tempDir}_${enteFile.uploadedFileID}_${newID("pv")}";
|
||||
@@ -197,69 +211,65 @@ class PreviewVideoStore {
|
||||
final keyinfo = File('$prefix/mykey.keyinfo');
|
||||
keyinfo.writeAsStringSync("data:text/plain;base64,${key.base64}\n"
|
||||
"${keyfile.path}\n");
|
||||
|
||||
_logger.info(
|
||||
'Generating HLS Playlist ${enteFile.displayName} at $prefix/output.m3u8}',
|
||||
);
|
||||
|
||||
FFmpegSession? session;
|
||||
final colorSpace = videoData["color_space"]?.toString().toLowerCase();
|
||||
final isColorGood = colorSpace == "bt709";
|
||||
final codecIsH264 = codec?.contains("h264") ?? false;
|
||||
|
||||
// case 1, if it's already a good stream
|
||||
if (bitrate != null && bitrate <= 4000 * 1000 && codecIsH264) {
|
||||
// create playlist without compression, as is
|
||||
session = await FFmpegKit.execute(
|
||||
'-i "${file.path}" '
|
||||
'-metadata:s:v:0 rotate=0 ' // Adjust metadata if needed
|
||||
'-c:v copy ' // Copy the original video codec
|
||||
'-c:a copy ' // Copy the original audio codec
|
||||
'-f hls -hls_time 10 -hls_flags single_file '
|
||||
'-metadata:s:v:0 rotate=0 '
|
||||
'-c:v copy -c:a copy '
|
||||
'-f hls -hls_time 2 -hls_flags single_file '
|
||||
'-hls_list_size 0 -hls_key_info_file ${keyinfo.path} '
|
||||
'$prefix/output.m3u8',
|
||||
);
|
||||
} else if (bitrate != null &&
|
||||
} // case 2, if it's bitrate is good, but codec is not
|
||||
else if (bitrate != null &&
|
||||
codec != null &&
|
||||
bitrate <= 2000 * 1000 &&
|
||||
!codecIsH264) {
|
||||
// compress video with crf=21, h264 no change in resolution or frame rate,
|
||||
// just change color scheme
|
||||
session = await FFmpegKit.execute(
|
||||
'-i "${file.path}" '
|
||||
'-metadata:s:v:0 rotate=0 ' // Keep rotation metadata
|
||||
'-vf "format=yuv420p10le,zscale=transfer=linear,tonemap=tonemap=hable:desat=0:peak=10,zscale=transfer=bt709:matrix=bt709:primaries=bt709,format=yuv420p" ' // Adjust color scheme
|
||||
'-color_primaries bt709 -color_trc bt709 -colorspace bt709 ' // Set color profile to BT.709
|
||||
'-c:v libx264 -crf 21 -preset medium ' // Compress with CRF=21 using H.264
|
||||
'-c:a copy ' // Keep original audio
|
||||
'-f hls -hls_time 10 -hls_flags single_file '
|
||||
'-metadata:s:v:0 rotate=0 '
|
||||
'-vf "format=yuv420p10le,zscale=transfer=linear,tonemap=tonemap=hable:desat=0:peak=10,zscale=transfer=bt709:matrix=bt709:primaries=bt709,format=yuv420p" '
|
||||
'-color_primaries bt709 -color_trc bt709 -colorspace bt709 '
|
||||
'-c:v libx264 -crf 23 -preset medium '
|
||||
'-c:a copy '
|
||||
'-f hls -hls_time 2 -hls_flags single_file '
|
||||
'-hls_list_size 0 -hls_key_info_file ${keyinfo.path} '
|
||||
'$prefix/output.m3u8',
|
||||
);
|
||||
}
|
||||
|
||||
if (colorSpace != null && isColorGood) {
|
||||
session ??= await FFmpegKit.execute(
|
||||
} // case 3, if it's color space is good
|
||||
else if (colorSpace != null && isColorGood) {
|
||||
session = await FFmpegKit.execute(
|
||||
'-i "${file.path}" '
|
||||
'-metadata:s:v:0 rotate=0 '
|
||||
'-vf "scale=-2:720,fps=30" '
|
||||
'-c:v libx264 -b:v 2000k -preset medium '
|
||||
'-c:a aac -b:a 128k -f hls -hls_time 10 -hls_flags single_file '
|
||||
'-c:v libx264 -b:v 2000k -crf 23 -preset medium '
|
||||
'-c:a aac -b:a 128k -f hls -hls_time 2 -hls_flags single_file '
|
||||
'-hls_list_size 0 -hls_key_info_file ${keyinfo.path} '
|
||||
'$prefix/output.m3u8',
|
||||
);
|
||||
} // case 4, make it compatible
|
||||
else {
|
||||
session = await FFmpegKit.execute(
|
||||
'-i "${file.path}" '
|
||||
'-metadata:s:v:0 rotate=0 '
|
||||
'-vf "scale=-2:720,fps=30,format=yuv420p10le,zscale=transfer=linear,tonemap=tonemap=hable:desat=0:peak=10,zscale=transfer=bt709:matrix=bt709:primaries=bt709,format=yuv420p" '
|
||||
'-color_primaries bt709 -color_trc bt709 -colorspace bt709 '
|
||||
'-x264-params "colorprim=bt709:transfer=bt709:colormatrix=bt709" '
|
||||
'-c:v libx264 -b:v 2000k -crf 23 -preset medium '
|
||||
'-c:a aac -b:a 128k -f hls -hls_time 2 -hls_flags single_file '
|
||||
'-hls_list_size 0 -hls_key_info_file ${keyinfo.path} '
|
||||
'$prefix/output.m3u8',
|
||||
);
|
||||
}
|
||||
|
||||
session ??= await FFmpegKit.execute(
|
||||
'-i "${file.path}" '
|
||||
'-metadata:s:v:0 rotate=0 '
|
||||
'-vf "scale=-2:720,fps=30,format=yuv420p10le,zscale=transfer=linear,tonemap=tonemap=hable:desat=0:peak=10,zscale=transfer=bt709:matrix=bt709:primaries=bt709,format=yuv420p" '
|
||||
'-color_primaries bt709 -color_trc bt709 -colorspace bt709 '
|
||||
'-x264-params "colorprim=bt709:transfer=bt709:colormatrix=bt709" '
|
||||
'-c:v libx264 -b:v 2000k -preset medium '
|
||||
'-c:a aac -b:a 128k -f hls -hls_time 10 -hls_flags single_file '
|
||||
'-hls_list_size 0 -hls_key_info_file ${keyinfo.path} '
|
||||
'$prefix/output.m3u8',
|
||||
);
|
||||
|
||||
final returnCode = await session.getReturnCode();
|
||||
|
||||
String? error;
|
||||
@@ -275,14 +285,15 @@ class PreviewVideoStore {
|
||||
Bus.instance.fire(PreviewUpdatedEvent(_items));
|
||||
|
||||
_logger.info('Playlist Generated ${enteFile.displayName}');
|
||||
|
||||
final playlistFile = File("$prefix/output.m3u8");
|
||||
final previewFile = File("$prefix/output.ts");
|
||||
final result = await _uploadPreviewVideo(enteFile, previewFile);
|
||||
|
||||
final String objectID = result.$1;
|
||||
final objectSize = result.$2;
|
||||
|
||||
// Logic to fetch width & height of preview
|
||||
//-allowed_extensions ALL -i "https://example.com/stream.m3u8" -frames:v 1 -c copy frame.ts
|
||||
// Fetch resolution of generated stream by decrypting a single frame
|
||||
final FFmpegSession session2 = await FFmpegKit.execute(
|
||||
'-allowed_extensions ALL -i "$prefix/output.m3u8" -frames:v 1 -c copy "$prefix/frame.ts"',
|
||||
);
|
||||
@@ -297,8 +308,8 @@ class PreviewVideoStore {
|
||||
width = props2?.width;
|
||||
height = props2?.height;
|
||||
}
|
||||
} catch (_) {
|
||||
_logger.warning("Failed to get width and height", _);
|
||||
} catch (err, sT) {
|
||||
_logger.warning("Failed to fetch resolution of stream", err, sT);
|
||||
}
|
||||
|
||||
await _reportVideoPreview(
|
||||
@@ -313,7 +324,7 @@ class PreviewVideoStore {
|
||||
_logger.info("Video preview uploaded for $enteFile");
|
||||
} catch (err, sT) {
|
||||
error = "Failed to upload video preview\nError: $err";
|
||||
_logger.shout("Video preview uploaded for $enteFile", err, sT);
|
||||
_logger.shout("Something went wrong with preview upload", err, sT);
|
||||
}
|
||||
} else if (ReturnCode.isCancel(returnCode)) {
|
||||
_logger.warning("FFmpeg command cancelled");
|
||||
@@ -324,14 +335,13 @@ class PreviewVideoStore {
|
||||
"FFmpeg command failed with return code $returnCode",
|
||||
output ?? "Error not found",
|
||||
);
|
||||
if (kDebugMode) {
|
||||
_logger.severe(output);
|
||||
}
|
||||
error = "Failed to generate video preview\nError: $output";
|
||||
}
|
||||
|
||||
if (error == null) {
|
||||
// update previewIds
|
||||
FileDataService.instance.syncFDStatus().ignore();
|
||||
|
||||
_items[enteFile.uploadedFileID!] = PreviewItem(
|
||||
status: PreviewItemStatus.uploaded,
|
||||
file: enteFile,
|
||||
@@ -339,37 +349,49 @@ class PreviewVideoStore {
|
||||
collectionID: enteFile.collectionID ?? 0,
|
||||
);
|
||||
} else {
|
||||
if (_items[enteFile.uploadedFileID!]!.retryCount < 3) {
|
||||
_items[enteFile.uploadedFileID!] = PreviewItem(
|
||||
status: PreviewItemStatus.retry,
|
||||
file: enteFile,
|
||||
retryCount: _items[enteFile.uploadedFileID!]!.retryCount + 1,
|
||||
collectionID: enteFile.collectionID ?? 0,
|
||||
);
|
||||
files.add(enteFile);
|
||||
} else {
|
||||
_items[enteFile.uploadedFileID!] = PreviewItem(
|
||||
status: PreviewItemStatus.failed,
|
||||
file: enteFile,
|
||||
retryCount: _items[enteFile.uploadedFileID!]!.retryCount,
|
||||
collectionID: enteFile.collectionID ?? 0,
|
||||
error: error,
|
||||
);
|
||||
}
|
||||
_retryFile(enteFile, error);
|
||||
}
|
||||
Bus.instance.fire(PreviewUpdatedEvent(_items));
|
||||
} finally {
|
||||
// reset uploading status if this was getting processed
|
||||
if (uploadingFileId == enteFile.uploadedFileID!) {
|
||||
uploadingFileId = -1;
|
||||
}
|
||||
if (files.isNotEmpty) {
|
||||
final file = files.first;
|
||||
files.remove(file);
|
||||
_logger.info("[chunk] Processing ${_items.length} items for streaming");
|
||||
// process next file
|
||||
if (fileQueue.isNotEmpty) {
|
||||
final file = fileQueue.first;
|
||||
fileQueue.remove(file);
|
||||
await chunkAndUploadVideo(ctx, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _removeFile(EnteFile enteFile) {
|
||||
_items.remove(enteFile.uploadedFileID!);
|
||||
Bus.instance.fire(PreviewUpdatedEvent(_items));
|
||||
}
|
||||
|
||||
void _retryFile(EnteFile enteFile, Object error) {
|
||||
if (_items[enteFile.uploadedFileID!]!.retryCount < 3) {
|
||||
_items[enteFile.uploadedFileID!] = PreviewItem(
|
||||
status: PreviewItemStatus.retry,
|
||||
file: enteFile,
|
||||
retryCount: _items[enteFile.uploadedFileID!]!.retryCount + 1,
|
||||
collectionID: enteFile.collectionID ?? 0,
|
||||
);
|
||||
fileQueue.add(enteFile);
|
||||
} else {
|
||||
_items[enteFile.uploadedFileID!] = PreviewItem(
|
||||
status: PreviewItemStatus.failed,
|
||||
file: enteFile,
|
||||
retryCount: _items[enteFile.uploadedFileID!]!.retryCount,
|
||||
collectionID: enteFile.collectionID ?? 0,
|
||||
error: error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _reportVideoPreview(
|
||||
EnteFile file,
|
||||
File playlist, {
|
||||
@@ -539,7 +561,7 @@ class PreviewVideoStore {
|
||||
final previewURL = response2.data["url"];
|
||||
if (objectKey != null) {
|
||||
unawaited(
|
||||
downloadAndCacheVideo(
|
||||
_downloadAndCacheVideo(
|
||||
previewURL,
|
||||
_getVideoPreviewKey(objectKey),
|
||||
),
|
||||
@@ -568,7 +590,7 @@ class PreviewVideoStore {
|
||||
}
|
||||
}
|
||||
|
||||
Future downloadAndCacheVideo(String url, String key) async {
|
||||
Future _downloadAndCacheVideo(String url, String key) async {
|
||||
final file = await videoCacheManager.downloadFile(url, key: key);
|
||||
return file;
|
||||
}
|
||||
@@ -590,9 +612,35 @@ class PreviewVideoStore {
|
||||
}
|
||||
}
|
||||
|
||||
// get all files after cutoff date and add it to queue for preview creation
|
||||
// only run when video streaming is enabled
|
||||
Future<void> putFilesForPreviewCreation() async {
|
||||
Future<(FFProbeProps?, bool, File?)> _checkFileForPreviewCreation(
|
||||
EnteFile enteFile,
|
||||
) async {
|
||||
final fileSize = enteFile.fileSize;
|
||||
FFProbeProps? props;
|
||||
File? file;
|
||||
bool result = false;
|
||||
|
||||
try {
|
||||
final isFileUnder10MB = fileSize != null && fileSize <= 10 * 1024 * 1024;
|
||||
if (isFileUnder10MB) {
|
||||
file = await getFile(enteFile, isOrigin: true);
|
||||
if (file != null) {
|
||||
props = await getVideoPropsAsync(file);
|
||||
final videoData = List.from(props?.propData?["streams"] ?? [])
|
||||
.firstWhereOrNull((e) => e["type"] == "video");
|
||||
|
||||
final codec = videoData["codec_name"]?.toString().toLowerCase();
|
||||
result = codec?.contains("h264") ?? false;
|
||||
}
|
||||
}
|
||||
} catch (e, sT) {
|
||||
_logger.warning("Failed to check props", e, sT);
|
||||
}
|
||||
return (props, result, file);
|
||||
}
|
||||
|
||||
// generate stream for all files after cutoff date
|
||||
Future<void> _putFilesForPreviewCreation() async {
|
||||
if (!isVideoStreamingEnabled) return;
|
||||
|
||||
final cutoff = videoStreamingCutoff;
|
||||
@@ -601,28 +649,44 @@ class PreviewVideoStore {
|
||||
final files = await FilesDB.instance.getAllFilesAfterDate(
|
||||
fileType: FileType.video,
|
||||
beginDate: cutoff,
|
||||
userID: Configuration.instance.getUserID()!,
|
||||
);
|
||||
|
||||
final previewIds = FileDataService.instance.previewIds;
|
||||
final allFiles = files
|
||||
.where((file) => previewIds?[file.uploadedFileID] == null)
|
||||
.toList();
|
||||
.sorted((a, b) {
|
||||
// put higher duration videos last along with remote files
|
||||
final first = (a.localID == null ? 2 : 0) +
|
||||
(a.duration == null || a.duration! >= 10 * 60 ? 1 : 0);
|
||||
final second = (b.localID == null ? 2 : 0) +
|
||||
(b.duration == null || b.duration! >= 10 * 60 ? 1 : 0);
|
||||
return first.compareTo(second);
|
||||
}).toList();
|
||||
|
||||
// set all video status to be in queue
|
||||
for (final file in allFiles) {
|
||||
_items[file.uploadedFileID!] = PreviewItem(
|
||||
status: PreviewItemStatus.inQueue,
|
||||
file: file,
|
||||
collectionID: file.collectionID ?? 0,
|
||||
);
|
||||
// set all video status to in queue
|
||||
final n = allFiles.length;
|
||||
for (int i = 0; i < n; i++) {
|
||||
final enteFile = allFiles[i];
|
||||
// elimination case for <=10 MB with H.264
|
||||
final (_, result, _) = await _checkFileForPreviewCreation(enteFile);
|
||||
if (result) {
|
||||
allFiles.removeAt(i);
|
||||
} else {
|
||||
_items[enteFile.uploadedFileID!] = PreviewItem(
|
||||
status: PreviewItemStatus.inQueue,
|
||||
file: enteFile,
|
||||
collectionID: enteFile.collectionID ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
Bus.instance.fire(PreviewUpdatedEvent(_items));
|
||||
|
||||
final file = allFiles.first;
|
||||
allFiles.remove(file);
|
||||
|
||||
this.files.addAll(allFiles);
|
||||
_logger.info("[init] Processing ${allFiles.length} items for streaming");
|
||||
|
||||
// take first file and put it for stream generation
|
||||
final file = allFiles.removeAt(0);
|
||||
fileQueue.addAll(allFiles);
|
||||
await chunkAndUploadVideo(null, file);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:photos/core/configuration.dart';
|
||||
|
||||
@@ -9,10 +9,10 @@ import 'package:photos/events/collection_updated_event.dart';
|
||||
import 'package:photos/events/force_reload_trash_page_event.dart';
|
||||
import 'package:photos/events/trash_updated_event.dart';
|
||||
import 'package:photos/extensions/list.dart';
|
||||
import 'package:photos/models/api/collection/trash_item_request.dart';
|
||||
import 'package:photos/models/file/file.dart';
|
||||
import 'package:photos/models/file/trash_file.dart';
|
||||
import 'package:photos/models/ignored_file.dart';
|
||||
import 'package:photos/models/trash_item_request.dart';
|
||||
import 'package:photos/services/ignored_files_service.dart';
|
||||
import 'package:photos/utils/trash_diff_fetcher.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
@@ -4,6 +4,7 @@ import "dart:math";
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:ente_crypto/ente_crypto.dart';
|
||||
import "package:flutter/foundation.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -19,13 +20,13 @@ import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/l10n/l10n.dart";
|
||||
import "package:photos/models/account/two_factor.dart";
|
||||
import "package:photos/models/api/collection/user.dart";
|
||||
import 'package:photos/models/api/user/delete_account.dart';
|
||||
import 'package:photos/models/api/user/key_attributes.dart';
|
||||
import 'package:photos/models/api/user/key_gen_result.dart';
|
||||
import 'package:photos/models/api/user/sessions.dart';
|
||||
import 'package:photos/models/api/user/set_keys_request.dart';
|
||||
import 'package:photos/models/api/user/set_recovery_key_request.dart';
|
||||
import "package:photos/models/api/user/srp.dart";
|
||||
import 'package:photos/models/delete_account.dart';
|
||||
import 'package:photos/models/key_attributes.dart';
|
||||
import 'package:photos/models/key_gen_result.dart';
|
||||
import 'package:photos/models/sessions.dart';
|
||||
import 'package:photos/models/set_keys_request.dart';
|
||||
import 'package:photos/models/set_recovery_key_request.dart';
|
||||
import 'package:photos/models/user_details.dart';
|
||||
import "package:photos/services/collections_service.dart";
|
||||
import "package:photos/services/machine_learning/face_ml/person/person_service.dart";
|
||||
@@ -40,7 +41,6 @@ import 'package:photos/ui/account/two_factor_recovery_page.dart';
|
||||
import 'package:photos/ui/account/two_factor_setup_page.dart';
|
||||
import "package:photos/ui/common/progress_dialog.dart";
|
||||
import "package:photos/ui/tabs/home_widget.dart";
|
||||
import 'package:photos/utils/crypto_util.dart';
|
||||
import 'package:photos/utils/dialog_util.dart';
|
||||
import 'package:photos/utils/navigation_util.dart';
|
||||
import 'package:photos/utils/toast_util.dart';
|
||||
@@ -695,7 +695,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,
|
||||
@@ -1311,34 +1311,30 @@ class UserService {
|
||||
|
||||
for (final c in CollectionsService.instance.getActiveCollections()) {
|
||||
// Add collaborators and viewers of collections owned by user
|
||||
if (c.owner?.id == ownerID) {
|
||||
for (final User? u in c.sharees ?? []) {
|
||||
if (u != null && u.id != null && u.email.isNotEmpty) {
|
||||
if (c.owner.id == ownerID) {
|
||||
for (final User u in c.sharees) {
|
||||
if (u.id != null && u.email.isNotEmpty) {
|
||||
if (!existingEmails.contains(u.email)) {
|
||||
relevantUsers.add(u);
|
||||
existingEmails.add(u.email);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (c.owner?.id != null && c.owner!.email.isNotEmpty) {
|
||||
} else if (c.owner.id != null && c.owner.email.isNotEmpty) {
|
||||
// Add owners of collections shared with user
|
||||
if (!existingEmails.contains(c.owner!.email)) {
|
||||
relevantUsers.add(c.owner!);
|
||||
existingEmails.add(c.owner!.email);
|
||||
if (!existingEmails.contains(c.owner.email)) {
|
||||
relevantUsers.add(c.owner);
|
||||
existingEmails.add(c.owner.email);
|
||||
}
|
||||
// Add collaborators of collections shared with user where user is a
|
||||
// viewer or a collaborator
|
||||
for (final User? u in c.sharees ?? []) {
|
||||
if (u != null &&
|
||||
u.id != null &&
|
||||
for (final User u in c.sharees) {
|
||||
if (u.id != null &&
|
||||
u.email.isNotEmpty &&
|
||||
u.email == ownerEmail &&
|
||||
(u.isCollaborator || u.isViewer)) {
|
||||
for (final User? u in c.sharees ?? []) {
|
||||
if (u != null &&
|
||||
u.id != null &&
|
||||
u.email.isNotEmpty &&
|
||||
u.isCollaborator) {
|
||||
for (final User u in c.sharees) {
|
||||
if (u.id != null && u.email.isNotEmpty && u.isCollaborator) {
|
||||
if (!existingEmails.contains(u.email)) {
|
||||
relevantUsers.add(u);
|
||||
existingEmails.add(u.email);
|
||||
@@ -1392,32 +1388,28 @@ class UserService {
|
||||
|
||||
for (final c in CollectionsService.instance.getActiveCollections()) {
|
||||
// Add collaborators and viewers of collections owned by user
|
||||
if (c.owner?.id == ownerID) {
|
||||
for (final User? u in c.sharees ?? []) {
|
||||
if (u != null && u.id != null && u.email.isNotEmpty) {
|
||||
if (c.owner.id == ownerID) {
|
||||
for (final User u in c.sharees) {
|
||||
if (u.id != null && u.email.isNotEmpty) {
|
||||
if (!emailIDs.contains(u.email)) {
|
||||
emailIDs.add(u.email);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (c.owner?.id != null && c.owner!.email.isNotEmpty) {
|
||||
} else if (c.owner.id != null && c.owner.email.isNotEmpty) {
|
||||
// Add owners of collections shared with user
|
||||
if (!emailIDs.contains(c.owner!.email)) {
|
||||
emailIDs.add(c.owner!.email);
|
||||
if (!emailIDs.contains(c.owner.email)) {
|
||||
emailIDs.add(c.owner.email);
|
||||
}
|
||||
// Add collaborators of collections shared with user where user is a
|
||||
// viewer or a collaborator
|
||||
for (final User? u in c.sharees ?? []) {
|
||||
if (u != null &&
|
||||
u.id != null &&
|
||||
for (final User u in c.sharees) {
|
||||
if (u.id != null &&
|
||||
u.email.isNotEmpty &&
|
||||
u.email == ownerEmail &&
|
||||
(u.isCollaborator || u.isViewer)) {
|
||||
for (final User? u in c.sharees ?? []) {
|
||||
if (u != null &&
|
||||
u.id != null &&
|
||||
u.email.isNotEmpty &&
|
||||
u.isCollaborator) {
|
||||
for (final User u in c.sharees) {
|
||||
if (u.id != null && u.email.isNotEmpty && u.isCollaborator) {
|
||||
if (!emailIDs.contains(u.email)) {
|
||||
emailIDs.add(u.email);
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ class UserDetailsStateWidget extends StatefulWidget {
|
||||
|
||||
const UserDetailsStateWidget({
|
||||
required this.child,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<UserDetailsStateWidget> createState() => UserDetailsStateWidgetState();
|
||||
@@ -65,12 +65,12 @@ class InheritedUserDetails extends InheritedWidget {
|
||||
final bool isCached;
|
||||
|
||||
const InheritedUserDetails({
|
||||
Key? key,
|
||||
required Widget child,
|
||||
super.key,
|
||||
required super.child,
|
||||
required this.userDetails,
|
||||
required this.isCached,
|
||||
required this.userDetailsState,
|
||||
}) : super(key: key, child: child);
|
||||
});
|
||||
|
||||
static InheritedUserDetails? of(BuildContext context) =>
|
||||
context.dependOnInheritedWidgetOfExactType<InheritedUserDetails>();
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:photos/utils/dialog_util.dart';
|
||||
import 'package:photos/utils/email_util.dart';
|
||||
|
||||
class ChangeEmailDialog extends StatefulWidget {
|
||||
const ChangeEmailDialog({Key? key}) : super(key: key);
|
||||
const ChangeEmailDialog({super.key});
|
||||
|
||||
@override
|
||||
State<ChangeEmailDialog> createState() => _ChangeEmailDialogState();
|
||||
|
||||
@@ -2,16 +2,16 @@ import "dart:async";
|
||||
import 'dart:convert';
|
||||
|
||||
import "package:dropdown_button2/dropdown_button2.dart";
|
||||
import 'package:ente_crypto/ente_crypto.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:logging/logging.dart";
|
||||
import 'package:photos/core/configuration.dart';
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import 'package:photos/models/delete_account.dart';
|
||||
import 'package:photos/models/api/user/delete_account.dart';
|
||||
import 'package:photos/services/user_service.dart';
|
||||
import 'package:photos/theme/ente_theme.dart';
|
||||
import 'package:photos/ui/components/buttons/button_widget.dart';
|
||||
import 'package:photos/ui/components/models/button_type.dart';
|
||||
import 'package:photos/utils/crypto_util.dart';
|
||||
import 'package:photos/utils/dialog_util.dart';
|
||||
import "package:photos/utils/toast_util.dart";
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import 'package:photos/ui/common/web_page.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();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import "package:dio/dio.dart";
|
||||
import "package:ente_crypto/ente_crypto.dart";
|
||||
import "package:flutter/foundation.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:logging/logging.dart";
|
||||
import 'package:photos/core/configuration.dart';
|
||||
import "package:photos/core/errors.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/models/api/user/srp.dart";
|
||||
import "package:photos/services/user_service.dart";
|
||||
@@ -21,8 +21,7 @@ import "package:photos/utils/email_util.dart";
|
||||
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() =>
|
||||
|
||||
@@ -18,8 +18,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();
|
||||
|
||||