Compare commits
339 Commits
cli-v0.2.0
...
auth-v4.0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d0f44b1da | ||
|
|
c656a1c6a4 | ||
|
|
14a3f426ce | ||
|
|
cfe83a40e5 | ||
|
|
c90feccfa4 | ||
|
|
06a55bc11a | ||
|
|
aa8d910a0b | ||
|
|
75362199e5 | ||
|
|
006eed1cd8 | ||
|
|
9dd18f0137 | ||
|
|
40f34417d9 | ||
|
|
8c722c39ec | ||
|
|
4e6dd14c71 | ||
|
|
34edc9b2e8 | ||
|
|
51f64799aa | ||
|
|
4791b10d91 | ||
|
|
c595c88e82 | ||
|
|
6657695858 | ||
|
|
bc5be62de4 | ||
|
|
78fd9cc6e6 | ||
|
|
29aa608399 | ||
|
|
310e319057 | ||
|
|
bef3d0949c | ||
|
|
e45baf6238 | ||
|
|
454f93fadb | ||
|
|
76a6b7402c | ||
|
|
9d4c5b29b3 | ||
|
|
ae50c83960 | ||
|
|
c6fdd6352e | ||
|
|
bed57c083c | ||
|
|
cda5caec3f | ||
|
|
01d48b0fcc | ||
|
|
4780f545fe | ||
|
|
80ed1e47b0 | ||
|
|
5c7f775f14 | ||
|
|
7ff5d40152 | ||
|
|
b4dbd942ab | ||
|
|
627a53428a | ||
|
|
35581099fc | ||
|
|
e62dfe0a07 | ||
|
|
21ae075674 | ||
|
|
2660cee263 | ||
|
|
2ae8a11138 | ||
|
|
70c62a62a6 | ||
|
|
a43599aad2 | ||
|
|
5bcce4d610 | ||
|
|
d61d9b95ad | ||
|
|
682d97f15a | ||
|
|
46b210c600 | ||
|
|
4f5f90259d | ||
|
|
d8d5e62888 | ||
|
|
2cef85dfd9 | ||
|
|
31ef1e4e29 | ||
|
|
952e9254b4 | ||
|
|
8e7ec58f45 | ||
|
|
7422568093 | ||
|
|
39b09abc50 | ||
|
|
8b3315e85f | ||
|
|
19273927d1 | ||
|
|
374625f5b3 | ||
|
|
2fae026a77 | ||
|
|
01488be836 | ||
|
|
953c915508 | ||
|
|
9a0ea3ac43 | ||
|
|
42d36b35d2 | ||
|
|
630b6d4101 | ||
|
|
72ba8bcd64 | ||
|
|
93f753fdff | ||
|
|
27defa92c2 | ||
|
|
6ba9ed9f8d | ||
|
|
6c5c3131f5 | ||
|
|
bb23c750a5 | ||
|
|
c036c8c7ba | ||
|
|
6464bf172f | ||
|
|
46e6af660e | ||
|
|
b18d987ba2 | ||
|
|
6b12f0a595 | ||
|
|
40b2b725b4 | ||
|
|
00346db9f9 | ||
|
|
78826d7782 | ||
|
|
31f591c28f | ||
|
|
0b0d8bd026 | ||
|
|
76e4d535b4 | ||
|
|
7f00b2619e | ||
|
|
6a592af94a | ||
|
|
bea6e8b473 | ||
|
|
7cfdd6ec55 | ||
|
|
b97cf93c12 | ||
|
|
2865113c4d | ||
|
|
ee1dbd7e84 | ||
|
|
5a21c932df | ||
|
|
c4021a82f5 | ||
|
|
6e209a68e2 | ||
|
|
090e2f235b | ||
|
|
a536d6af63 | ||
|
|
53cf029f00 | ||
|
|
48d6c2d008 | ||
|
|
28095ca935 | ||
|
|
d31d7592cb | ||
|
|
7645ec0e24 | ||
|
|
5c0fe9c411 | ||
|
|
80416d5b90 | ||
|
|
9ce432681e | ||
|
|
e91cddbc25 | ||
|
|
a37dcceb41 | ||
|
|
aa31e4354c | ||
|
|
a92115ffb1 | ||
|
|
7f0d028b55 | ||
|
|
628edf713b | ||
|
|
4f74470abe | ||
|
|
b5ad13ee69 | ||
|
|
4383512540 | ||
|
|
59a68b56bc | ||
|
|
09c4b19449 | ||
|
|
d4cd71b56c | ||
|
|
a01ea511e9 | ||
|
|
ab6f514d23 | ||
|
|
b4d8dea2ef | ||
|
|
2abd0b0588 | ||
|
|
dae0492800 | ||
|
|
94a8ff2c6f | ||
|
|
aae00dcc15 | ||
|
|
18c7d59f90 | ||
|
|
fcf87d237b | ||
|
|
f88022730a | ||
|
|
753ed30d5c | ||
|
|
216be38915 | ||
|
|
164ace9f8c | ||
|
|
a97bb195b6 | ||
|
|
9ecb7c4044 | ||
|
|
1895e90b3e | ||
|
|
f118a9d2f2 | ||
|
|
bb5261f73b | ||
|
|
225dade722 | ||
|
|
c39a3c789d | ||
|
|
28580cf107 | ||
|
|
6799ee3832 | ||
|
|
d86df11f15 | ||
|
|
7b04bd548a | ||
|
|
ffaf4659ce | ||
|
|
8fc38244f3 | ||
|
|
415cf451a8 | ||
|
|
f4709d3442 | ||
|
|
0f6c8a6441 | ||
|
|
f337b1ff36 | ||
|
|
2799652d3a | ||
|
|
6b92acbb11 | ||
|
|
a343fe5427 | ||
|
|
d10e37454d | ||
|
|
1cb80e619b | ||
|
|
d307b4bf07 | ||
|
|
5967f2a66a | ||
|
|
6e6248a3c2 | ||
|
|
aff37dc5df | ||
|
|
b2b4a703ac | ||
|
|
46f9a16db9 | ||
|
|
d232c94547 | ||
|
|
dc6217eb7a | ||
|
|
98123438fa | ||
|
|
4c51c960f2 | ||
|
|
a89d7e472f | ||
|
|
ef1b192ced | ||
|
|
19d82d332c | ||
|
|
0d97355838 | ||
|
|
04b296587a | ||
|
|
3193778c98 | ||
|
|
4a7ec88424 | ||
|
|
201bda3999 | ||
|
|
50ab94a355 | ||
|
|
790f34662a | ||
|
|
d0138310dc | ||
|
|
cb66494924 | ||
|
|
a322a28e44 | ||
|
|
a95a21790b | ||
|
|
e8d0673a9a | ||
|
|
f8f4db409b | ||
|
|
f4e260cfb3 | ||
|
|
e605169ac1 | ||
|
|
811fe93dcc | ||
|
|
6d7a6b86f7 | ||
|
|
5760b6a56b | ||
|
|
6b04ef69ed | ||
|
|
2f0d06cad7 | ||
|
|
08ee4e2861 | ||
|
|
282b440d0f | ||
|
|
c9e29dbcbe | ||
|
|
ca62fb5105 | ||
|
|
58ca0a5cd4 | ||
|
|
3ef7185166 | ||
|
|
b92e9f4c6e | ||
|
|
2f2eb4e265 | ||
|
|
17aa385782 | ||
|
|
0ac6ea9af8 | ||
|
|
fce4c9869e | ||
|
|
1eca25b050 | ||
|
|
47a0ee749e | ||
|
|
1c86a69cd6 | ||
|
|
ce8310c874 | ||
|
|
e607b4e3ed | ||
|
|
498a4dcd4e | ||
|
|
624726734f | ||
|
|
4b4350c107 | ||
|
|
ecb785d75c | ||
|
|
2c1637c55c | ||
|
|
31f210da31 | ||
|
|
777f298ca3 | ||
|
|
c5c86c484e | ||
|
|
e9b3e76b50 | ||
|
|
15e31f1763 | ||
|
|
fffebec025 | ||
|
|
35957b1f40 | ||
|
|
6ee7500011 | ||
|
|
803195afdd | ||
|
|
2c23c3463e | ||
|
|
39c5a3f1bd | ||
|
|
c625593162 | ||
|
|
5864648c24 | ||
|
|
b605e41f9e | ||
|
|
ffc082eec4 | ||
|
|
692e12979b | ||
|
|
9ff6a79ada | ||
|
|
530ae6c0be | ||
|
|
1aab753046 | ||
|
|
3e131464e6 | ||
|
|
1aa940d410 | ||
|
|
33bc19978c | ||
|
|
4670c1d712 | ||
|
|
ff32e6852e | ||
|
|
b39e727e0a | ||
|
|
2a1931157c | ||
|
|
15370fa731 | ||
|
|
b65a7055c1 | ||
|
|
1a336769b9 | ||
|
|
aa6eebca27 | ||
|
|
c41ad8b1a9 | ||
|
|
9ca651f4b7 | ||
|
|
255302e3cd | ||
|
|
a4fa8e0deb | ||
|
|
ffa1d90ed8 | ||
|
|
ef6734195f | ||
|
|
50a0dc754b | ||
|
|
19eb1bdb22 | ||
|
|
f11493842e | ||
|
|
b7a8e33665 | ||
|
|
abce21d819 | ||
|
|
2db43536f7 | ||
|
|
ee685bcc5d | ||
|
|
6a489ad520 | ||
|
|
3c61b49548 | ||
|
|
6a24528ed0 | ||
|
|
ebc40d1b65 | ||
|
|
405c0c343f | ||
|
|
f8b77f71b5 | ||
|
|
ed907c71f8 | ||
|
|
94513e8c8e | ||
|
|
f3e98cff34 | ||
|
|
49e7c4baaf | ||
|
|
29a72ac4a1 | ||
|
|
2722b50cc0 | ||
|
|
36079aa2dc | ||
|
|
891af00454 | ||
|
|
783c0c48ef | ||
|
|
ed1c9df007 | ||
|
|
f64c0dcc86 | ||
|
|
9d1332bff1 | ||
|
|
000fe87ebb | ||
|
|
6344a3c640 | ||
|
|
18a0b18a13 | ||
|
|
d4cdfc8834 | ||
|
|
aa2b81ad7e | ||
|
|
df17b11573 | ||
|
|
8ca3b80e94 | ||
|
|
345cc2f34f | ||
|
|
c4f70c370e | ||
|
|
e8b692b5ad | ||
|
|
7b552a1ee3 | ||
|
|
5d6ac29d71 | ||
|
|
8a031360c5 | ||
|
|
c9fd0183e7 | ||
|
|
6753f1e9f7 | ||
|
|
806098961b | ||
|
|
21e45e8138 | ||
|
|
1de1273391 | ||
|
|
8ea7481a98 | ||
|
|
12da709445 | ||
|
|
5c601ab2cc | ||
|
|
87bdab027e | ||
|
|
50f4878d0f | ||
|
|
523336d644 | ||
|
|
4b7104bf4e | ||
|
|
6a8ca4c2cf | ||
|
|
2e6c7d29e4 | ||
|
|
b7f86b3e89 | ||
|
|
384b4d2c35 | ||
|
|
e6d7d2298c | ||
|
|
6139ed45cd | ||
|
|
6662f51a5f | ||
|
|
1108fa9f79 | ||
|
|
2b02ea7409 | ||
|
|
cdca58eb3c | ||
|
|
0381dee786 | ||
|
|
1c727131ad | ||
|
|
944070eb23 | ||
|
|
f2b86ff1e1 | ||
|
|
a14160f799 | ||
|
|
dcca546e5a | ||
|
|
bb0bdf113e | ||
|
|
a323c7b31b | ||
|
|
2d46b70d8f | ||
|
|
e695f2eccb | ||
|
|
1942935c3c | ||
|
|
cef85ddd9f | ||
|
|
341ef58970 | ||
|
|
983cfe4482 | ||
|
|
2ae23dfa3d | ||
|
|
b269fddac2 | ||
|
|
ca5be3518b | ||
|
|
b85a90e5dd | ||
|
|
a4c47ffbd4 | ||
|
|
4ee9815971 | ||
|
|
5f873a0f7b | ||
|
|
d02da225f8 | ||
|
|
a8c7dd52ba | ||
|
|
84900159ae | ||
|
|
6ed0ad806e | ||
|
|
c1b6458e2e | ||
|
|
a7cc96d994 | ||
|
|
2a483edbe4 | ||
|
|
428d786f10 | ||
|
|
7d66b4c29f | ||
|
|
53b7ea6203 | ||
|
|
1e8b184ed0 | ||
|
|
daa9e01729 | ||
|
|
a6ab51727c | ||
|
|
4fe7ec6257 | ||
|
|
bdacd1058e | ||
|
|
7fb31eee0a | ||
|
|
f1adcd4573 | ||
|
|
130b2757a9 |
5
.github/workflows/auth-release.yml
vendored
@@ -45,6 +45,11 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup JDK 17
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 17
|
||||
|
||||
- name: Install Flutter ${{ env.FLUTTER_VERSION }}
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
|
||||
@@ -70,7 +70,7 @@ want to give back, please check out Ente Photos or spread the word.
|
||||
[<img height="42" src=".github/assets/app-store-badge.svg">](https://apps.apple.com/app/id6444121398)
|
||||
[<img height="42" src=".github/assets/play-store-badge.png">](https://play.google.com/store/apps/details?id=io.ente.auth)
|
||||
[<img height="42" src=".github/assets/f-droid-badge.png">](https://f-droid.org/packages/io.ente.auth/)
|
||||
[<img height="42" src=".github/assets/desktop-badge.png">](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v3)
|
||||
[<img height="42" src=".github/assets/desktop-badge.png">](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v4)
|
||||
[<img height="42" src=".github/assets/web-badge.svg">](https://auth.ente.io)
|
||||
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@ multi-device sync.
|
||||
### Android
|
||||
|
||||
This repository's [GitHub
|
||||
releases](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v3)
|
||||
releases](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v4)
|
||||
contains APKs, built straight from source. These builds keep themselves updated,
|
||||
without relying on third party stores.
|
||||
|
||||
@@ -33,7 +33,7 @@ You can alternatively install the build from PlayStore or F-Droid.
|
||||
|
||||
### Desktop
|
||||
|
||||
You can [**download**](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v3)
|
||||
You can [**download**](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v4)
|
||||
a native desktop app from this repository's GitHub releases. The desktop app
|
||||
works on Windows, Linux and macOS.
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
{
|
||||
"title": "1xBet"
|
||||
},
|
||||
{
|
||||
"title": "23andme"
|
||||
},
|
||||
{
|
||||
"title": "3Commas"
|
||||
},
|
||||
@@ -119,6 +122,9 @@
|
||||
{
|
||||
"title": "Bybit"
|
||||
},
|
||||
{
|
||||
"title": "Canva"
|
||||
},
|
||||
{
|
||||
"title": "Capacities"
|
||||
},
|
||||
@@ -139,6 +145,9 @@
|
||||
{
|
||||
"title": "Cloudflare"
|
||||
},
|
||||
{
|
||||
"title": "Coinbase"
|
||||
},
|
||||
{
|
||||
"title": "CoinDCX"
|
||||
},
|
||||
@@ -188,6 +197,9 @@
|
||||
"title": "dus.net",
|
||||
"slug": "dusnet"
|
||||
},
|
||||
{
|
||||
"title": "eBay"
|
||||
},
|
||||
{
|
||||
"title": "ecitizen kenya",
|
||||
"slug": "ecitizen_kenya"
|
||||
@@ -223,6 +235,9 @@
|
||||
"title": "Firefox",
|
||||
"slug": "mozilla"
|
||||
},
|
||||
{
|
||||
"title": "ForUsAll"
|
||||
},
|
||||
{
|
||||
"title": "G2A"
|
||||
},
|
||||
@@ -252,6 +267,12 @@
|
||||
"Government Gateway"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Guideline"
|
||||
},
|
||||
{
|
||||
"title": "Gusto"
|
||||
},
|
||||
{
|
||||
"title": "Habbo"
|
||||
},
|
||||
@@ -274,6 +295,10 @@
|
||||
{
|
||||
"title": "IceDrive"
|
||||
},
|
||||
{
|
||||
"title": "ID.me",
|
||||
"slug": "IDme"
|
||||
},
|
||||
{
|
||||
"title": "Infomaniak"
|
||||
},
|
||||
@@ -343,6 +368,10 @@
|
||||
"Local Wordpress"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Login.gov",
|
||||
"slug": "login_gov"
|
||||
},
|
||||
{
|
||||
"title": "Marketplace.tf",
|
||||
"slug": "marketplacedottf"
|
||||
@@ -586,6 +615,12 @@
|
||||
"title": "Synology DSM",
|
||||
"slug": "synology_dsm"
|
||||
},
|
||||
{
|
||||
"title": "T-Mobile",
|
||||
"altNames": [
|
||||
"T Mobile"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "TCPShield"
|
||||
},
|
||||
@@ -607,6 +642,9 @@
|
||||
"title": "Termius",
|
||||
"hex": "858585"
|
||||
},
|
||||
{
|
||||
"title": "Titan"
|
||||
},
|
||||
{
|
||||
"title": "TorGuard"
|
||||
},
|
||||
@@ -698,4 +736,4 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
1
auth/assets/custom-icons/icons/23andme.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 90 90" xmlns="http://www.w3.org/2000/svg"><g transform="translate(26.145 5.696)" fill="none" fill-rule="evenodd"><path d="M7.824 10.031a6 6 0 0 1 7.492 3.984L32.42 69.958a6 6 0 1 1-11.476 3.509L3.84 17.523a6 6 0 0 1 3.984-7.492Z" fill="#D91A62"/><rect fill="#7BC144" transform="rotate(26 20.845 34.313)" x="14.845" y="-.937" width="12" height="70.5" rx="6"/><path fill="#49A848" d="m18.689 25.047 5.144 16.826-7.713 15.815-5.144-16.827 7.713-15.814z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 480 B |
6
auth/assets/custom-icons/icons/IDme.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="126" height="45" viewBox="0 0 126 45" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.76597 0.337891H4.59152C1.87715 0.337891 0.5 1.14359 0.5 2.73083V42.0032C0.5 43.5914 1.87715 44.3961 4.59152 44.3961H6.76597C9.4814 44.3961 10.8575 43.5904 10.8575 42.0032V2.73083C10.8585 1.14359 9.4814 0.337891 6.76597 0.337891Z" fill="white"/>
|
||||
<path d="M45.1179 39.5856C45.1179 34.9006 48.2073 30.9351 52.4743 29.5706C52.9617 27.3867 53.2075 24.978 53.2117 22.3445C53.2117 8.14862 45.9908 0.330078 32.878 0.330078H18.8996C16.9468 0.330078 16.0371 1.23137 16.0371 3.16736V41.5216C16.0371 43.4576 16.9468 44.3589 18.8985 44.3589H32.877C37.8761 44.3589 42.0159 43.2191 45.2282 41.0268C45.1578 40.5499 45.1211 40.0678 45.1169 39.5856M32.8759 35.5298H26.4398V9.15916H32.8759C40.9287 9.15916 42.6199 16.3296 42.6199 22.3445C42.6199 28.3594 40.9287 35.5298 32.8759 35.5298Z" fill="white"/>
|
||||
<path d="M55.7368 34.7764C58.4165 34.7764 60.5889 36.9298 60.5889 39.5864C60.5889 42.243 58.4165 44.3964 55.7368 44.3964C53.0571 44.3964 50.8848 42.243 50.8848 39.5864C50.8848 36.9298 53.0571 34.7764 55.7368 34.7764Z" fill="#2EA76D"/>
|
||||
<path d="M124.735 32.8363C124.593 32.7754 124.441 32.7417 124.286 32.7365C123.623 32.7144 123.171 32.9728 122.772 33.6126C122.536 33.996 122.303 34.3857 122.072 34.7754C121.192 36.2618 120.283 37.7965 118.962 38.8974C117.168 40.3912 114.57 41.0897 112.342 40.6842C111.017 40.4437 110.071 39.493 109.51 38.7388C108.482 37.3522 107.981 35.4855 108.061 33.3342C111.341 33.06 121.224 31.6114 122.01 24.1847C122.163 22.7446 121.749 21.4399 120.814 20.4104C119.629 19.1058 117.736 18.3862 115.487 18.3862C108.895 18.3862 102.389 25.2215 101.564 33.0138C101.338 35.1515 101.607 37.1253 102.364 38.8848C101.884 39.3449 101.335 39.7283 100.74 40.0224C100.187 40.283 99.7092 40.3365 99.3584 40.1769C98.9245 39.9773 98.7481 39.4741 98.6787 39.0886C98.4193 37.6621 98.7544 36.0223 99.1514 34.3279C99.3846 33.3342 99.6777 32.2995 99.9351 31.3856C100.732 28.5672 101.557 25.6533 101.282 22.7088C101.032 20.0134 99.1231 18.3389 96.3016 18.3389C92.3455 18.3389 89.7404 21.1374 88.0544 23.5366C88.0187 21.7487 87.5891 20.4504 86.7466 19.5827C85.9451 18.757 84.7676 18.3379 83.2486 18.3379C79.3682 18.3379 76.7956 21.026 75.1139 23.379C75.1359 23.1626 75.159 22.942 75.1832 22.7214C75.2893 21.713 75.3366 20.2676 74.5246 19.3747C74.0414 18.8432 73.3144 18.5732 72.3638 18.5732C71.7104 18.5532 71.057 18.6037 70.4152 18.7255C70.4057 18.7266 69.5339 18.8957 69.2019 19.1982C68.621 19.7266 68.8059 20.4556 68.912 20.8716C68.9256 20.9241 68.9382 20.9724 68.9445 21.0134C69.0107 21.4746 69.0307 21.941 69.0055 22.4063C68.8836 25.2362 68.3542 28.0651 67.851 30.4591C67.5789 31.7491 67.2733 33.0611 66.9707 34.3563C66.2985 37.2303 65.6051 40.2 65.249 43.1875C65.1703 43.7684 65.5757 44.3031 66.1566 44.3818C66.2092 44.3892 66.2627 44.3923 66.3163 44.3913L66.5253 44.3934C68.6578 44.4218 70.7534 44.3346 71.2461 43.1286C71.7913 41.7988 72.0928 40.1811 72.3596 38.7503L72.4772 38.1159C73.1096 34.8258 73.6621 32.4686 74.7756 29.4381C75.3492 27.876 76.3975 26.4695 77.2452 25.4169C78.2715 24.1427 79.3787 22.8958 80.6277 22.8643C81.1445 22.838 81.4817 22.9904 81.7359 23.3255C82.9492 24.9306 81.4387 30.1103 80.7937 32.3226C80.654 32.8006 80.5363 33.205 80.4617 33.5012L80.0174 35.2103C79.363 37.702 78.6865 40.2777 78.3398 42.8839C78.3052 43.1507 78.2726 43.4186 78.2453 43.6896L78.2159 44.053L78.489 44.261C79.0394 44.6844 82.9702 44.0919 83.0017 44.0825C84.4514 43.5971 84.7665 42.2452 84.8705 41.8009C85.1405 40.6517 85.3789 39.4773 85.609 38.3428L85.6258 38.2587C86.0586 36.1211 86.5061 33.9098 87.1752 31.7764C88.482 27.625 90.2236 24.7929 92.3539 23.358C93.2647 22.7425 94.2007 22.6742 94.624 23.1941C95.3604 24.0912 94.8939 26.5199 94.6944 27.5567C94.4191 28.9958 94.0662 30.4591 93.7258 31.8751L93.708 31.9476C93.4895 32.8499 93.2731 33.7512 93.0714 34.6557C92.4485 37.4583 91.9369 40.9332 93.5284 42.9385C94.3383 43.9606 95.5914 44.4785 97.2533 44.4785C99.0159 44.4785 100.674 43.8955 102.471 42.6444C102.966 42.2967 103.466 41.8923 104.05 41.4111C106.073 43.6906 108.37 44.6676 111.687 44.6676C119.185 44.6676 122.71 39.7283 124.71 36.0129C124.976 35.537 125.211 35.0443 125.412 34.538C125.66 33.8542 125.362 33.1062 124.734 32.8373M115.539 23.4557C115.549 23.5713 115.559 23.6868 115.56 23.8034C115.56 23.9778 115.551 24.1522 115.533 24.3265C115.477 24.9043 115.363 25.4757 115.194 26.0314C114.124 29.4717 111.112 30.0809 108.506 30.1681C108.849 28.6996 109.358 27.2741 110.021 25.919C111.288 23.358 112.881 21.7676 114.175 21.7666C114.295 21.7666 114.414 21.7844 114.528 21.817C114.588 21.838 114.646 21.8632 114.701 21.8937L114.756 21.9178C114.778 21.9262 114.799 21.9368 114.819 21.9494C114.876 21.9872 114.928 22.0313 114.977 22.0786L115.021 22.1195C115.039 22.1342 115.054 22.15 115.07 22.1668C115.105 22.2078 115.137 22.2519 115.166 22.2981L115.204 22.3559C115.233 22.3958 115.259 22.4378 115.282 22.4809C115.296 22.5082 115.307 22.5387 115.342 22.6185C115.379 22.7036 115.41 22.7908 115.438 22.879L115.462 22.9862C115.491 23.0986 115.513 23.212 115.528 23.3265L115.539 23.4557Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 MiB |
1
auth/assets/custom-icons/icons/canva.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 508 508" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2"><g transform="matrix(.26718 0 0 .26718 0 0)"><circle cx="950" cy="950" r="950" fill="#7d2ae7"/><circle cx="950" cy="950" r="950" fill="url(#prefix___Radial1)"/><circle cx="950" cy="950" r="950" fill="url(#prefix___Radial2)"/><circle cx="950" cy="950" r="950" fill="url(#prefix___Radial3)"/><circle cx="950" cy="950" r="950" fill="url(#prefix___Radial4)"/></g><path d="M446.744 276.845c-.665 0-1.271.43-1.584 1.33-4.011 11.446-9.43 18.254-13.891 18.254-2.563 0-3.6-2.856-3.6-7.336 0-11.21 6.71-34.982 10.095-45.82.392-1.312.646-2.485.646-3.483 0-3.15-1.722-4.696-5.987-4.696-4.598 0-9.547 1.8-14.36 10.233-1.663-7.435-6.691-10.683-13.715-10.683-8.12 0-15.965 5.224-22.421 13.696-6.456 8.471-14.048 11.25-19.76 9.88 4.108-10.057 5.634-17.57 5.634-23.145 0-8.746-4.324-14.028-11.308-14.028-10.624 0-16.747 10.134-16.747 20.797 0 8.237 3.736 16.708 11.954 20.817-6.887 15.573-16.943 29.66-20.758 29.66-4.93 0-6.379-24.123-6.105-41.38.176-9.9.998-10.408.998-13.401 0-1.722-1.115-2.896-5.595-2.896-10.448 0-13.676 8.844-14.165 18.998a50.052 50.052 0 01-1.8 11.406c-4.363 15.573-13.363 27.39-19.232 27.39-2.72 0-3.463-2.72-3.463-6.28 0-11.21 6.28-25.219 6.28-37.173 0-8.784-3.854-14.34-11.112-14.34-8.55 0-19.858 10.173-30.56 29.229 3.521-14.595 4.97-28.721-5.459-28.721a14.115 14.115 0 00-6.476 1.683 3.689 3.689 0 00-2.113 3.56c.998 15.535-12.521 55.329-25.336 55.329-2.328 0-3.463-2.524-3.463-6.593 0-11.23 6.691-34.943 10.056-45.801.43-1.409.666-2.622.666-3.678 0-2.974-1.84-4.5-6.007-4.5-4.578 0-9.547 1.741-14.34 10.174-1.683-7.435-6.711-10.683-13.735-10.683-11.523 0-24.397 12.19-30.051 28.076-7.572 21.208-22.832 41.692-43.375 41.692-18.645 0-28.486-15.515-28.486-40.03 0-35.392 25.982-64.308 45.253-64.308 9.215 0 13.617 5.869 13.617 14.869 0 10.897-6.085 15.964-6.085 20.112 0 1.272 1.057 2.524 3.15 2.524 8.374 0 18.234-9.841 18.234-23.262 0-13.422-10.897-23.243-30.168-23.243-31.851 0-63.898 32.047-63.898 73.113 0 32.673 16.121 52.374 44 52.374 19.017 0 35.628-14.79 44.588-32.047 1.018 14.302 7.513 21.776 17.413 21.776 8.804 0 15.925-5.243 21.364-14.458 2.094 9.645 7.65 14.36 14.87 14.36 8.275 0 15.201-5.243 21.794-14.986-.097 7.65 1.644 14.85 8.276 14.85 3.13 0 6.867-.725 7.533-3.464 6.984-28.877 24.24-52.453 29.523-52.453 1.565 0 1.995 1.507 1.995 3.287 0 7.846-5.537 23.928-5.537 34.2 0 11.092 4.716 18.43 14.459 18.43 10.8 0 21.775-13.227 29.092-32.556 2.29 18.058 7.24 32.633 14.987 32.633 9.508 0 26.392-20.014 36.625-41.203 4.01.509 10.036.372 15.827-3.717-2.465 6.241-3.912 13.07-3.912 19.897 0 19.663 9.39 25.18 17.47 25.18 8.785 0 15.907-5.243 21.365-14.458 1.8 8.315 6.398 14.34 14.85 14.34 13.225 0 24.71-13.519 24.71-24.612 0-2.934-1.252-4.715-2.72-4.715zm-274.51 18.547c-5.342 0-7.435-5.38-7.435-13.401 0-13.93 9.528-37.193 19.604-37.193 4.402 0 6.065 5.185 6.065 11.524 0 14.145-9.059 39.07-18.235 39.07zm182.948-41.574c-3.189-3.796-4.343-8.961-4.343-13.559 0-5.673 2.074-10.467 4.558-10.467 2.485 0 3.248 2.446 3.248 5.85 0 5.693-2.035 14.008-3.463 18.176zm41.418 41.574c-5.34 0-7.434-6.182-7.434-13.401 0-13.441 9.528-37.193 19.682-37.193 4.402 0 5.967 5.146 5.967 11.524 0 14.145-8.902 39.07-18.215 39.07z" fill="#fff" fill-rule="nonzero"/><defs><radialGradient id="prefix___Radial1" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="scale(1469.491) rotate(-49.416 1.37 .302)"><stop offset="0" stop-color="#6420ff"/><stop offset="1" stop-color="#6420ff" stop-opacity="0"/></radialGradient><radialGradient id="prefix___Radial2" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(54.703 42.717 594.194) scale(1657.122)"><stop offset="0" stop-color="#00c4cc"/><stop offset="1" stop-color="#00c4cc" stop-opacity="0"/></radialGradient><radialGradient id="prefix___Radial3" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1023 -1030 473.711 470.491 367 1684)"><stop offset="0" stop-color="#6420ff"/><stop offset="1" stop-color="#6420ff" stop-opacity="0"/></radialGradient><radialGradient id="prefix___Radial4" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(595.999 1372 -2298.41 998.431 777 256)"><stop offset="0" stop-color="#00c4cc" stop-opacity=".73"/><stop offset="0" stop-color="#00c4cc"/><stop offset="1" stop-color="#00c4cc" stop-opacity="0"/></radialGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
14
auth/assets/custom-icons/icons/coinbase.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="katman_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 800 600" style="enable-background:new 0 0 800 600;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#0052FF;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
</style>
|
||||
<path class="st0" d="M399.8,69.1L399.8,69.1c127.3,0,230.5,103.2,230.5,230.5l0,0c0,127.3-103.2,230.5-230.5,230.5l0,0
|
||||
c-127.3,0-230.5-103.2-230.5-230.5l0,0C169.3,172.3,272.5,69.1,399.8,69.1z"/>
|
||||
<path class="st1" d="M399.9,380.6c-44.8,0-81-36.3-81-81s36.3-81,81-81c40.1,0,73.4,29.2,79.8,67.5h81.6
|
||||
c-6.9-83.2-76.5-148.6-161.5-148.6c-89.5,0-162.1,72.6-162.1,162.1s72.6,162.1,162.1,162.1c85,0,154.6-65.4,161.5-148.6h-81.7
|
||||
C473.2,351.4,440,380.6,399.9,380.6z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 904 B |
27
auth/assets/custom-icons/icons/ebay.svg
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
|
||||
<svg
|
||||
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="300"
|
||||
height="120.32412">
|
||||
|
||||
<path id="e"
|
||||
d="m 38.866448,26.308378 c -21.145729,0 -38.76645103,8.97108 -38.76645103,36.036419 0,21.441707 11.84866003,34.944406 39.31245703,34.944406 32.326175,0 34.3984,-21.294248 34.3984,-21.294248 l -15.663557,0 c 0,0 -3.358398,11.466134 -19.690354,11.466134 -13.301932,0 -22.869203,-8.985845 -22.869203,-21.580814 l 59.861133,0 0,-7.903529 c 0,-12.460384 -7.91007,-31.668368 -36.582425,-31.668368 z m -0.546007,10.101117 c 12.662062,0 21.294248,7.757047 21.294248,19.383225 l -43.680508,0 c 0,-12.34261 11.267202,-19.383225 22.38626,-19.383225 z"
|
||||
style="fill:#e53238;fill-opacity:1;stroke:none" />
|
||||
|
||||
<path id="b"
|
||||
d="m 75.437762,0.10007279 0,83.59702321 c 0,4.745232 -0.338677,11.408082 -0.338677,11.408082 l 14.939733,0 c 0,0 0.536238,-4.785353 0.536238,-9.1587 0,0 7.381193,11.547477 27.451204,11.547477 21.13453,0 35.49041,-14.673014 35.49041,-35.695165 0,-19.556604 -13.18634,-35.28566 -35.45629,-35.28566 -20.854235,0 -27.33444,11.261381 -27.33444,11.261381 l 0,-37.67443821 z M 114.20421,36.853125 c 14.35199,0 23.47828,10.651661 23.47828,24.945665 0,15.327725 -10.54056,25.35517 -23.3759,25.35517 -15.317854,0 -23.58065,-11.960116 -23.58065,-25.218668 0,-12.354387 7.414449,-25.082167 23.47827,-25.082167 z"
|
||||
style="fill:#0064d2;fill-opacity:1;stroke:none" />
|
||||
|
||||
<path id="a"
|
||||
d="m 190.6451,26.308378 c -31.81215,0 -33.85239,17.418776 -33.85239,20.202235 l 15.83418,0 c 0,0 0.83034,-10.169369 16.9262,-10.169369 10.45935,0 18.56422,4.787411 18.56422,13.991413 l 0,3.276038 -18.56422,0 c -24.64532,0 -37.67444,7.20973 -37.67444,21.840254 0,14.398537 12.03849,22.232696 28.30702,22.232696 22.17148,0 29.31371,-12.251017 29.31371,-12.251017 0,4.872784 0.37568,9.67455 0.37568,9.67455 l 14.07643,0 c 0,0 -0.54601,-5.951939 -0.54601,-9.759864 l 0,-32.913945 c 0,-21.581223 -17.40751,-26.122991 -32.76038,-26.122991 z m 17.47221,37.128431 0,4.368051 c 0,5.697129 -3.51553,19.860981 -24.21197,19.860981 -11.3333,0 -16.1925,-5.656156 -16.1925,-12.216892 0,-11.935273 16.36378,-12.01214 40.40447,-12.01214 z"
|
||||
style="fill:#f5af02;fill-opacity:1;stroke:none" />
|
||||
|
||||
<path id="y"
|
||||
d="m 214.87901,29.041161 17.81346,0 25.56479,51.217345 25.5063,-51.217345 16.13644,0 -46.45929,91.183029 -16.9262,0 13.40641,-25.418513 z"
|
||||
style="fill:#86b817;fill-opacity:1;stroke:none" />
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
3
auth/assets/custom-icons/icons/forusall.svg
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
1
auth/assets/custom-icons/icons/guideline.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 400 400"><path fill="#150A45" d="M0 0h400v400H0z"/><path fill="#fff" d="M234.117 91.106C225.896 77.188 211.92 69 192.19 69 155.196 69 133 98.475 133 139.413c0 40.937 22.196 70.412 59.19 70.412 6.577 0 13.976-.819 19.73-3.275 6.577-2.456 12.332-6.55 16.442-11.463v19.65c0 13.1-7.399 29.475-29.595 29.475-17.264 0-25.485-9.825-28.773-19.65L139.577 239.3c8.221 18.012 26.306 34.388 61.656 34.388 51.792 0 65.767-35.207 65.767-65.5V70.638h-32.061l-.822 20.468Zm-33.706 86.788c-18.908 0-29.595-14.738-29.595-38.481 0-23.744 10.687-38.482 29.595-38.482s29.595 14.738 29.595 38.482c0 22.925-10.687 38.481-29.595 38.481ZM267 284.332V331H133.822v-27.837h106.049v-18.831H267Z"/></svg>
|
||||
|
After Width: | Height: | Size: 739 B |
23
auth/assets/custom-icons/icons/gusto.svg
Normal file
@@ -0,0 +1,23 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 333.8 127" style="enable-background:new 0 0 333.8 127;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#F45D48;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M296.4,18.6c-20.6,0-37.4,16.8-37.4,37.6c0,20.7,16.8,37.6,37.4,37.6c20.6,0,37.4-16.9,37.4-37.6
|
||||
C333.8,35.4,317,18.6,296.4,18.6L296.4,18.6z M296.4,75.9c-10.9,0-19.7-8.9-19.7-19.8c0-10.9,8.8-19.8,19.7-19.8
|
||||
c10.8,0,19.7,8.9,19.7,19.8C316.1,67,307.2,75.9,296.4,75.9L296.4,75.9z M186.2,48.5l-6.5-3.3c-2.8-1.4-4.6-2.5-5.3-3.3
|
||||
c-0.7-0.7-1.1-1.7-1.1-2.6c0-1.3,0.6-2.3,1.7-3.2c1.1-0.9,2.7-1.3,4.7-1.3c3.7,0,7.7,2.2,12.2,6.7l11-11.1
|
||||
c-2.7-3.7-6.3-6.7-10.4-8.8c-4-2-8.5-3.1-13-3c-6.7,0-12.3,2-16.6,6c-4.3,4-6.5,8.8-6.5,14.5c0,8.7,5.7,16,17.2,21.8l6,3
|
||||
c5.2,2.6,7.8,5.3,7.8,8.1c0,1.5-0.7,2.8-2.1,4c-1.4,1.1-3.3,1.7-5.7,1.7c-2.2,0-4.7-0.8-7.5-2.3c-2.8-1.5-5.3-3.5-7.3-5.9l-11,12
|
||||
c6.2,8.1,14.5,12.2,24.8,12.2c7.8,0,14-2.1,18.6-6.4c4.6-4.2,7-9.5,7-15.9c0-4.8-1.3-8.9-3.8-12.5C197.8,55.4,193.1,52,186.2,48.5
|
||||
L186.2,48.5z M75.2,85.5V20.1H57.4v4.4c-6-3.9-13-5.9-20.1-5.9C16.8,18.6,0,35.4,0,56.1c0,20.7,16.8,37.6,37.4,37.6
|
||||
c7,0,13.9-2,19.9-5.7c0,0.6,0,1.1,0,1.4c0,10.9-8.8,19.8-19.7,19.8c-3.7,0-7.3-1.1-10.4-3l-8.8,15.4c5.8,3.5,12.4,5.4,19.2,5.4
|
||||
c20.6,0,37.6-16.9,37.6-37.6C75.2,88.6,75.3,86.3,75.2,85.5L75.2,85.5z M37.4,75.9c-10.8,0-19.7-8.9-19.7-19.8
|
||||
c0-10.9,8.8-19.8,19.7-19.8c10.9,0,19.7,8.9,19.7,19.8C57.1,67,48.2,75.9,37.4,75.9L37.4,75.9z M145.9,20h-17.8v35.7
|
||||
c0,5.1,0,13.8-3.7,17.6c-1.8,1.8-3.7,3.4-7.8,3.4c-4.1,0-6.1-1.6-7.9-3.4c-3.7-3.7-3.7-12.5-3.7-17.6V20H87.2v35.6
|
||||
c-0.1,7.4-0.1,21,8.8,30c5.2,5.3,11.5,8,20.5,8c9,0,15.3-2.7,20.5-8c8.9-9,8.9-22.7,8.8-30L145.9,20z M247,72.7
|
||||
c-1.4,1.1-4.9,3.5-8.8,3c-3.2-0.4-5.8-2.8-6.2-10.7V35.3h21V20h-21V0h-17.8v3.9h0v58.7c0,9.3,2.3,31,24,31
|
||||
c10.9-0.2,17.6-6.1,20.4-8.5l0.4-0.3l-10.7-13.2C248.2,71.8,247.5,72.4,247,72.7L247,72.7z M247,72.7">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
1
auth/assets/custom-icons/icons/login_gov.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 90"><path fill="#FFF" d="M0 0h90v90H0z"/><path fill="#FFF" d="M33.258 25.45h26.027v33.51H33.258z"/><path d="m39.22 53.522 2.194-12.578a6.684 6.684 0 0 1-2.21-7.444 6.645 6.645 0 0 1 6.311-4.495v-13.96H28.567c-3.627 0-6.567 2.952-6.567 6.593v32.324c-.005.61.13 1.213.394 1.762C23.816 58.61 29.43 67.876 45.5 75V54.038a36.635 36.635 0 0 1-6.28-.516Z" fill="#E21D3E"/><path d="M62.417 15H45.5v13.96a6.644 6.644 0 0 1 6.302 4.458 6.684 6.684 0 0 1-2.14 7.435l2.193 12.638c-2.098.367-4.225.55-6.355.547V74.97c16.055-7.094 21.684-16.39 23.106-19.276.264-.55.399-1.153.394-1.762v-32.31a6.606 6.606 0 0 0-1.918-4.686A6.555 6.555 0 0 0 62.417 15Z" fill="#B51E23"/></svg>
|
||||
|
After Width: | Height: | Size: 717 B |
1
auth/assets/custom-icons/icons/titan.svg
Normal file
|
After Width: | Height: | Size: 11 KiB |
@@ -19,6 +19,20 @@
|
||||
"pleaseVerifyDetails": "Моля, проверете данните и опитайте отново",
|
||||
"codeIssuerHint": "Издател",
|
||||
"codeSecretKeyHint": "Код",
|
||||
"secret": "Код",
|
||||
"all": "Всички",
|
||||
"notes": "Бележки",
|
||||
"notesLengthLimit": "Бележките могат да съдържат най-много {count} знака",
|
||||
"@notesLengthLimit": {
|
||||
"description": "Text to indicate the maximum number of characters allowed for notes",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"description": "The maximum number of characters allowed for notes",
|
||||
"type": "int",
|
||||
"example": "100"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codeAccountHint": "Профил (you@domain.com)",
|
||||
"codeTagHint": "Етикет",
|
||||
"accountKeyType": "Тип на кода",
|
||||
@@ -34,6 +48,9 @@
|
||||
"nextTotpTitle": "следващ",
|
||||
"deleteCodeTitle": "Изтриване на кода?",
|
||||
"deleteCodeMessage": "Сигурни ли сте, че искате да изтриете този код? Това действие е необратимо.",
|
||||
"trashCode": "Преместване в кошчето на кода?",
|
||||
"trashCodeMessage": "Сигурни ли сте, че искате да изместите кода в кошчето за {account}?",
|
||||
"trash": "Кошче",
|
||||
"viewLogsAction": "Преглед на историята на действията",
|
||||
"sendLogsDescription": "Това ще изпрати файлове с история на действията, за да ни помогне да отстраним проблема Ви. Въпреки че вземаме предпазни мерки, за да гарантираме, че чувствителната информация не се регистрира, препоръчваме Ви да прегледате тези файлове с история на действията, преди да ги споделите.",
|
||||
"preparingLogsTitle": "Подготвяне на файловете с историята...",
|
||||
@@ -100,6 +117,7 @@
|
||||
"emailVerificationToggle": "Потвърждение чрез имейл",
|
||||
"emailVerificationEnableWarning": "За да избегнете загуба на достъп до акаунта си, не забравяйте да съхраните копие от Вашия имейл 2FA извън Ente Auth, преди да активирате потвърждението чрез имейл.",
|
||||
"authToChangeEmailVerificationSetting": "Моля, удостоверете се, за да промените потвърждението чрез имейл",
|
||||
"authenticateGeneric": "Моля, удостоверете се",
|
||||
"authToViewYourRecoveryKey": "Моля, удостоверете се, за да видите Вашия ключ за възстановяване",
|
||||
"authToChangeYourEmail": "Моля, удостоверете се, за да промените своя имейл",
|
||||
"authToChangeYourPassword": "Моля, удостоверете се, за да промените паролата си",
|
||||
@@ -137,6 +155,8 @@
|
||||
"leaveFamily": "Напуснете семейството",
|
||||
"leaveFamilyMessage": "Сигурни ли сте, че искате да напуснете семейния план?",
|
||||
"inFamilyPlanMessage": "Вие сте на семеен план!",
|
||||
"hintForMobile": "Натиснете продължително код, за да го редактирате или премахнете.",
|
||||
"hintForDesktop": "Натиснете десен бутон върху код, за да го редактирате или премахнете.",
|
||||
"scan": "Сканиране",
|
||||
"scanACode": "Скениране на код",
|
||||
"verify": "Потвърждаване",
|
||||
@@ -192,6 +212,10 @@
|
||||
"scanAQrCode": "Скениране на QR код",
|
||||
"enterDetailsManually": "Въведете подробности ръчно",
|
||||
"edit": "Редактиране",
|
||||
"share": "Споделяне",
|
||||
"shareCodes": "Споделяне на кодове",
|
||||
"shareCodesDuration": "Изберете продължителността, за която искате да споделите кодовете.",
|
||||
"restore": "Възстановяване",
|
||||
"copiedToClipboard": "Копирано в буферната памет",
|
||||
"copiedNextToClipboard": "Следващият код е копиран в буферната памет",
|
||||
"error": "Грешка",
|
||||
@@ -345,6 +369,7 @@
|
||||
"sigInBackupReminder": "Моля, експортирайте Вашите кодове, за да сте сигурни, че имате резервно копие, от което можете да ги възстановите.",
|
||||
"offlineModeWarning": "Избрахте да продължите без резервни копия. Моля, направете ръчни резервни копия, за да сте сигурни, че Вашите кодове са в безопасност.",
|
||||
"showLargeIcons": "Показване на големи икони",
|
||||
"compactMode": "Компактен изглед",
|
||||
"shouldHideCode": "Скриване на кодове",
|
||||
"doubleTapToViewHiddenCode": "Можете да докоснете два пъти върху запис, за да видите кода",
|
||||
"focusOnSearchBar": "Фокусиране на търсенето при стартиране на приложението",
|
||||
@@ -465,5 +490,7 @@
|
||||
"pinLock": "Заключване с ПИН код",
|
||||
"enterPin": "Въведете ПИН код",
|
||||
"setNewPin": "Задаване на нов ПИН код",
|
||||
"importFailureDescNew": "Неуспешно обработване на избрания файл."
|
||||
"importFailureDescNew": "Неуспешно обработване на избрания файл.",
|
||||
"appLockNotEnabled": "Заключването на приложението не е активирано",
|
||||
"appLockNotEnabledDescription": "Моля, активирайте заключването на приложението от Сигурност > Заключване на приложението"
|
||||
}
|
||||
@@ -19,6 +19,20 @@
|
||||
"pleaseVerifyDetails": "Παρακαλούμε επιβεβαιώστε τα στοιχεία σας και προσπαθήστε ξανά",
|
||||
"codeIssuerHint": "Εκδότης",
|
||||
"codeSecretKeyHint": "Μυστικό Κλειδί",
|
||||
"secret": "Μυστικό",
|
||||
"all": "Όλα",
|
||||
"notes": "Σημειώσεις",
|
||||
"notesLengthLimit": "Οι σημειώσεις μπορούν να είναι το πολύ {count} χαρακτήρες",
|
||||
"@notesLengthLimit": {
|
||||
"description": "Text to indicate the maximum number of characters allowed for notes",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"description": "The maximum number of characters allowed for notes",
|
||||
"type": "int",
|
||||
"example": "100"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codeAccountHint": "Λογαριασμός (you@domain.com)",
|
||||
"codeTagHint": "Ετικέτα",
|
||||
"accountKeyType": "Τύπος κλειδιού",
|
||||
@@ -34,6 +48,9 @@
|
||||
"nextTotpTitle": "επόμενο",
|
||||
"deleteCodeTitle": "Διαγραφή κωδικού;",
|
||||
"deleteCodeMessage": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτόν τον κωδικό; Αυτή η ενέργεια είναι μη αναστρέψιμη.",
|
||||
"trashCode": "Διαγραφή κώδικα;",
|
||||
"trashCodeMessage": "Είστε σίγουροι ότι θέλετε να διαγράψετε τον κώδικα για το {account} σας;",
|
||||
"trash": "Διαγραφή",
|
||||
"viewLogsAction": "Προβολή αρχείων καταγραφής",
|
||||
"sendLogsDescription": "Αυτό θα στείλει σε μας όλα τα αρχεία καταγραφής για να μας βοηθήσει να αποσφαλματώσουμε το πρόβλημά σας. Ενώ λαμβάνουμε προφυλάξεις για να διασφαλίσουμε ότι οι ευαίσθητες πληροφορίες δεν καταγράφονται, σας ενθαρρύνουμε να δείτε αυτά τα αρχεία καταγραφής πριν τα μοιραστείτε.",
|
||||
"preparingLogsTitle": "Προετοιμασία αρχείων καταγραφής...",
|
||||
@@ -71,7 +88,7 @@
|
||||
"useRecoveryKey": "Χρήση κλειδιού ανάκτησης",
|
||||
"incorrectPasswordTitle": "Λάθος κωδικός πρόσβασης",
|
||||
"welcomeBack": "Καλωσορίσατε και πάλι!",
|
||||
"madeWithLoveAtPrefix": "γίνεται με ❤️ στο ",
|
||||
"madeWithLoveAtPrefix": "φτιαγμένη με ❤️ στο ",
|
||||
"supportDevs": "Εγγραφείτε στο <bold-green>ente</bold-green> για να μας υποστηρίξετε",
|
||||
"supportDiscount": "Χρησιμοποιήστε τον κωδικό κουπονιού \"AUTH\" για να πάρετε 10% από το πρώτο έτος",
|
||||
"changeEmail": "Αλλαγή διεύθυνσης ηλ. ταχυδρομείου",
|
||||
@@ -100,6 +117,7 @@
|
||||
"emailVerificationToggle": "Επαλήθευση διεύθυνσης ηλ. ταχυδρομείου",
|
||||
"emailVerificationEnableWarning": "Για να αποφύγετε να κλειδωθείτε έξω από τον λογαριασμό σας, φροντίστε να αποθηκεύσετε ένα αντίγραφο του 2FA του ηλ. ταχυδρομείου σας έξω από το Ente Auth πριν ενεργοποιήσετε την επαλήθευση μέσω ηλ. ταχυδρομείου.",
|
||||
"authToChangeEmailVerificationSetting": "Παρακαλώ πραγματοποιήστε έλεγχο ταυτότητας για να αλλάξετε την επαλήθευση ηλ. ταχυδρομείου",
|
||||
"authenticateGeneric": "Παρακαλώ πιστοποιήστε την ταυτότητά σας",
|
||||
"authToViewYourRecoveryKey": "Παρακαλώ πραγματοποιήστε έλεγχο ταυτότητας για να δείτε το κλειδί ανάκτησης",
|
||||
"authToChangeYourEmail": "Παρακαλώ πραγματοποιήστε έλεγχο ταυτότητας για να αλλάξετε τη διεύθυνση ηλ. ταχυδρομείου σας",
|
||||
"authToChangeYourPassword": "Παρακαλώ πραγματοποιήστε έλεγχο ταυτότητας για να αλλάξετε τον κωδικό πρόσβασής σας",
|
||||
@@ -137,6 +155,8 @@
|
||||
"leaveFamily": "Αποχώρηση από οικογένεια",
|
||||
"leaveFamilyMessage": "Είστε σίγουροι ότι θέλετε να φύγετε από το οικογενειακό πρόγραμμα;",
|
||||
"inFamilyPlanMessage": "Είστε σε οικογενειακό πρόγραμμα!",
|
||||
"hintForMobile": "Πατήστε παρατεταμένα σε έναν κωδικό για να τον τροποποιήσετε ή να τον διαγράψετε.",
|
||||
"hintForDesktop": "Κάντε δεξί κλικ σε έναν κωδικό για να τον τροποποιήσετε ή να τον διαγράψετε.",
|
||||
"scan": "Σάρωση",
|
||||
"scanACode": "Σάρωση κωδικού",
|
||||
"verify": "Επαλήθευση",
|
||||
@@ -192,6 +212,10 @@
|
||||
"scanAQrCode": "Σαρώστε έναν κωδικό QR",
|
||||
"enterDetailsManually": "Χειροκίνητη εισαγωγή στοιχείων",
|
||||
"edit": "Επεξεργασία",
|
||||
"share": "Κοινοποίηση",
|
||||
"shareCodes": "Κοινοποίηση κωδικών",
|
||||
"shareCodesDuration": "Επιλέξτε τη διάρκεια για την οποία θέλετε να μοιραστείτε τους κωδικούς.",
|
||||
"restore": "Επαναφορά",
|
||||
"copiedToClipboard": "Αντιγράφηκε στο πρόχειρο",
|
||||
"copiedNextToClipboard": "Αντιγράφηκε ο επόμενος κωδικός στο πρόχειρο",
|
||||
"error": "Σφάλμα",
|
||||
@@ -345,6 +369,7 @@
|
||||
"sigInBackupReminder": "Παρακαλώ εξάγετε τους κωδικούς σας για να βεβαιωθείτε ότι έχετε ένα αντίγραφο ασφαλείας από το οποίο μπορείτε να επαναφέρετε.",
|
||||
"offlineModeWarning": "Επιλέξατε να προχωρήσετε χωρίς αντίγραφα ασφαλείας. Παρακαλώ κάντε χειροκίνητα αντίγραφα ασφαλείας για να βεβαιωθείτε ότι οι κωδικοί σας είναι ασφαλείς.",
|
||||
"showLargeIcons": "Εμφάνιση μεγάλων εικονιδίων",
|
||||
"compactMode": "Συμπαγής λειτουργία",
|
||||
"shouldHideCode": "Απόκρυψη κωδικών",
|
||||
"doubleTapToViewHiddenCode": "Μπορείτε να πατήσετε δύο φορές σε μια καταχώρηση για να δείτε τον κωδικό",
|
||||
"focusOnSearchBar": "Εστίαση στην αναζήτηση κατά την εκκίνηση εφαρμογής",
|
||||
@@ -465,5 +490,7 @@
|
||||
"pinLock": "Κλείδωμα καρφιτσωμάτων",
|
||||
"enterPin": "Εισαγωγή PIN",
|
||||
"setNewPin": "Ορίστε νέο PIN",
|
||||
"importFailureDescNew": "Αδυναμία ανάλυσης του επιλεγμένου αρχείου."
|
||||
"importFailureDescNew": "Αδυναμία ανάλυσης του επιλεγμένου αρχείου.",
|
||||
"appLockNotEnabled": "Το κλείδωμα εφαρμογής δεν είναι ενεργοποιημένο",
|
||||
"appLockNotEnabledDescription": "Παρακαλώ ενεργοποιήστε το κλείδωμα εφαρμογής μέσω της επιλογής Ασφάλεια > Κλείδωμα εφαρμογής"
|
||||
}
|
||||
@@ -23,16 +23,16 @@
|
||||
"all": "All",
|
||||
"notes": "Notes",
|
||||
"notesLengthLimit": "Notes can be at most {count} characters long",
|
||||
"@notesLengthLimit": {
|
||||
"description": "Text to indicate the maximum number of characters allowed for notes",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"description": "The maximum number of characters allowed for notes",
|
||||
"type": "int",
|
||||
"example": "100"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@notesLengthLimit": {
|
||||
"description": "Text to indicate the maximum number of characters allowed for notes",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"description": "The maximum number of characters allowed for notes",
|
||||
"type": "int",
|
||||
"example": "100"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codeAccountHint": "Account (you@domain.com)",
|
||||
"codeTagHint": "Tag",
|
||||
"accountKeyType": "Type of key",
|
||||
@@ -141,16 +141,6 @@
|
||||
"oops": "Oops",
|
||||
"suggestFeatures": "Suggest features",
|
||||
"faq": "FAQ",
|
||||
"faq_q_1": "How secure is Auth?",
|
||||
"faq_a_1": "All codes you backup via Auth is stored end-to-end encrypted. This means only you can access your codes. Our apps are open source and our cryptography has been externally audited.",
|
||||
"faq_q_2": "Can I access my codes on desktop?",
|
||||
"faq_a_2": "You can access your codes on the web @ auth.ente.io.",
|
||||
"faq_q_3": "How can I delete codes?",
|
||||
"faq_a_3": "You can delete a code by swiping left on that item.",
|
||||
"faq_q_4": "How can I support this project?",
|
||||
"faq_a_4": "You can support the development of this project by subscribing to our Photos app @ ente.io.",
|
||||
"faq_q_5": "How can I enable FaceID lock in Auth",
|
||||
"faq_a_5": "You can enable FaceID lock under Settings → Security → Lockscreen.",
|
||||
"somethingWentWrongMessage": "Something went wrong, please try again",
|
||||
"leaveFamily": "Leave family",
|
||||
"leaveFamilyMessage": "Are you sure that you want to leave the family plan?",
|
||||
@@ -492,5 +482,6 @@
|
||||
"setNewPin": "Set new PIN",
|
||||
"importFailureDescNew": "Could not parse the selected file.",
|
||||
"appLockNotEnabled": "App lock not enabled",
|
||||
"appLockNotEnabledDescription": "Please enable app lock from Security > App Lock"
|
||||
"appLockNotEnabledDescription": "Please enable app lock from Security > App Lock",
|
||||
"authToViewPasskey": "Please authenticate to view passkey"
|
||||
}
|
||||
@@ -19,6 +19,20 @@
|
||||
"pleaseVerifyDetails": "Verifica i dettagli e riprova",
|
||||
"codeIssuerHint": "Emittente",
|
||||
"codeSecretKeyHint": "Codice segreto",
|
||||
"secret": "Segreto",
|
||||
"all": "Tutto",
|
||||
"notes": "Note",
|
||||
"notesLengthLimit": "Le note possono essere al massimo {count} caratteri",
|
||||
"@notesLengthLimit": {
|
||||
"description": "Text to indicate the maximum number of characters allowed for notes",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"description": "The maximum number of characters allowed for notes",
|
||||
"type": "int",
|
||||
"example": "100"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codeAccountHint": "Account (username@dominio.it)",
|
||||
"codeTagHint": "Tag",
|
||||
"accountKeyType": "Tipo di chiave",
|
||||
@@ -34,6 +48,9 @@
|
||||
"nextTotpTitle": "successivo",
|
||||
"deleteCodeTitle": "Eliminare il codice?",
|
||||
"deleteCodeMessage": "Sei sicuro di voler rimuovere questo codice? L'azione è irreversibile.",
|
||||
"trashCode": "Codice del cestino?",
|
||||
"trashCodeMessage": "Sei sicuro di voler cestinare il codice per {account}?",
|
||||
"trash": "Cestino",
|
||||
"viewLogsAction": "Visualizza i log",
|
||||
"sendLogsDescription": "Invierai i tuoi log per aiutarci a risolvere il tuo problema. Prendiamo precauzioni per garantire che le informazioni sensibili non siano registrate, tuttavia ti invitiamo a leggerli prima di condividerli con noi.",
|
||||
"preparingLogsTitle": "Preparando i log...",
|
||||
@@ -100,6 +117,7 @@
|
||||
"emailVerificationToggle": "Verifica email",
|
||||
"emailVerificationEnableWarning": "Se memorizzate il 2FA per accedere alla vostra email con noi, l'attivazione della verifica dell'email potrebbe provocare dei problemi. Se siete rimasti bloccati fuori da un servizio, potreste non essere in grado di accedere anche all'altro.",
|
||||
"authToChangeEmailVerificationSetting": "Autenticati per cambiare la verifica email",
|
||||
"authenticateGeneric": "Si prega di autenticarsi",
|
||||
"authToViewYourRecoveryKey": "Autenticati per visualizzare la tua chiave di recupero",
|
||||
"authToChangeYourEmail": "Autenticati per cambiare la tua email",
|
||||
"authToChangeYourPassword": "Autenticati per cambiare la tua password",
|
||||
@@ -137,6 +155,8 @@
|
||||
"leaveFamily": "Abbandona il piano famiglia",
|
||||
"leaveFamilyMessage": "Sei sicuro di voler uscire dal piano famiglia?",
|
||||
"inFamilyPlanMessage": "Sei un utente con piano famiglia!",
|
||||
"hintForMobile": "Premi a lungo su un codice per modificare o rimuovere.",
|
||||
"hintForDesktop": "Fare clic con il tasto destro su un codice per modificare o rimuovere.",
|
||||
"scan": "Scansiona",
|
||||
"scanACode": "Scansiona un codice",
|
||||
"verify": "Verifica",
|
||||
@@ -192,6 +212,10 @@
|
||||
"scanAQrCode": "Scansiona un codice QR",
|
||||
"enterDetailsManually": "Inserisci i dettagli manualmente",
|
||||
"edit": "Modifica",
|
||||
"share": "Condividi",
|
||||
"shareCodes": "Condividi codice",
|
||||
"shareCodesDuration": "Seleziona la durata per la quale vuoi condividere i codici.",
|
||||
"restore": "Ripristina",
|
||||
"copiedToClipboard": "Copiato negli appunti",
|
||||
"copiedNextToClipboard": "Copiato il codice successivo negli appunti",
|
||||
"error": "Errore",
|
||||
@@ -345,6 +369,7 @@
|
||||
"sigInBackupReminder": "Si prega di esportare i codici per assicurarsi di avere un backup da cui è possibile ripristinare.",
|
||||
"offlineModeWarning": "Hai scelto di procedere senza backup. Si prega di eseguire backup manuali per assicurarsi che i codici siano al sicuro.",
|
||||
"showLargeIcons": "Mostra icone grandi",
|
||||
"compactMode": "Modalità compatta",
|
||||
"shouldHideCode": "Nascondi i codici",
|
||||
"doubleTapToViewHiddenCode": "Puoi toccare due volte una voce per visualizzare il codice",
|
||||
"focusOnSearchBar": "Apri ricerca all'avvio dell'app",
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
"faq": "FAQ",
|
||||
"scan": "Skanna",
|
||||
"twoFactorAuthTitle": "Tvåfaktorsautentisering",
|
||||
"verifyPasskey": "Verifiera nyckel",
|
||||
"enterRecoveryKeyHint": "Ange din återställningsnyckel",
|
||||
"invalidQRCode": "Ogiltig QR-kod",
|
||||
"noRecoveryKeyTitle": "Ingen återställningsnyckel?",
|
||||
@@ -200,6 +201,7 @@
|
||||
"noInternetConnection": "Ingen internetanslutning",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "Kontrollera din internetanslutning och försök igen.",
|
||||
"signOutOtherDevices": "Logga ut andra enheter",
|
||||
"passkey": "Nyckel",
|
||||
"loginSessionExpiredDetails": "Din session har upphört. Logga in igen.",
|
||||
"tags": "Taggar",
|
||||
"createNewTag": "Skapa ny tagg",
|
||||
|
||||
@@ -19,6 +19,20 @@
|
||||
"pleaseVerifyDetails": "Lütfen bilgileri doğrulayın ve tekrar deneyin",
|
||||
"codeIssuerHint": "Yayınlayan",
|
||||
"codeSecretKeyHint": "Gizli Anahtar",
|
||||
"secret": "Gizli",
|
||||
"all": "Tümü",
|
||||
"notes": "Notlar",
|
||||
"notesLengthLimit": "Notlar en fazla {count} karakter uzunluğunda olabilir",
|
||||
"@notesLengthLimit": {
|
||||
"description": "Text to indicate the maximum number of characters allowed for notes",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"description": "The maximum number of characters allowed for notes",
|
||||
"type": "int",
|
||||
"example": "100"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codeAccountHint": "Hesap (ornek@domain.com)",
|
||||
"codeTagHint": "Etiket",
|
||||
"accountKeyType": "Anahtar türü",
|
||||
@@ -34,6 +48,9 @@
|
||||
"nextTotpTitle": "sonraki",
|
||||
"deleteCodeTitle": "Kodu silmek istiyor musunuz?",
|
||||
"deleteCodeMessage": "Bu kodu silmek istediğinize emin misiniz? Bu geri alınamaz bir işlemdir.",
|
||||
"trashCode": "Kod çöpe atılsın mı?",
|
||||
"trashCodeMessage": "{account} için kodu çöpe atmak istediğinize emin misiniz?",
|
||||
"trash": "Çöp",
|
||||
"viewLogsAction": "Günlüğü görüntüle",
|
||||
"sendLogsDescription": "Günlüğünüz hatanızı çözmemize yardımcı olacaktır. Hassas bilginin kaydedilmediğine dikkat etsek de bu günlükleri paylaşmadan önce kontrol etmenizi isteriz.",
|
||||
"preparingLogsTitle": "Günlük hazırlanıyor...",
|
||||
@@ -100,6 +117,7 @@
|
||||
"emailVerificationToggle": "E-posta doğrulama",
|
||||
"emailVerificationEnableWarning": "E-postanız için 2FA'yı bizde saklıyorsanız, e-posta doğrulamasını açmak bir kilitlenmeye neden olabilir. Bir hizmetin dışında kalırsanız, diğerine giriş yapamayabilirsiniz.",
|
||||
"authToChangeEmailVerificationSetting": "E-posta doğrulamasını değiştirmek için lütfen kimlik doğrulaması yapın",
|
||||
"authenticateGeneric": "Lütfen doğrulama yapın",
|
||||
"authToViewYourRecoveryKey": "Kurtarma anahtarınızı görmek için lütfen kimliğinizi doğrulayın",
|
||||
"authToChangeYourEmail": "Epostanızı değiştirmek için lütfen kimliğinizi doğrulayın",
|
||||
"authToChangeYourPassword": "Şifrenizi değiştirmek için lütfen kimliğinizi doğrulayın",
|
||||
@@ -137,6 +155,8 @@
|
||||
"leaveFamily": "Aile planından ayrıl",
|
||||
"leaveFamilyMessage": "Aile planından ayrılmak istediğinize emin misiniz?",
|
||||
"inFamilyPlanMessage": "Aile planı kullanıyorsunuz!",
|
||||
"hintForMobile": "Bir koda uzun basarak düzenleyin veya silin.",
|
||||
"hintForDesktop": "Bir koda sağ tıklayıp düzenleyin veya silin.",
|
||||
"scan": "Tara",
|
||||
"scanACode": "Bir kodu tarayın",
|
||||
"verify": "Doğrula",
|
||||
@@ -192,6 +212,10 @@
|
||||
"scanAQrCode": "Bir QR kod tarayın",
|
||||
"enterDetailsManually": "Bilgileri elle girin",
|
||||
"edit": "Düzenle",
|
||||
"share": "Paylaş",
|
||||
"shareCodes": "Kodları paylaş",
|
||||
"shareCodesDuration": "Kodları paylaşma süresini seçin.",
|
||||
"restore": "Geri yükle",
|
||||
"copiedToClipboard": "Panoya kopyalandı",
|
||||
"copiedNextToClipboard": "Sonraki kod panoya kopyalandı",
|
||||
"error": "Hata",
|
||||
@@ -345,6 +369,7 @@
|
||||
"sigInBackupReminder": "Geri yükleyebileceğiniz bir yedeğiniz olduğundan emin olmak için lütfen kodlarınızı dışa aktarın.",
|
||||
"offlineModeWarning": "Yedekleme yapmadan devam etmeyi seçtiniz. Kodlarınızın güvende olduğundan emin olmak için lütfen manuel yedekleme yapın.",
|
||||
"showLargeIcons": "Büyük simgeler göster",
|
||||
"compactMode": "Sıkıştırılmış mod",
|
||||
"shouldHideCode": "Kodları gizle",
|
||||
"doubleTapToViewHiddenCode": "Kodu görüntülemek için bir girdiye çift dokunabilirsiniz",
|
||||
"focusOnSearchBar": "Uygulama başladığında arama bölümüne odaklan",
|
||||
@@ -465,5 +490,7 @@
|
||||
"pinLock": "Pin kilidi",
|
||||
"enterPin": "PIN Girin",
|
||||
"setNewPin": "Yeni PIN belirleyin",
|
||||
"importFailureDescNew": "Seçilen dosya ayrıştırılamadı."
|
||||
"importFailureDescNew": "Seçilen dosya ayrıştırılamadı.",
|
||||
"appLockNotEnabled": "Uygulama kilidi etkin değil",
|
||||
"appLockNotEnabledDescription": "Uygulama kilidini Güvenlik -> Uygulama Kilidi üzerinden etkinleştirin"
|
||||
}
|
||||
@@ -155,6 +155,8 @@
|
||||
"leaveFamily": "离开家庭",
|
||||
"leaveFamilyMessage": "您确定要离开家庭计划吗?",
|
||||
"inFamilyPlanMessage": "你在一个家庭计划中!",
|
||||
"hintForMobile": "长按代码即可编辑或删除。",
|
||||
"hintForDesktop": "右键单击代码即可编辑或删除。",
|
||||
"scan": "扫描",
|
||||
"scanACode": "扫描代码",
|
||||
"verify": "验证",
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
// Add more language to the list only when at least 90% of the strings are
|
||||
// translated in the corresponding language.
|
||||
const List<Locale> appSupportedLocales = <Locale>[
|
||||
Locale('bg'),
|
||||
Locale('de'),
|
||||
Locale('en'),
|
||||
Locale('es', 'ES'),
|
||||
|
||||
@@ -28,10 +28,14 @@ class Code {
|
||||
bool get isPinned => display.pinned;
|
||||
|
||||
bool get isTrashed => display.trashed;
|
||||
String get note => display.note;
|
||||
|
||||
final Object? err;
|
||||
bool get hasError => err != null;
|
||||
|
||||
String get issuerAccount =>
|
||||
account.isNotEmpty ? '$issuer ($account)' : issuer;
|
||||
|
||||
Code(
|
||||
this.account,
|
||||
this.issuer,
|
||||
@@ -170,8 +174,10 @@ class Code {
|
||||
if (uri.queryParameters.containsKey("issuer") && !path.contains(":")) {
|
||||
return path;
|
||||
}
|
||||
return path.split(':')[1];
|
||||
} catch (e) {
|
||||
return path
|
||||
.substring(path.indexOf(':') + 1); // return data after first colon
|
||||
} catch (e, s) {
|
||||
Logger('_getAccount').severe('Error while parsing account', e, s);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/events/trigger_logout_event.dart';
|
||||
import "package:ente_auth/l10n/l10n.dart";
|
||||
import 'package:ente_auth/locale.dart';
|
||||
import 'package:ente_auth/services/preference_service.dart';
|
||||
import 'package:ente_auth/theme/text_style.dart';
|
||||
import 'package:ente_auth/ui/account/email_entry_page.dart';
|
||||
import 'package:ente_auth/ui/account/login_page.dart';
|
||||
@@ -49,6 +50,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
await autoLogoutAlert(context);
|
||||
});
|
||||
super.initState();
|
||||
PreferenceService.instance.configureDefaults().ignore();
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -57,6 +57,12 @@ class PreferenceService {
|
||||
await _prefs.setBool(kCompactMode, value);
|
||||
}
|
||||
|
||||
Future<void> configureDefaults() async {
|
||||
if (!_prefs.containsKey(kCompactMode)) {
|
||||
await _prefs.setBool(kCompactMode, true);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setHideCodes(bool value) async {
|
||||
await _prefs.setBool(kShouldHideCodesKey, value);
|
||||
Bus.instance.fire(IconsChangedEvent());
|
||||
|
||||
@@ -611,11 +611,13 @@ class _CodeWidgetState extends State<CodeWidget> {
|
||||
}
|
||||
FocusScope.of(context).requestFocus();
|
||||
final l10n = context.l10n;
|
||||
final String issuerAccount = widget.code.account.isNotEmpty
|
||||
? '${widget.code.issuer} (${widget.code.account})'
|
||||
: widget.code.issuer;
|
||||
await showChoiceActionSheet(
|
||||
context,
|
||||
title: l10n.trashCode,
|
||||
body: l10n
|
||||
.trashCodeMessage('${widget.code.issuer} (${widget.code.account})'),
|
||||
body: l10n.trashCodeMessage(issuerAccount),
|
||||
firstButtonLabel: l10n.trash,
|
||||
isCritical: true,
|
||||
firstButtonOnTap: () async {
|
||||
|
||||
@@ -24,34 +24,49 @@ class _ActionBarWidgetState extends State<ActionBarWidget> {
|
||||
return SizedBox(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
child: Column(
|
||||
// left align the text
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
widget.code.issuer,
|
||||
if (widget.code.note.isNotEmpty)
|
||||
SelectableText(
|
||||
widget.code.note,
|
||||
style: textTheme.miniMuted,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
widget.onCancel?.call();
|
||||
},
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text(
|
||||
context.l10n.cancel,
|
||||
style: textTheme.mini,
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: Column(
|
||||
children: [
|
||||
SelectableText(
|
||||
widget.code.issuerAccount,
|
||||
style: textTheme.miniMuted,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
widget.onCancel?.call();
|
||||
},
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text(
|
||||
context.l10n.cancel,
|
||||
style: textTheme.mini,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:ente_auth/models/code.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/components/actions_bar_widget.dart';
|
||||
@@ -37,9 +39,12 @@ class BottomActionBarWidget extends StatelessWidget {
|
||||
final bottomPadding = MediaQuery.of(context).padding.bottom;
|
||||
final widthOfScreen = MediaQuery.of(context).size.width;
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
final double leftRightPadding = widthOfScreen > restrictedMaxWidth
|
||||
? (widthOfScreen - restrictedMaxWidth) / 2
|
||||
: 0;
|
||||
final double leftRightPadding = min(
|
||||
widthOfScreen > restrictedMaxWidth
|
||||
? (widthOfScreen - restrictedMaxWidth) / 2
|
||||
: 0,
|
||||
20,
|
||||
);
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor ?? colorScheme.backgroundElevated2,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/settings/data/import_page.dart';
|
||||
import 'package:ente_auth/ui/settings/faq.dart';
|
||||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class HomeEmptyStateWidget extends StatelessWidget {
|
||||
final VoidCallback? onScanTap;
|
||||
@@ -73,15 +73,16 @@ class HomeEmptyStateWidget extends StatelessWidget {
|
||||
const SizedBox(height: 18),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
showModalBottomSheet<void>(
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.surface,
|
||||
barrierColor: Colors.black87,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return const FAQQuestionsWidget();
|
||||
},
|
||||
);
|
||||
try {
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.faq,
|
||||
"https://help.ente.io/auth/faq",
|
||||
);
|
||||
} catch (e) {
|
||||
Logger("HomeEmptyStateWidget")
|
||||
.severe("Failed to open FAQ", e);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
l10n.faq,
|
||||
|
||||
@@ -461,7 +461,7 @@ class _HomePageState extends State<HomePage> {
|
||||
|
||||
return ClipRect(
|
||||
child: CodeWidget(
|
||||
key: ValueKey(code.hashCode),
|
||||
key: ValueKey('${code.hashCode}_$newIndex'),
|
||||
code,
|
||||
isCompactMode: isCompactMode,
|
||||
),
|
||||
@@ -493,6 +493,7 @@ class _HomePageState extends State<HomePage> {
|
||||
itemBuilder: ((context, index) {
|
||||
final codeState = _filteredCodes[index];
|
||||
return CodeWidget(
|
||||
key: ValueKey('${codeState.hashCode}_$index'),
|
||||
codeState,
|
||||
isCompactMode: isCompactMode,
|
||||
);
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/ui/common/loading_widget.dart';
|
||||
import 'package:expansion_tile_card/expansion_tile_card.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FAQQuestionsWidget extends StatelessWidget {
|
||||
const FAQQuestionsWidget({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<List<FaqItem>>(
|
||||
future: Future.value(_getFAQs(context)),
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
final faqs = <Widget>[];
|
||||
faqs.add(
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Text(
|
||||
context.l10n.faq,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
for (final faq in snapshot.data) {
|
||||
faqs.add(FaqWidget(faq: faq));
|
||||
}
|
||||
faqs.add(
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
),
|
||||
);
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: faqs,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const EnteLoadingWidget();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<FaqItem> _getFAQs(BuildContext context) {
|
||||
final l01n = context.l10n;
|
||||
List<FaqItem> faqs = [];
|
||||
faqs.add(FaqItem(q: l01n.faq_q_1, a: l01n.faq_a_1));
|
||||
faqs.add(FaqItem(q: l01n.faq_q_2, a: l01n.faq_a_2));
|
||||
faqs.add(FaqItem(q: l01n.faq_q_3, a: l01n.faq_a_3));
|
||||
faqs.add(FaqItem(q: l01n.faq_q_4, a: l01n.faq_a_4));
|
||||
faqs.add(FaqItem(q: l01n.faq_q_5, a: l01n.faq_a_5));
|
||||
return faqs;
|
||||
}
|
||||
}
|
||||
|
||||
class FaqWidget extends StatelessWidget {
|
||||
const FaqWidget({
|
||||
super.key,
|
||||
required this.faq,
|
||||
});
|
||||
|
||||
final FaqItem? faq;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(2),
|
||||
child: ExpansionTileCard(
|
||||
elevation: 0,
|
||||
title: Text(faq!.q),
|
||||
expandedTextColor: Theme.of(context).colorScheme.alternativeColor,
|
||||
baseColor: Theme.of(context).cardColor,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: 12,
|
||||
),
|
||||
child: Text(
|
||||
faq!.a,
|
||||
style: const TextStyle(
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FaqItem {
|
||||
final String q;
|
||||
final String a;
|
||||
|
||||
FaqItem({
|
||||
required this.q,
|
||||
required this.a,
|
||||
});
|
||||
|
||||
factory FaqItem.fromMap(Map<String, dynamic> map) {
|
||||
return FaqItem(
|
||||
q: map['q'] ?? 'q',
|
||||
a: map['a'] ?? 'a',
|
||||
);
|
||||
}
|
||||
|
||||
factory FaqItem.fromJson(String source) =>
|
||||
FaqItem.fromMap(json.decode(source));
|
||||
|
||||
}
|
||||
@@ -126,6 +126,8 @@ class _ItemsWidgetState extends State<ItemsWidget> {
|
||||
switch (locale.languageCode) {
|
||||
case 'en':
|
||||
return 'English';
|
||||
case 'bg':
|
||||
return 'Български';
|
||||
case 'es':
|
||||
switch (locale.countryCode) {
|
||||
case 'ES':
|
||||
|
||||
@@ -140,12 +140,13 @@ class CustomPinKeypad extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _Button extends StatelessWidget {
|
||||
class _Button extends StatefulWidget {
|
||||
final String number;
|
||||
final String text;
|
||||
final VoidCallback? onTap;
|
||||
final bool muteButton;
|
||||
final Widget? icon;
|
||||
|
||||
const _Button({
|
||||
required this.number,
|
||||
required this.text,
|
||||
@@ -154,47 +155,78 @@ class _Button extends StatelessWidget {
|
||||
this.icon,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_Button> createState() => _ButtonState();
|
||||
}
|
||||
|
||||
class _ButtonState extends State<_Button> {
|
||||
bool isPressed = false;
|
||||
|
||||
void _onTapDown(TapDownDetails details) {
|
||||
setState(() {
|
||||
isPressed = true;
|
||||
});
|
||||
}
|
||||
|
||||
void _onTapUp(TapUpDetails details) async {
|
||||
setState(() {
|
||||
isPressed = false;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
final textTheme = getEnteTextTheme(context);
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.rectangle,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
color: muteButton
|
||||
? colorScheme.fillFaintPressed
|
||||
: icon == null
|
||||
? colorScheme.backgroundElevated2
|
||||
: null,
|
||||
),
|
||||
child: Center(
|
||||
child: muteButton
|
||||
? const SizedBox.shrink()
|
||||
: icon != null
|
||||
? Container(
|
||||
child: icon,
|
||||
)
|
||||
: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
number,
|
||||
style: textTheme.h3,
|
||||
),
|
||||
Text(
|
||||
text,
|
||||
style: textTheme.tinyBold,
|
||||
),
|
||||
],
|
||||
onTap: widget.onTap,
|
||||
onTapDown: _onTapDown,
|
||||
onTapUp: _onTapUp,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
curve: Curves.easeOut,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.rectangle,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
color: isPressed
|
||||
? colorScheme.backgroundElevated
|
||||
: widget.muteButton
|
||||
? colorScheme.fillFaintPressed
|
||||
: widget.icon == null
|
||||
? colorScheme.backgroundElevated2
|
||||
: null,
|
||||
),
|
||||
child: Center(
|
||||
child: widget.muteButton
|
||||
? const SizedBox.shrink()
|
||||
: widget.icon != null
|
||||
? Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 10,
|
||||
),
|
||||
child: widget.icon,
|
||||
)
|
||||
: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
widget.number,
|
||||
style: textTheme.h3,
|
||||
),
|
||||
Text(
|
||||
widget.text,
|
||||
style: textTheme.tinyBold,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -101,7 +101,18 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async => await onPasskeyClick(context),
|
||||
onTap: () async {
|
||||
final bool hasAuthenticated = await LocalAuthenticationService
|
||||
.instance
|
||||
.requestLocalAuthentication(
|
||||
context,
|
||||
context.l10n.authToViewPasskey,
|
||||
);
|
||||
await PlatformUtil.refocusWindows();
|
||||
if (hasAuthenticated) {
|
||||
await onPasskeyClick(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
@@ -178,6 +189,15 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
||||
|
||||
Future<void> onPasskeyClick(BuildContext buildContext) async {
|
||||
try {
|
||||
final hasAuthenticated =
|
||||
await LocalAuthenticationService.instance.requestLocalAuthentication(
|
||||
context,
|
||||
context.l10n.authenticateGeneric,
|
||||
);
|
||||
await PlatformUtil.refocusWindows();
|
||||
if (!hasAuthenticated) {
|
||||
return;
|
||||
}
|
||||
final isPassKeyResetEnabled =
|
||||
await PasskeyService.instance.isPasskeyRecoveryEnabled();
|
||||
if (!isPassKeyResetEnabled) {
|
||||
|
||||
@@ -5,8 +5,8 @@ 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/ui/settings/faq.dart';
|
||||
import 'package:ente_auth/utils/email_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
@@ -42,15 +42,15 @@ class _SupportSectionWidgetState extends State<SupportSectionWidget> {
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
// ignore: unawaited_futures
|
||||
showModalBottomSheet<void>(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
barrierColor: Colors.black87,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return const FAQQuestionsWidget();
|
||||
},
|
||||
);
|
||||
try {
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.faq,
|
||||
"https://help.ente.io/auth/faq",
|
||||
);
|
||||
} catch (e) {
|
||||
Logger("SupportSection").severe("Failed to open FAQ", e);
|
||||
}
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:confetti/confetti.dart';
|
||||
@@ -14,6 +15,7 @@ import 'package:ente_auth/ui/components/dialog_widget.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_result.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_type.dart';
|
||||
import 'package:ente_auth/utils/email_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -140,11 +142,19 @@ Future<ButtonResult?> showGenericErrorDialog({
|
||||
bool isDismissible = true,
|
||||
required Object? error,
|
||||
}) async {
|
||||
final errorBody = parseErrorForUI(
|
||||
String errorBody = parseErrorForUI(
|
||||
context,
|
||||
context.l10n.itLooksLikeSomethingWentWrongPleaseRetryAfterSome,
|
||||
error: error,
|
||||
);
|
||||
bool isWindowCertError = false;
|
||||
if (Platform.isWindows &&
|
||||
error != null &&
|
||||
error.toString().contains("CERTIFICATE_VERIFY_FAILED")) {
|
||||
isWindowCertError = true;
|
||||
errorBody =
|
||||
"Certificate verification failed. Please update your system certificates, & restart the app. If the issue persists, please contact support.";
|
||||
}
|
||||
|
||||
return showDialogWidget(
|
||||
context: context,
|
||||
@@ -159,6 +169,20 @@ Future<ButtonResult?> showGenericErrorDialog({
|
||||
buttonAction: ButtonAction.first,
|
||||
isInAlert: true,
|
||||
),
|
||||
if (isWindowCertError)
|
||||
ButtonWidget(
|
||||
buttonType: ButtonType.neutral,
|
||||
labelText: 'Update Certificates',
|
||||
buttonAction: ButtonAction.third,
|
||||
isInAlert: true,
|
||||
onTap: () async {
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.faq,
|
||||
"https://help.ente.io/auth/troubleshooting/windows-login",
|
||||
);
|
||||
},
|
||||
),
|
||||
ButtonWidget(
|
||||
buttonType: ButtonType.secondary,
|
||||
labelText: context.l10n.contactSupport,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: ente_auth
|
||||
description: ente two-factor authenticator
|
||||
version: 3.1.8+328
|
||||
version: 4.0.1+401
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var AppVersion = "0.2.0"
|
||||
var AppVersion = "0.2.1"
|
||||
|
||||
func main() {
|
||||
cliDBPath, err := GetCLIConfigPath()
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func MapRemoteAuthEntityToString(ctx context.Context, authEntity models.AuthEntity, authKey []byte) (*string, error) {
|
||||
_, decrypted, err := eCrypto.DecryptChaChaBase64(*authEntity.EncryptedData, authKey, *authEntity.Header)
|
||||
_, decrypted, err := eCrypto.DecryptChaChaBase64Auth(*authEntity.EncryptedData, authKey, *authEntity.Header)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decrypt auth enityt %s: %v", authEntity.ID, err)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
## v1.7.5 (Unreleased)
|
||||
|
||||
- Directly upload to selected album on drag and drop.
|
||||
- Include shared files in export.
|
||||
- .
|
||||
|
||||
## v1.7.4
|
||||
|
||||
@@ -254,6 +254,10 @@ export const sidebar = [
|
||||
text: "Using external S3",
|
||||
link: "/self-hosting/guides/external-s3",
|
||||
},
|
||||
{
|
||||
text: "DB migration",
|
||||
link: "/self-hosting/guides/db-migration",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -11,13 +11,9 @@ All codes you backup via Ente is stored end-to-end encrypted. This means only
|
||||
you can access your codes. Our apps are open source and our cryptography has
|
||||
been externally audited.
|
||||
|
||||
### Can I access my codes on desktop?
|
||||
|
||||
You can access your codes on the web at [auth.ente.io](https://auth.ente.io).
|
||||
|
||||
### How can I delete or edit codes?
|
||||
|
||||
You can delete or edit a code by swiping left on that item.
|
||||
You can delete or edit a code long press (or right click on desktop) on that item.
|
||||
|
||||
### How can I support this project?
|
||||
|
||||
@@ -39,6 +35,10 @@ Usually this discrepancy occurs because the time in your browser might be
|
||||
incorrect. In particular, multiple users who have reported that Firefox provides
|
||||
incorrect time when various privacy settings are enabled.
|
||||
|
||||
### Can I access my codes on web?
|
||||
|
||||
You can access your codes on the web at [auth.ente.io](https://auth.ente.io).
|
||||
|
||||
### Does Ente Authenticator require an account?
|
||||
|
||||
Answer: No, Ente Authenticator does not require an account. You can choose to
|
||||
@@ -49,6 +49,10 @@ use the app without backups if you prefer.
|
||||
Yes, you can download the Ente app on multiple devices and sync the codes,
|
||||
end-to-end encrypted.
|
||||
|
||||
### What information about my codes is stored on Ente server?
|
||||
|
||||
Due to E2EE, the server doesn't know anything about the code. Everything is encrypted, including the tags, type, account, issuer, notes, and pinned or trash status, etc."
|
||||
|
||||
### What does it mean when I receive a message saying my current device is not powerful enough to verify my password?
|
||||
|
||||
This means that the parameters that were used to derive your master-key on your
|
||||
|
||||
@@ -60,6 +60,21 @@ As mentioned above, the auth data is encrypted using a key that's derived by
|
||||
using user provided password & kdf params. For encryption, we are using
|
||||
`XChaCha20-Poly1305` algorithm.
|
||||
|
||||
## Automated backups
|
||||
|
||||
You can use [Ente's CLI](https://github.com/ente-io/ente/tree/main/cli#readme)
|
||||
to automatically backup your Auth codes.
|
||||
|
||||
To export your data, add an account using `ente account add` command. In the
|
||||
first step, specify `auth` as the app name. At a later point, CLI will also ask
|
||||
you specify the path where it should write the exported codes.
|
||||
|
||||
You can change the export directory using following command
|
||||
|
||||
```
|
||||
ente account update --app auth --email <email> --dir <path>
|
||||
```
|
||||
|
||||
## How to use the exported data
|
||||
|
||||
- **Ente Authenticator app**: You can directly import the codes in the Ente
|
||||
@@ -67,9 +82,9 @@ using user provided password & kdf params. For encryption, we are using
|
||||
|
||||
> Settings -> Data -> Import Codes -> Ente Encrypted export.
|
||||
|
||||
- **Decrypt using Ente CLI** : Download the latest version of
|
||||
[Ente CLI](https://github.com/ente-io/ente/releases?q=tag%3Acli-v0), and run
|
||||
the following command
|
||||
- **Decrypt using Ente CLI** : Download the latest version of [Ente
|
||||
CLI](https://github.com/ente-io/ente/releases?q=tag%3Acli-v0), and run the
|
||||
following command
|
||||
|
||||
```
|
||||
./ente auth decrypt <export_file> <output_file>
|
||||
|
||||
@@ -176,3 +176,7 @@ you can gain more value out of a single subscription.
|
||||
## Is there a forever-free plan?
|
||||
|
||||
Yes, we offer 5 GB of storage for free.
|
||||
|
||||
## What are the limitations of the free plan?
|
||||
|
||||
You cannot share albums, or setup a family while you are on a free plan.
|
||||
|
||||
@@ -56,9 +56,6 @@ Code_.
|
||||
|
||||
</div>
|
||||
|
||||
Please note that referral codes should be applied within one month of account
|
||||
creation to claim free storage.
|
||||
|
||||
---
|
||||
|
||||
More questions? Drop a mail to [referrals@ente.io](mailto:referrals@ente.io),
|
||||
|
||||
@@ -93,3 +93,8 @@ implemented, are in various blog posts announcing these features.
|
||||
|
||||
We are now working on the other requested features around sharing, including
|
||||
comments and reactions.
|
||||
|
||||
## Limitations
|
||||
|
||||
Sharing is only available to paid customers. This limitation safeguards against
|
||||
potential platform abuse.
|
||||
|
||||
158
docs/docs/self-hosting/guides/db-migration.md
Normal file
@@ -0,0 +1,158 @@
|
||||
---
|
||||
title: DB Migration
|
||||
description:
|
||||
Migrating your self hosted Postgres 12 database to newer Postgres versions
|
||||
---
|
||||
|
||||
# Migrating Postgres 12 to 15
|
||||
|
||||
The old sample docker compose file used Postgres 12, which is now nearing end of
|
||||
life, so we've updated it to Postgres 15. Postgres major versions changes
|
||||
require a migration step. This document mentions some approaches you can use.
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> Ente itself does not use any specific Postgres 12 or Postgres 15 features, and
|
||||
> will talk to either happily. It should also work with newer Postgres versions,
|
||||
> but let us know if you run into any problems and we'll update this page.
|
||||
|
||||
### Taking a backup
|
||||
|
||||
`docker compose exec` allows us to run a command against a running container. We
|
||||
can use it to run the `pg_dumpall` command on the postgres container to create a
|
||||
plaintext backup.
|
||||
|
||||
Assuming your cluster is already running, and you are in the `ente/server`
|
||||
directory, you can run the following (this command uses the default credentials,
|
||||
you'll need to change these to match your setup):
|
||||
|
||||
```sh
|
||||
docker compose exec postgres env PGPASSWORD=pgpass PGUSER=pguser PG_DB=ente_db pg_dumpall >pg12.backup.sql
|
||||
```
|
||||
|
||||
This will produce a `pg12.backup.sql` in your current directory. You can open it
|
||||
in a text editor (it can be huge!) to verify that it looks correct.
|
||||
|
||||
We won't be needing this file, this backup is recommended just in case something
|
||||
goes amiss with the actual migration.
|
||||
|
||||
> If you need to restore from this plaintext backup, you could subsequently run
|
||||
> something like:
|
||||
>
|
||||
> ```sh
|
||||
> docker compose up postgres
|
||||
> cat pg12.backup.sql | docker compose exec -T postgres env PGPASSWORD=pgpass psql -U pguser -d ente_db
|
||||
> ```
|
||||
|
||||
## The migration
|
||||
|
||||
At the high level, the steps are
|
||||
|
||||
1. Stop your cluster.
|
||||
|
||||
2. Start just the postgres container after changing the image to
|
||||
`pgautoupgrade/pgautoupgrade:15-bookworm`.
|
||||
|
||||
3. Once the in-place migration completes, stop the container, and change the
|
||||
image to `postgres:15`.
|
||||
|
||||
#### 1. Stop the cluster
|
||||
|
||||
Stop your running Ente cluster.
|
||||
|
||||
```sh
|
||||
docker compose down
|
||||
```
|
||||
|
||||
#### 2. Run `pgautoupgrade`
|
||||
|
||||
Modify your `compose.yaml`, changing the image for the "postgres" container from
|
||||
"postgres:12" to "pgautoupgrade/pgautoupgrade:15-bookworm"
|
||||
|
||||
```diff
|
||||
diff a/server/compose.yaml b/server/compose.yaml
|
||||
|
||||
postgres:
|
||||
- image: postgres:12
|
||||
+ image: pgautoupgrade/pgautoupgrade:15-bookworm
|
||||
ports:
|
||||
```
|
||||
|
||||
[pgautoupgrade](https://github.com/pgautoupgrade/docker-pgautoupgrade) is a
|
||||
community docker image that performs an in-place migration.
|
||||
|
||||
After making the change, run only the `postgres` container in the cluster
|
||||
|
||||
```sh
|
||||
docker compose up postgres
|
||||
```
|
||||
|
||||
The container will start and peform an in-place migration. Once it is done, it
|
||||
will start postgres normally. You should see something like this is the logs
|
||||
|
||||
```
|
||||
postgres-1 | Automatic upgrade process finished with no errors reported
|
||||
...
|
||||
postgres-1 | ... starting PostgreSQL 15...
|
||||
```
|
||||
|
||||
At this point, you can stop the container (`CTRL-C`).
|
||||
|
||||
#### 3. Finish by changing image
|
||||
|
||||
Modify `compose.yaml` again, changing the image to "postgres:15".
|
||||
|
||||
```diff
|
||||
diff a/server/compose.yaml b/server/compose.yaml
|
||||
|
||||
postgres:
|
||||
- image: pgautoupgrade/pgautoupgrade:15-bookworm
|
||||
+ image: postgres:15
|
||||
ports:
|
||||
```
|
||||
|
||||
And cleanup the temporary containers by
|
||||
|
||||
```sh
|
||||
docker compose down --remove-orphans
|
||||
```
|
||||
|
||||
Migration is now complete. You can start your Ente cluster normally.
|
||||
|
||||
```sh
|
||||
docker compose up
|
||||
```
|
||||
|
||||
## Migration elsewhere
|
||||
|
||||
The above instructions are for Postgres running inside docker, as the sample
|
||||
docker compose file does. There are myriad other ways to run Postgres, and the
|
||||
migration sequence then will depend on your exact setup.
|
||||
|
||||
Two common approaches are
|
||||
|
||||
1. Backup and restore, the `pg_dumpall` + `psql` import sequence described in
|
||||
[Taking a backup](#taking-a-backup) above.
|
||||
|
||||
2. In place migrations using `pg_upgrade`, which is what the
|
||||
[pgautoupgrade](#the-migration) migration above does under the hood.
|
||||
|
||||
The first method, backup and restore, is low tech and will work similarly in
|
||||
most setups. The second method is more efficient, but requires a bit more
|
||||
careful preparation.
|
||||
|
||||
As another example, here is how one can migrate 12 to 15 when running Postgres
|
||||
on macOS, installed using Homebrew.
|
||||
|
||||
1. Stop your postgres. Make sure there are no more commands shown by
|
||||
`ps aux | grep '[p]ostgres'`.
|
||||
|
||||
2. Install postgres15.
|
||||
|
||||
3. Migrate data using `pg_upgrade`:
|
||||
|
||||
```sh
|
||||
/opt/homebrew/Cellar/postgresql@15/15.8/bin/pg_upgrade -b /opt/homebrew/Cellar/postgresql@12/12.18_1/bin -B /opt/homebrew/Cellar/postgresql@15/15.8/bin/ -d /opt/homebrew/var/postgresql@12 -D /opt/homebrew/var/postgresql@15
|
||||
```
|
||||
|
||||
4. Start postgres 15 and verify version using `SELECT VERSION()`.
|
||||
@@ -3,21 +3,16 @@ FROM ubuntu:latest
|
||||
RUN apt-get update && apt-get install -y curl gnupg
|
||||
RUN apt-get install -y tini
|
||||
|
||||
# Install pg_dump (via Postgres client)
|
||||
# Install Postgres client (needed for restores, and for local testing)
|
||||
# https://www.postgresql.org/download/linux/ubuntu/
|
||||
#
|
||||
# We don't need it for production backups, but this is useful for local testing.
|
||||
RUN \
|
||||
apt-get install -y lsb-release && \
|
||||
sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' && \
|
||||
curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \
|
||||
apt-get update && \
|
||||
apt-get -y install postgresql-client-12
|
||||
# We don't have specific dependencies on Postgres, so just use the latest.
|
||||
RUN apt-get update && apt-get -y install postgresql-client
|
||||
|
||||
# Install SCW CLI
|
||||
# Latest release: https://github.com/scaleway/scaleway-cli/releases/latest
|
||||
RUN \
|
||||
export VERSION="2.32.1" && \
|
||||
export VERSION="2.34.0" && \
|
||||
curl -o /usr/local/bin/scw -L "https://github.com/scaleway/scaleway-cli/releases/download/v${VERSION}/scaleway-cli_${VERSION}_linux_amd64" && \
|
||||
chmod +x /usr/local/bin/scw
|
||||
|
||||
|
||||
4
mobile/lib/generated/intl/messages_cs.dart
generated
@@ -21,5 +21,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'cs';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{};
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"incorrectRecoveryKeyBody": MessageLookupByLibrary.simpleMessage("")
|
||||
};
|
||||
}
|
||||
|
||||
20
mobile/lib/generated/intl/messages_de.dart
generated
@@ -661,6 +661,26 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Zwei-Faktor-Authentifizierung (2FA) wird deaktiviert..."),
|
||||
"discord": MessageLookupByLibrary.simpleMessage("Discord"),
|
||||
"discover": MessageLookupByLibrary.simpleMessage("Entdecken"),
|
||||
"discover_babies": MessageLookupByLibrary.simpleMessage("Babys"),
|
||||
"discover_celebrations": MessageLookupByLibrary.simpleMessage("Feiern"),
|
||||
"discover_food": MessageLookupByLibrary.simpleMessage("Essen"),
|
||||
"discover_greenery": MessageLookupByLibrary.simpleMessage("Grün"),
|
||||
"discover_hills": MessageLookupByLibrary.simpleMessage("Berge"),
|
||||
"discover_identity": MessageLookupByLibrary.simpleMessage("Identität"),
|
||||
"discover_memes": MessageLookupByLibrary.simpleMessage("Memes"),
|
||||
"discover_notes": MessageLookupByLibrary.simpleMessage("Notizen"),
|
||||
"discover_pets": MessageLookupByLibrary.simpleMessage("Haustiere"),
|
||||
"discover_receipts": MessageLookupByLibrary.simpleMessage("Belege"),
|
||||
"discover_screenshots":
|
||||
MessageLookupByLibrary.simpleMessage("Bildschirmfotos"),
|
||||
"discover_selfies": MessageLookupByLibrary.simpleMessage("Selfies"),
|
||||
"discover_sunset":
|
||||
MessageLookupByLibrary.simpleMessage("Sonnenuntergang"),
|
||||
"discover_visiting_cards":
|
||||
MessageLookupByLibrary.simpleMessage("Visitenkarten"),
|
||||
"discover_wallpapers":
|
||||
MessageLookupByLibrary.simpleMessage("Hintergründe"),
|
||||
"dismiss": MessageLookupByLibrary.simpleMessage("Verwerfen"),
|
||||
"distanceInKMUnit": MessageLookupByLibrary.simpleMessage("km"),
|
||||
"doNotSignOut":
|
||||
|
||||
21
mobile/lib/generated/intl/messages_fr.dart
generated
@@ -675,6 +675,27 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Désactiver la double-authentification..."),
|
||||
"discord": MessageLookupByLibrary.simpleMessage("Discord"),
|
||||
"discover": MessageLookupByLibrary.simpleMessage("Découverte"),
|
||||
"discover_babies": MessageLookupByLibrary.simpleMessage("Bébés"),
|
||||
"discover_celebrations": MessageLookupByLibrary.simpleMessage("Fêtes"),
|
||||
"discover_food": MessageLookupByLibrary.simpleMessage("Alimentation"),
|
||||
"discover_greenery": MessageLookupByLibrary.simpleMessage("Plantes"),
|
||||
"discover_hills": MessageLookupByLibrary.simpleMessage("Montagnes"),
|
||||
"discover_identity": MessageLookupByLibrary.simpleMessage("Identité"),
|
||||
"discover_memes": MessageLookupByLibrary.simpleMessage("Mèmes"),
|
||||
"discover_notes": MessageLookupByLibrary.simpleMessage("Notes"),
|
||||
"discover_pets":
|
||||
MessageLookupByLibrary.simpleMessage("Animaux de compagnie"),
|
||||
"discover_receipts": MessageLookupByLibrary.simpleMessage("Recettes"),
|
||||
"discover_screenshots":
|
||||
MessageLookupByLibrary.simpleMessage("Captures d\'écran "),
|
||||
"discover_selfies": MessageLookupByLibrary.simpleMessage("Selfies"),
|
||||
"discover_sunset":
|
||||
MessageLookupByLibrary.simpleMessage("Coucher du soleil"),
|
||||
"discover_visiting_cards":
|
||||
MessageLookupByLibrary.simpleMessage("Carte de Visite"),
|
||||
"discover_wallpapers":
|
||||
MessageLookupByLibrary.simpleMessage("Fonds d\'écran"),
|
||||
"dismiss": MessageLookupByLibrary.simpleMessage("Rejeter"),
|
||||
"distanceInKMUnit": MessageLookupByLibrary.simpleMessage("km"),
|
||||
"doNotSignOut":
|
||||
|
||||
26
mobile/lib/generated/intl/messages_id.dart
generated
@@ -565,6 +565,19 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Menonaktifkan autentikasi dua langkah..."),
|
||||
"discord": MessageLookupByLibrary.simpleMessage("Discord"),
|
||||
"discover_babies": MessageLookupByLibrary.simpleMessage("Bayi"),
|
||||
"discover_food": MessageLookupByLibrary.simpleMessage("Makanan"),
|
||||
"discover_hills": MessageLookupByLibrary.simpleMessage("Bukit"),
|
||||
"discover_identity": MessageLookupByLibrary.simpleMessage("Identitas"),
|
||||
"discover_memes": MessageLookupByLibrary.simpleMessage("Meme"),
|
||||
"discover_notes": MessageLookupByLibrary.simpleMessage("Catatan"),
|
||||
"discover_pets": MessageLookupByLibrary.simpleMessage("Peliharaan"),
|
||||
"discover_screenshots":
|
||||
MessageLookupByLibrary.simpleMessage("Tangkapan layar"),
|
||||
"discover_selfies": MessageLookupByLibrary.simpleMessage("Swafoto"),
|
||||
"discover_sunset": MessageLookupByLibrary.simpleMessage("Senja"),
|
||||
"discover_wallpapers":
|
||||
MessageLookupByLibrary.simpleMessage("Gambar latar"),
|
||||
"distanceInKMUnit": MessageLookupByLibrary.simpleMessage("km"),
|
||||
"doNotSignOut":
|
||||
MessageLookupByLibrary.simpleMessage("Jangan keluarkan akun"),
|
||||
@@ -792,6 +805,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"kindlyHelpUsWithThisInformation": MessageLookupByLibrary.simpleMessage(
|
||||
"Harap bantu kami dengan informasi ini"),
|
||||
"language": MessageLookupByLibrary.simpleMessage("Bahasa"),
|
||||
"lastUpdated":
|
||||
MessageLookupByLibrary.simpleMessage("Terakhir diperbaharui"),
|
||||
"leave": MessageLookupByLibrary.simpleMessage("Tinggalkan"),
|
||||
"leaveAlbum": MessageLookupByLibrary.simpleMessage("Tinggalkan album"),
|
||||
"leaveFamily":
|
||||
@@ -897,6 +912,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Pindah ke sampah"),
|
||||
"movingFilesToAlbum": MessageLookupByLibrary.simpleMessage(
|
||||
"Memindahkan file ke album..."),
|
||||
"name": MessageLookupByLibrary.simpleMessage("Nama"),
|
||||
"networkConnectionRefusedErr": MessageLookupByLibrary.simpleMessage(
|
||||
"Tidak dapat terhubung dengan Ente, silakan coba lagi setelah beberapa saat. Jika masalah berlanjut, harap hubungi dukungan."),
|
||||
"networkHostLookUpErr": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -904,6 +920,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"never": MessageLookupByLibrary.simpleMessage("Tidak pernah"),
|
||||
"newAlbum": MessageLookupByLibrary.simpleMessage("Album baru"),
|
||||
"newToEnte": MessageLookupByLibrary.simpleMessage("Baru di Ente"),
|
||||
"newest": MessageLookupByLibrary.simpleMessage("Terbaru"),
|
||||
"no": MessageLookupByLibrary.simpleMessage("Tidak"),
|
||||
"noAlbumsSharedByYouYet": MessageLookupByLibrary.simpleMessage(
|
||||
"Belum ada album yang kamu bagikan"),
|
||||
@@ -1040,11 +1057,15 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Kebijakan Privasi"),
|
||||
"privateBackups":
|
||||
MessageLookupByLibrary.simpleMessage("Cadangan pribadi"),
|
||||
"privateSharing":
|
||||
MessageLookupByLibrary.simpleMessage("Berbagi secara privat"),
|
||||
"publicLinkCreated":
|
||||
MessageLookupByLibrary.simpleMessage("Link publik dibuat"),
|
||||
"publicLinkEnabled":
|
||||
MessageLookupByLibrary.simpleMessage("Link publik aktif"),
|
||||
"radius": MessageLookupByLibrary.simpleMessage("Radius"),
|
||||
"raiseTicket":
|
||||
MessageLookupByLibrary.simpleMessage("Buat tiket dukungan"),
|
||||
"rateTheApp": MessageLookupByLibrary.simpleMessage("Nilai app ini"),
|
||||
"rateUs": MessageLookupByLibrary.simpleMessage("Beri kami nilai"),
|
||||
"rateUsOnStore": m47,
|
||||
@@ -1197,6 +1218,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"sendLink": MessageLookupByLibrary.simpleMessage("Kirim link"),
|
||||
"serverEndpoint":
|
||||
MessageLookupByLibrary.simpleMessage("Endpoint server"),
|
||||
"sessionExpired":
|
||||
MessageLookupByLibrary.simpleMessage("Sesi telah berakhir"),
|
||||
"setAPassword": MessageLookupByLibrary.simpleMessage("Atur sandi"),
|
||||
"setAs": MessageLookupByLibrary.simpleMessage("Pasang sebagai"),
|
||||
"setCover": MessageLookupByLibrary.simpleMessage("Ubah sampul"),
|
||||
@@ -1295,6 +1318,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Keluarga"),
|
||||
"storageBreakupYou": MessageLookupByLibrary.simpleMessage("Kamu"),
|
||||
"storageInGB": m60,
|
||||
"storageLimitExceeded": MessageLookupByLibrary.simpleMessage(
|
||||
"Batas penyimpanan terlampaui"),
|
||||
"storageUsageInfo": m61,
|
||||
"strongStrength": MessageLookupByLibrary.simpleMessage("Kuat"),
|
||||
"subAlreadyLinkedErrMessage": m62,
|
||||
@@ -1405,6 +1430,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Pembaruan tersedia"),
|
||||
"updatingFolderSelection": MessageLookupByLibrary.simpleMessage(
|
||||
"Memperbaharui pilihan folder..."),
|
||||
"upgrade": MessageLookupByLibrary.simpleMessage("Tingkatkan"),
|
||||
"uploadingFilesToAlbum":
|
||||
MessageLookupByLibrary.simpleMessage("Mengunggah file ke album..."),
|
||||
"upto50OffUntil4thDec": MessageLookupByLibrary.simpleMessage(
|
||||
|
||||
25
mobile/lib/generated/intl/messages_it.dart
generated
@@ -126,7 +126,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
|
||||
static String m38(albumName) => "Spostato con successo su ${albumName}";
|
||||
|
||||
static String m39(name) => "Non sei ${name}?";
|
||||
static String m39(name) => "Non è ${name}?";
|
||||
|
||||
static String m40(familyAdminEmail) =>
|
||||
"Per favore contatta ${familyAdminEmail} per cambiare il tuo codice.";
|
||||
@@ -659,6 +659,27 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Disattivazione autenticazione a due fattori..."),
|
||||
"discord": MessageLookupByLibrary.simpleMessage("Discord"),
|
||||
"discover": MessageLookupByLibrary.simpleMessage("Scopri"),
|
||||
"discover_babies": MessageLookupByLibrary.simpleMessage("Neonati"),
|
||||
"discover_celebrations":
|
||||
MessageLookupByLibrary.simpleMessage("Festeggiamenti"),
|
||||
"discover_food": MessageLookupByLibrary.simpleMessage("Cibo"),
|
||||
"discover_greenery":
|
||||
MessageLookupByLibrary.simpleMessage("Vegetazione"),
|
||||
"discover_hills": MessageLookupByLibrary.simpleMessage("Colline"),
|
||||
"discover_identity": MessageLookupByLibrary.simpleMessage("Identità"),
|
||||
"discover_memes": MessageLookupByLibrary.simpleMessage("Meme"),
|
||||
"discover_notes": MessageLookupByLibrary.simpleMessage("Note"),
|
||||
"discover_pets":
|
||||
MessageLookupByLibrary.simpleMessage("Animali domestici"),
|
||||
"discover_receipts": MessageLookupByLibrary.simpleMessage("Ricette"),
|
||||
"discover_screenshots":
|
||||
MessageLookupByLibrary.simpleMessage("Schermate"),
|
||||
"discover_selfies": MessageLookupByLibrary.simpleMessage("Selfie"),
|
||||
"discover_sunset": MessageLookupByLibrary.simpleMessage("Tramonto"),
|
||||
"discover_visiting_cards":
|
||||
MessageLookupByLibrary.simpleMessage("Biglietti da Visita"),
|
||||
"discover_wallpapers": MessageLookupByLibrary.simpleMessage("Sfondi"),
|
||||
"dismiss": MessageLookupByLibrary.simpleMessage("Ignora"),
|
||||
"distanceInKMUnit": MessageLookupByLibrary.simpleMessage("km"),
|
||||
"doNotSignOut": MessageLookupByLibrary.simpleMessage("Non uscire"),
|
||||
@@ -1549,6 +1570,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"subAlreadyLinkedErrMessage": m62,
|
||||
"subWillBeCancelledOn": m63,
|
||||
"subscribe": MessageLookupByLibrary.simpleMessage("Iscriviti"),
|
||||
"subscribeToEnableSharing": MessageLookupByLibrary.simpleMessage(
|
||||
"È necessario un abbonamento a pagamento attivo per abilitare la condivisione."),
|
||||
"subscription": MessageLookupByLibrary.simpleMessage("Abbonamento"),
|
||||
"success": MessageLookupByLibrary.simpleMessage("Operazione riuscita"),
|
||||
"successfullyArchived":
|
||||
|
||||
21
mobile/lib/generated/intl/messages_pl.dart
generated
@@ -657,6 +657,27 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Uwierzytelnianie dwustopniowe jest wyłączane..."),
|
||||
"discord": MessageLookupByLibrary.simpleMessage("Discord"),
|
||||
"discover": MessageLookupByLibrary.simpleMessage("Odkryj"),
|
||||
"discover_babies": MessageLookupByLibrary.simpleMessage("Niemowlęta"),
|
||||
"discover_celebrations":
|
||||
MessageLookupByLibrary.simpleMessage("Uroczystości"),
|
||||
"discover_food": MessageLookupByLibrary.simpleMessage("Jedzenie"),
|
||||
"discover_greenery": MessageLookupByLibrary.simpleMessage("Zieleń"),
|
||||
"discover_hills": MessageLookupByLibrary.simpleMessage("Wzgórza"),
|
||||
"discover_identity": MessageLookupByLibrary.simpleMessage("Tożsamość"),
|
||||
"discover_memes": MessageLookupByLibrary.simpleMessage("Memy"),
|
||||
"discover_notes": MessageLookupByLibrary.simpleMessage("Notatki"),
|
||||
"discover_pets":
|
||||
MessageLookupByLibrary.simpleMessage("Zwierzęta domowe"),
|
||||
"discover_receipts": MessageLookupByLibrary.simpleMessage("Paragony"),
|
||||
"discover_screenshots":
|
||||
MessageLookupByLibrary.simpleMessage("Zrzuty ekranu"),
|
||||
"discover_selfies": MessageLookupByLibrary.simpleMessage("Selfie"),
|
||||
"discover_sunset":
|
||||
MessageLookupByLibrary.simpleMessage("Zachód słońca"),
|
||||
"discover_visiting_cards":
|
||||
MessageLookupByLibrary.simpleMessage("Wizytówki"),
|
||||
"discover_wallpapers": MessageLookupByLibrary.simpleMessage("Tapety"),
|
||||
"dismiss": MessageLookupByLibrary.simpleMessage("Odrzuć"),
|
||||
"distanceInKMUnit": MessageLookupByLibrary.simpleMessage("km"),
|
||||
"doNotSignOut":
|
||||
|
||||
21
mobile/lib/generated/intl/messages_pt.dart
generated
@@ -655,6 +655,27 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Desativando a autenticação de dois fatores..."),
|
||||
"discord": MessageLookupByLibrary.simpleMessage("Discord"),
|
||||
"discover": MessageLookupByLibrary.simpleMessage("Explorar"),
|
||||
"discover_babies": MessageLookupByLibrary.simpleMessage("Bebés"),
|
||||
"discover_celebrations":
|
||||
MessageLookupByLibrary.simpleMessage("Celebrações"),
|
||||
"discover_food": MessageLookupByLibrary.simpleMessage("Comida"),
|
||||
"discover_greenery": MessageLookupByLibrary.simpleMessage("Vegetação"),
|
||||
"discover_hills": MessageLookupByLibrary.simpleMessage("Montanhas"),
|
||||
"discover_identity": MessageLookupByLibrary.simpleMessage("Identidade"),
|
||||
"discover_memes": MessageLookupByLibrary.simpleMessage("Memes"),
|
||||
"discover_notes": MessageLookupByLibrary.simpleMessage("Notas"),
|
||||
"discover_pets":
|
||||
MessageLookupByLibrary.simpleMessage("Animais de Estimação"),
|
||||
"discover_receipts": MessageLookupByLibrary.simpleMessage("Recibos"),
|
||||
"discover_screenshots":
|
||||
MessageLookupByLibrary.simpleMessage("Capturas de Tela"),
|
||||
"discover_selfies": MessageLookupByLibrary.simpleMessage("Selfies"),
|
||||
"discover_sunset": MessageLookupByLibrary.simpleMessage("Pôr do Sol"),
|
||||
"discover_visiting_cards":
|
||||
MessageLookupByLibrary.simpleMessage("Cartões de Visita"),
|
||||
"discover_wallpapers":
|
||||
MessageLookupByLibrary.simpleMessage("Papéis de Parede"),
|
||||
"dismiss": MessageLookupByLibrary.simpleMessage("Descartar"),
|
||||
"distanceInKMUnit": MessageLookupByLibrary.simpleMessage("km"),
|
||||
"doNotSignOut":
|
||||
|
||||
5
mobile/lib/generated/intl/messages_sv.dart
generated
@@ -232,6 +232,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"disableDownloadWarningTitle":
|
||||
MessageLookupByLibrary.simpleMessage("Vänligen notera:"),
|
||||
"discord": MessageLookupByLibrary.simpleMessage("Discord"),
|
||||
"discover_notes": MessageLookupByLibrary.simpleMessage("Anteckningar"),
|
||||
"discover_receipts": MessageLookupByLibrary.simpleMessage("Kvitton"),
|
||||
"doThisLater": MessageLookupByLibrary.simpleMessage("Gör detta senare"),
|
||||
"done": MessageLookupByLibrary.simpleMessage("Klar"),
|
||||
"dropSupportEmail": m22,
|
||||
@@ -376,6 +378,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"oops": MessageLookupByLibrary.simpleMessage("Hoppsan"),
|
||||
"orPickAnExistingOne":
|
||||
MessageLookupByLibrary.simpleMessage("Eller välj en befintlig"),
|
||||
"passkey": MessageLookupByLibrary.simpleMessage("Nyckel"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("Lösenord"),
|
||||
"passwordChangedSuccessfully":
|
||||
MessageLookupByLibrary.simpleMessage("Lösenordet har ändrats"),
|
||||
@@ -556,6 +559,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"verifyEmail":
|
||||
MessageLookupByLibrary.simpleMessage("Bekräfta e-postadress"),
|
||||
"verifyEmailID": m70,
|
||||
"verifyPasskey":
|
||||
MessageLookupByLibrary.simpleMessage("Verifiera nyckel"),
|
||||
"verifyPassword":
|
||||
MessageLookupByLibrary.simpleMessage("Bekräfta lösenord"),
|
||||
"verifyingRecoveryKey": MessageLookupByLibrary.simpleMessage(
|
||||
|
||||
16
mobile/lib/generated/intl/messages_zh.dart
generated
@@ -548,6 +548,22 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"disablingTwofactorAuthentication":
|
||||
MessageLookupByLibrary.simpleMessage("正在禁用双重认证..."),
|
||||
"discord": MessageLookupByLibrary.simpleMessage("Discord"),
|
||||
"discover": MessageLookupByLibrary.simpleMessage("发现"),
|
||||
"discover_babies": MessageLookupByLibrary.simpleMessage("婴儿"),
|
||||
"discover_celebrations": MessageLookupByLibrary.simpleMessage("节日"),
|
||||
"discover_food": MessageLookupByLibrary.simpleMessage("食物"),
|
||||
"discover_greenery": MessageLookupByLibrary.simpleMessage("绿植"),
|
||||
"discover_hills": MessageLookupByLibrary.simpleMessage("山"),
|
||||
"discover_identity": MessageLookupByLibrary.simpleMessage("身份"),
|
||||
"discover_memes": MessageLookupByLibrary.simpleMessage("表情包"),
|
||||
"discover_notes": MessageLookupByLibrary.simpleMessage("备注"),
|
||||
"discover_pets": MessageLookupByLibrary.simpleMessage("宠物"),
|
||||
"discover_receipts": MessageLookupByLibrary.simpleMessage("收据"),
|
||||
"discover_screenshots": MessageLookupByLibrary.simpleMessage("屏幕截图"),
|
||||
"discover_selfies": MessageLookupByLibrary.simpleMessage("自拍"),
|
||||
"discover_sunset": MessageLookupByLibrary.simpleMessage("日落"),
|
||||
"discover_visiting_cards": MessageLookupByLibrary.simpleMessage("访问卡"),
|
||||
"discover_wallpapers": MessageLookupByLibrary.simpleMessage("壁纸"),
|
||||
"dismiss": MessageLookupByLibrary.simpleMessage("忽略"),
|
||||
"distanceInKMUnit": MessageLookupByLibrary.simpleMessage("公里"),
|
||||
"doNotSignOut": MessageLookupByLibrary.simpleMessage("不要登出"),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"@@locale ": "en"
|
||||
"@@locale ": "en",
|
||||
"incorrectRecoveryKeyBody": ""
|
||||
}
|
||||
@@ -420,6 +420,25 @@
|
||||
"mlConsentPrivacy": "Bitte klicke hier für weitere Details zu dieser Funktion in unserer Datenschutzerklärung",
|
||||
"mlConsentConfirmation": "Ich verstehe und möchte das maschinelle Lernen aktivieren",
|
||||
"magicSearch": "Magische Suche",
|
||||
"discover": "Entdecken",
|
||||
"@discover": {
|
||||
"description": "The text to display for the discover section under which we show receipts, screenshots, sunsets, greenery, etc."
|
||||
},
|
||||
"discover_identity": "Identität",
|
||||
"discover_screenshots": "Bildschirmfotos",
|
||||
"discover_receipts": "Belege",
|
||||
"discover_notes": "Notizen",
|
||||
"discover_memes": "Memes",
|
||||
"discover_visiting_cards": "Visitenkarten",
|
||||
"discover_babies": "Babys",
|
||||
"discover_pets": "Haustiere",
|
||||
"discover_selfies": "Selfies",
|
||||
"discover_wallpapers": "Hintergründe",
|
||||
"discover_food": "Essen",
|
||||
"discover_celebrations": "Feiern",
|
||||
"discover_sunset": "Sonnenuntergang",
|
||||
"discover_hills": "Berge",
|
||||
"discover_greenery": "Grün",
|
||||
"mlIndexingDescription": "Bitte beachte, dass das maschinelle Lernen zu einem höheren Daten- und Akkuverbrauch führen wird, bis alle Elemente indiziert sind. Du kannst die Desktop-App für eine schnellere Indizierung verwenden, alle Ergebnisse werden automatisch synchronisiert.",
|
||||
"loadingModel": "Lade Modelle herunter...",
|
||||
"waitingForWifi": "Warte auf WLAN...",
|
||||
|
||||
@@ -420,6 +420,25 @@
|
||||
"mlConsentPrivacy": "Clicca qui per maggiori dettagli su questa funzione nella nostra informativa sulla privacy",
|
||||
"mlConsentConfirmation": "Comprendo e desidero abilitare l'apprendimento automatico",
|
||||
"magicSearch": "Ricerca magica",
|
||||
"discover": "Scopri",
|
||||
"@discover": {
|
||||
"description": "The text to display for the discover section under which we show receipts, screenshots, sunsets, greenery, etc."
|
||||
},
|
||||
"discover_identity": "Identità",
|
||||
"discover_screenshots": "Schermate",
|
||||
"discover_receipts": "Ricette",
|
||||
"discover_notes": "Note",
|
||||
"discover_memes": "Meme",
|
||||
"discover_visiting_cards": "Biglietti da Visita",
|
||||
"discover_babies": "Neonati",
|
||||
"discover_pets": "Animali domestici",
|
||||
"discover_selfies": "Selfie",
|
||||
"discover_wallpapers": "Sfondi",
|
||||
"discover_food": "Cibo",
|
||||
"discover_celebrations": "Festeggiamenti",
|
||||
"discover_sunset": "Tramonto",
|
||||
"discover_hills": "Colline",
|
||||
"discover_greenery": "Vegetazione",
|
||||
"mlIndexingDescription": "Si prega di notare che l'attivazione dell'apprendimento automatico si tradurrà in un maggior utilizzo della connessione e della batteria fino a quando tutti gli elementi non saranno indicizzati. Valuta di utilizzare l'applicazione desktop per un'indicizzazione più veloce, tutti i risultati verranno sincronizzati automaticamente.",
|
||||
"loadingModel": "Scaricamento modelli...",
|
||||
"waitingForWifi": "In attesa del WiFi...",
|
||||
@@ -1268,7 +1287,7 @@
|
||||
"whatsNew": "Novità",
|
||||
"reviewSuggestions": "Esamina i suggerimenti",
|
||||
"useAsCover": "Usa come copertina",
|
||||
"notPersonLabel": "Non sei {name}?",
|
||||
"notPersonLabel": "Non è {name}?",
|
||||
"@notPersonLabel": {
|
||||
"description": "Label to indicate that the person in the photo is not the person whose name is mentioned",
|
||||
"placeholders": {
|
||||
|
||||
@@ -420,6 +420,25 @@
|
||||
"mlConsentPrivacy": "Por favor, clique aqui para mais detalhes sobre este recurso em nossa política de privacidade",
|
||||
"mlConsentConfirmation": "Eu entendo, e desejo habilitar o aprendizado de máquina",
|
||||
"magicSearch": "Busca mágica",
|
||||
"discover": "Explorar",
|
||||
"@discover": {
|
||||
"description": "The text to display for the discover section under which we show receipts, screenshots, sunsets, greenery, etc."
|
||||
},
|
||||
"discover_identity": "Identidade",
|
||||
"discover_screenshots": "Capturas de Tela",
|
||||
"discover_receipts": "Recibos",
|
||||
"discover_notes": "Notas",
|
||||
"discover_memes": "Memes",
|
||||
"discover_visiting_cards": "Cartões de Visita",
|
||||
"discover_babies": "Bebés",
|
||||
"discover_pets": "Animais de Estimação",
|
||||
"discover_selfies": "Selfies",
|
||||
"discover_wallpapers": "Papéis de Parede",
|
||||
"discover_food": "Comida",
|
||||
"discover_celebrations": "Celebrações",
|
||||
"discover_sunset": "Pôr do Sol",
|
||||
"discover_hills": "Montanhas",
|
||||
"discover_greenery": "Vegetação",
|
||||
"mlIndexingDescription": "Observe que o aprendizado de máquina resultará em uma largura de banda maior e no uso da bateria até que todos os itens sejam indexados. Considere usar o aplicativo para computador para uma indexação mais rápida, todos os resultados serão sincronizados automaticamente.",
|
||||
"loadingModel": "Fazendo download de modelos...",
|
||||
"waitingForWifi": "Esperando por Wi-Fi...",
|
||||
|
||||
@@ -434,7 +434,9 @@
|
||||
"contacts": "Kontakter",
|
||||
"noInternetConnection": "Ingen internetanslutning",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "Kontrollera din internetanslutning och försök igen.",
|
||||
"passkey": "Nyckel",
|
||||
"loginSessionExpiredDetails": "Din session har upphört. Logga in igen.",
|
||||
"verifyPasskey": "Verifiera nyckel",
|
||||
"search": "Sök",
|
||||
"whatsNew": "Nyheter",
|
||||
"useAsCover": "Använd som omslag",
|
||||
|
||||
@@ -420,6 +420,25 @@
|
||||
"mlConsentPrivacy": "请点击此处查看我们隐私政策中有关此功能的更多详细信息",
|
||||
"mlConsentConfirmation": "我了解了,并希望启用机器学习",
|
||||
"magicSearch": "魔法搜索",
|
||||
"discover": "发现",
|
||||
"@discover": {
|
||||
"description": "The text to display for the discover section under which we show receipts, screenshots, sunsets, greenery, etc."
|
||||
},
|
||||
"discover_identity": "身份",
|
||||
"discover_screenshots": "屏幕截图",
|
||||
"discover_receipts": "收据",
|
||||
"discover_notes": "备注",
|
||||
"discover_memes": "表情包",
|
||||
"discover_visiting_cards": "访问卡",
|
||||
"discover_babies": "婴儿",
|
||||
"discover_pets": "宠物",
|
||||
"discover_selfies": "自拍",
|
||||
"discover_wallpapers": "壁纸",
|
||||
"discover_food": "食物",
|
||||
"discover_celebrations": "节日",
|
||||
"discover_sunset": "日落",
|
||||
"discover_hills": "山",
|
||||
"discover_greenery": "绿植",
|
||||
"mlIndexingDescription": "请注意,机器学习会导致带宽和电池使用量增加,直到所有项目都被索引。请考虑使用桌面应用程序来加快索引速度,所有结果都将自动同步。",
|
||||
"loadingModel": "正在下载模型...",
|
||||
"waitingForWifi": "正在等待 WiFi...",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import "dart:convert";
|
||||
|
||||
import "package:flutter/cupertino.dart";
|
||||
import 'package:photos/models/metadata/common_keys.dart';
|
||||
|
||||
const editTimeKey = 'editedTime';
|
||||
@@ -88,8 +89,8 @@ class PubMagicMetadata {
|
||||
editedName: map[editNameKey],
|
||||
caption: map[captionKey],
|
||||
uploaderName: map[uploaderNameKey],
|
||||
w: map[widthKey],
|
||||
h: map[heightKey],
|
||||
w: safeParseInt(map[widthKey], widthKey),
|
||||
h: safeParseInt(map[heightKey], heightKey),
|
||||
lat: map[latKey],
|
||||
long: map[longKey],
|
||||
mvi: map[motionVideoIndexKey],
|
||||
@@ -97,4 +98,12 @@ class PubMagicMetadata {
|
||||
mediaType: map[mediaTypeKey],
|
||||
);
|
||||
}
|
||||
|
||||
static int? safeParseInt(dynamic value, String key) {
|
||||
if (value == null) return null;
|
||||
if (value is int) return value;
|
||||
debugPrint("PubMagicMetadata key: $key Unexpected value: $value");
|
||||
if (value is String) return int.tryParse(value);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +131,10 @@ class MLService {
|
||||
);
|
||||
await clusterAllImages();
|
||||
}
|
||||
if (_mlControllerStatus == true) {
|
||||
// refresh discover section
|
||||
MagicCacheService.instance.updateCache().ignore();
|
||||
}
|
||||
await indexAllImages();
|
||||
if ((await MLDataDB.instance.getUnclusteredFaceCount()) > 0) {
|
||||
await clusterAllImages();
|
||||
@@ -208,6 +212,9 @@ class MLService {
|
||||
);
|
||||
fileAnalyzedCount += sumFutures;
|
||||
}
|
||||
if (fileAnalyzedCount > 0) {
|
||||
MagicCacheService.instance.queueUpdate('fileIndexed');
|
||||
}
|
||||
_logger.info(
|
||||
"`indexAllImages()` finished. Analyzed $fileAnalyzedCount images, in ${stopwatch.elapsed.inSeconds} seconds (avg of ${stopwatch.elapsed.inSeconds / fileAnalyzedCount} seconds per image)",
|
||||
);
|
||||
|
||||
@@ -174,7 +174,7 @@ class MagicCacheService {
|
||||
_updateCacheIfTheTimeHasCome();
|
||||
});
|
||||
Bus.instance.on<FileUploadedEvent>().listen((event) {
|
||||
_pendingUpdateReason.add("File uploaded");
|
||||
queueUpdate("File uploaded");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -191,6 +191,10 @@ class MagicCacheService {
|
||||
|
||||
bool get enableDiscover => localSettings.isMLIndexingEnabled;
|
||||
|
||||
void queueUpdate(String reason) {
|
||||
_pendingUpdateReason.add(reason);
|
||||
}
|
||||
|
||||
Future<void> _updateCacheIfTheTimeHasCome() async {
|
||||
if (!enableDiscover) {
|
||||
return;
|
||||
@@ -198,12 +202,12 @@ class MagicCacheService {
|
||||
final updatedJSONFile = await RemoteAssetsService.instance
|
||||
.getAssetIfUpdated(_kMagicPromptsDataUrl);
|
||||
if (updatedJSONFile != null) {
|
||||
_pendingUpdateReason.add("Prompts data updated");
|
||||
queueUpdate("Prompts data updated");
|
||||
} else if (lastMagicCacheUpdateTime <
|
||||
DateTime.now()
|
||||
.subtract(const Duration(days: 1))
|
||||
.millisecondsSinceEpoch) {
|
||||
_pendingUpdateReason.add("Cache is old");
|
||||
queueUpdate("Cache is old");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,7 +230,6 @@ class MagicCacheService {
|
||||
return;
|
||||
}
|
||||
_logger.info("updating magic cache ${_pendingUpdateReason.toList()}");
|
||||
_pendingUpdateReason.clear();
|
||||
_isUpdateInProgress = true;
|
||||
final EnteWatch? w = kDebugMode ? EnteWatch("magicCacheWatch") : null;
|
||||
w?.start();
|
||||
@@ -245,6 +248,7 @@ class MagicCacheService {
|
||||
w?.log("cacheWritten");
|
||||
await _resetLastMagicCacheUpdateTime();
|
||||
w?.logAndReset('done');
|
||||
_pendingUpdateReason.clear();
|
||||
Bus.instance.fire(MagicCacheUpdatedEvent());
|
||||
} catch (e, s) {
|
||||
_logger.info("Error updating magic cache", e, s);
|
||||
@@ -376,6 +380,7 @@ class MagicCacheService {
|
||||
List<Prompt> magicPromptsData,
|
||||
) async {
|
||||
final results = <MagicCache>[];
|
||||
final List<int> matchCount = [];
|
||||
for (Prompt prompt in magicPromptsData) {
|
||||
final fileUploadedIDs =
|
||||
await SemanticSearchService.instance.getMatchingFileIDs(
|
||||
@@ -387,7 +392,9 @@ class MagicCacheService {
|
||||
MagicCache(prompt.title, fileUploadedIDs),
|
||||
);
|
||||
}
|
||||
matchCount.add(fileUploadedIDs.length);
|
||||
}
|
||||
_logger.info('magic result count $matchCount');
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ class SearchService {
|
||||
Future<List<GenericSearchResult>> getMagicSectionResults(
|
||||
BuildContext context,
|
||||
) async {
|
||||
if (localSettings.isMLIndexingEnabled && flagService.internalUser) {
|
||||
if (localSettings.isMLIndexingEnabled) {
|
||||
return MagicCacheService.instance.getMagicGenericSearchResult(context);
|
||||
} else {
|
||||
return <GenericSearchResult>[];
|
||||
|
||||
@@ -137,9 +137,11 @@ class _TextInputWidgetState extends State<TextInputWidget> {
|
||||
if (executionState == ExecutionState.successful) {
|
||||
Future.delayed(Duration(seconds: widget.popNavAfterSubmission ? 1 : 2),
|
||||
() {
|
||||
setState(() {
|
||||
executionState = ExecutionState.idle;
|
||||
});
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
executionState = ExecutionState.idle;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
|
||||
@@ -7,9 +7,7 @@ import 'package:photo_manager/photo_manager.dart';
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import 'package:photos/services/sync_service.dart';
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import "package:photos/utils/debouncer.dart";
|
||||
import "package:photos/utils/dialog_util.dart";
|
||||
import "package:photos/utils/email_util.dart";
|
||||
import "package:photos/utils/photo_manager_util.dart";
|
||||
import "package:styled_text/styled_text.dart";
|
||||
|
||||
@@ -21,88 +19,67 @@ class GrantPermissionsWidget extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _GrantPermissionsWidgetState extends State<GrantPermissionsWidget> {
|
||||
final _debouncer = Debouncer(const Duration(milliseconds: 500));
|
||||
final Logger _logger = Logger("_GrantPermissionsWidgetState");
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_debouncer.cancelDebounceTimer();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isLightMode = Theme.of(context).brightness == Brightness.light;
|
||||
return Scaffold(
|
||||
body: SingleChildScrollView(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onScaleEnd: (details) {
|
||||
_debouncer.run(() async {
|
||||
unawaited(
|
||||
triggerSendLogs(
|
||||
"support@ente.io",
|
||||
"Stuck on grant permission screen on ${Platform.operatingSystem}",
|
||||
null,
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 20, bottom: 120),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
Center(
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
isLightMode
|
||||
? Image.asset(
|
||||
'assets/loading_photos_background.png',
|
||||
color: Colors.white.withOpacity(0.4),
|
||||
colorBlendMode: BlendMode.modulate,
|
||||
)
|
||||
: Image.asset(
|
||||
'assets/loading_photos_background_dark.png',
|
||||
),
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 42),
|
||||
Image.asset(
|
||||
"assets/gallery_locked.png",
|
||||
height: 160,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 20, bottom: 120),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
Center(
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
isLightMode
|
||||
? Image.asset(
|
||||
'assets/loading_photos_background.png',
|
||||
color: Colors.white.withOpacity(0.4),
|
||||
colorBlendMode: BlendMode.modulate,
|
||||
)
|
||||
: Image.asset(
|
||||
'assets/loading_photos_background_dark.png',
|
||||
),
|
||||
Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 42),
|
||||
Image.asset(
|
||||
"assets/gallery_locked.png",
|
||||
height: 160,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 36),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(40, 0, 40, 0),
|
||||
child: StyledText(
|
||||
text: S.of(context).entePhotosPerm,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall!
|
||||
.copyWith(fontWeight: FontWeight.w700),
|
||||
tags: {
|
||||
'i': StyledTextTag(
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall!
|
||||
.copyWith(fontWeight: FontWeight.w400),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 36),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(40, 0, 40, 0),
|
||||
child: StyledText(
|
||||
text: S.of(context).entePhotosPerm,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall!
|
||||
.copyWith(fontWeight: FontWeight.w700),
|
||||
tags: {
|
||||
'i': StyledTextTag(
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall!
|
||||
.copyWith(fontWeight: FontWeight.w400),
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -2,6 +2,7 @@ import "package:flutter/foundation.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:flutter/scheduler.dart";
|
||||
import "package:flutter_animate/flutter_animate.dart";
|
||||
import "package:photos/services/update_service.dart";
|
||||
import "package:photos/theme/colors.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import 'package:photos/utils/data_util.dart';
|
||||
@@ -162,17 +163,30 @@ class _Price extends StatelessWidget {
|
||||
final priceDouble = double.parse(priceWithoutCurrency);
|
||||
final pricePerMonth = priceDouble / 12;
|
||||
final pricePerMonthString = pricePerMonth.toStringAsFixed(2);
|
||||
final bool isPlayStore = UpdateService.instance.isPlayStoreFlavor();
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
currencySymbol + pricePerMonthString + ' / ' + 'month',
|
||||
style: textTheme.largeBold.copyWith(color: textBaseLight),
|
||||
),
|
||||
Text(
|
||||
price + " / " + "yr",
|
||||
style: textTheme.small.copyWith(color: textFaintLight),
|
||||
),
|
||||
if (isPlayStore)
|
||||
Text(
|
||||
price + " / " + "yr",
|
||||
style: textTheme.largeBold.copyWith(color: textBaseLight),
|
||||
),
|
||||
if (isPlayStore)
|
||||
Text(
|
||||
currencySymbol + pricePerMonthString + ' / ' + 'month',
|
||||
style: textTheme.small.copyWith(color: textFaintLight),
|
||||
),
|
||||
if (!isPlayStore)
|
||||
Text(
|
||||
currencySymbol + pricePerMonthString + ' / ' + 'month',
|
||||
style: textTheme.largeBold.copyWith(color: textBaseLight),
|
||||
),
|
||||
if (!isPlayStore)
|
||||
Text(
|
||||
price + " / " + "yr",
|
||||
style: textTheme.small.copyWith(color: textFaintLight),
|
||||
),
|
||||
],
|
||||
)
|
||||
.animate(delay: const Duration(milliseconds: 100))
|
||||
|
||||
@@ -133,12 +133,13 @@ class CustomPinKeypad extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _Button extends StatelessWidget {
|
||||
class _Button extends StatefulWidget {
|
||||
final String number;
|
||||
final String text;
|
||||
final VoidCallback? onTap;
|
||||
final bool muteButton;
|
||||
final Widget? icon;
|
||||
|
||||
const _Button({
|
||||
required this.number,
|
||||
required this.text,
|
||||
@@ -147,30 +148,59 @@ class _Button extends StatelessWidget {
|
||||
this.icon,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_Button> createState() => _ButtonState();
|
||||
}
|
||||
|
||||
class _ButtonState extends State<_Button> {
|
||||
bool isPressed = false;
|
||||
|
||||
void _onTapDown(TapDownDetails details) {
|
||||
setState(() {
|
||||
isPressed = true;
|
||||
});
|
||||
}
|
||||
|
||||
void _onTapUp(TapUpDetails details) async {
|
||||
setState(() {
|
||||
isPressed = false;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
final textTheme = getEnteTextTheme(context);
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
onTapDown: _onTapDown,
|
||||
onTapUp: _onTapUp,
|
||||
onTap: widget.onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
curve: Curves.easeOut,
|
||||
margin: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.rectangle,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
color: muteButton
|
||||
? colorScheme.fillFaintPressed
|
||||
: icon == null
|
||||
? colorScheme.backgroundElevated2
|
||||
: null,
|
||||
color: isPressed
|
||||
? colorScheme.backgroundElevated
|
||||
: widget.muteButton
|
||||
? colorScheme.fillFaintPressed
|
||||
: widget.icon == null
|
||||
? colorScheme.backgroundElevated2
|
||||
: null,
|
||||
),
|
||||
child: Center(
|
||||
child: muteButton
|
||||
child: widget.muteButton
|
||||
? const SizedBox.shrink()
|
||||
: icon != null
|
||||
: widget.icon != null
|
||||
? Container(
|
||||
child: icon,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 10,
|
||||
),
|
||||
child: widget.icon,
|
||||
)
|
||||
: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
@@ -178,11 +208,11 @@ class _Button extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
number,
|
||||
widget.number,
|
||||
style: textTheme.h3,
|
||||
),
|
||||
Text(
|
||||
text,
|
||||
widget.text,
|
||||
style: textTheme.tinyBold,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import "dart:async";
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:logging/logging.dart";
|
||||
import 'package:photos/core/event_bus.dart';
|
||||
import 'package:photos/events/files_updated_event.dart';
|
||||
import 'package:photos/events/local_photos_updated_event.dart';
|
||||
@@ -44,6 +45,8 @@ class _MagicResultScreenState extends State<MagicResultScreen> {
|
||||
late final List<EnteFile> files;
|
||||
late final StreamSubscription<LocalPhotosUpdatedEvent> _filesUpdatedEvent;
|
||||
late final StreamSubscription<MagicSortChangeEvent> _magicSortChangeEvent;
|
||||
late final Logger _logger = Logger("_MagicResultScreenState");
|
||||
late final Map<int, int> fileIDToRelevantPos;
|
||||
bool _enableGrouping = false;
|
||||
|
||||
@override
|
||||
@@ -51,6 +54,7 @@ class _MagicResultScreenState extends State<MagicResultScreen> {
|
||||
super.initState();
|
||||
files = widget.files;
|
||||
_enableGrouping = widget.enableGrouping;
|
||||
fileIDToRelevantPos = getFileIDToRelevantPos();
|
||||
_filesUpdatedEvent =
|
||||
Bus.instance.on<LocalPhotosUpdatedEvent>().listen((event) {
|
||||
if (event.type == EventType.deletedFromDevice ||
|
||||
@@ -68,11 +72,17 @@ class _MagicResultScreenState extends State<MagicResultScreen> {
|
||||
Bus.instance.on<MagicSortChangeEvent>().listen((event) {
|
||||
if (event.sortType == MagicSortType.mostRelevant) {
|
||||
if (_enableGrouping) {
|
||||
files.sort(
|
||||
(a, b) =>
|
||||
widget.fileIdToPosMap[a.uploadedFileID]! -
|
||||
widget.fileIdToPosMap[b.uploadedFileID]!,
|
||||
);
|
||||
if (fileIDToRelevantPos.isNotEmpty) {
|
||||
files.sort(
|
||||
(a, b) =>
|
||||
fileIDToRelevantPos[a.uploadedFileID]! -
|
||||
fileIDToRelevantPos[b.uploadedFileID]!,
|
||||
);
|
||||
} else {
|
||||
_logger.warning(
|
||||
"fileIdToPosMap is empty, cannot sort by most relevant.",
|
||||
);
|
||||
}
|
||||
}
|
||||
setState(() {
|
||||
_enableGrouping = false;
|
||||
@@ -88,6 +98,26 @@ class _MagicResultScreenState extends State<MagicResultScreen> {
|
||||
});
|
||||
}
|
||||
|
||||
Map<int, int> getFileIDToRelevantPos() {
|
||||
if (widget.fileIdToPosMap.isNotEmpty) {
|
||||
return widget.fileIdToPosMap;
|
||||
} else if (widget.enableGrouping == false) {
|
||||
_logger.warning(
|
||||
"fileIdToPosMap is empty, assuming existing list of files is sorted by most relevant.",
|
||||
);
|
||||
final map = <int, int>{};
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
map[files[i].uploadedFileID!] = i;
|
||||
}
|
||||
return map;
|
||||
} else {
|
||||
_logger.warning(
|
||||
"fileIdToPosMap is empty, cannot sort by most relevant.",
|
||||
);
|
||||
return <int, int>{};
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_filesUpdatedEvent.cancel();
|
||||
|
||||
@@ -61,13 +61,15 @@ class SearchWidgetState extends State<SearchWidget> {
|
||||
//This buffer is for doing this operation only after SearchWidget's
|
||||
//animation is complete.
|
||||
Future.delayed(const Duration(milliseconds: 300), () {
|
||||
final RenderBox box =
|
||||
widgetKey.currentContext!.findRenderObject() as RenderBox;
|
||||
final heightOfWidget = box.size.height;
|
||||
final offsetPosition = box.localToGlobal(Offset.zero);
|
||||
final y = offsetPosition.dy;
|
||||
final heightOfScreen = MediaQuery.sizeOf(context).height;
|
||||
_distanceOfWidgetFromBottom = heightOfScreen - (y + heightOfWidget);
|
||||
if (mounted) {
|
||||
final RenderBox box =
|
||||
widgetKey.currentContext!.findRenderObject() as RenderBox;
|
||||
final heightOfWidget = box.size.height;
|
||||
final offsetPosition = box.localToGlobal(Offset.zero);
|
||||
final y = offsetPosition.dy;
|
||||
final heightOfScreen = MediaQuery.sizeOf(context).height;
|
||||
_distanceOfWidgetFromBottom = heightOfScreen - (y + heightOfWidget);
|
||||
}
|
||||
});
|
||||
|
||||
textController.addListener(textControllerListener);
|
||||
|
||||
@@ -12,7 +12,7 @@ description: ente photos application
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
|
||||
version: 0.9.41+941
|
||||
version: 0.9.44+944
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
|
||||
@@ -893,14 +893,14 @@ func setupAndStartCrons(userAuthRepo *repo.UserAuthRepository, publicCollectionR
|
||||
}
|
||||
})
|
||||
|
||||
schedule(c, "@every 2m", func() {
|
||||
schedule(c, "@every 10m", func() {
|
||||
fileController.CleanupDeletedFiles()
|
||||
})
|
||||
schedule(c, "@every 101s", func() {
|
||||
embeddingCtrl.CleanupDeletedEmbeddings()
|
||||
})
|
||||
|
||||
schedule(c, "@every 10m", func() {
|
||||
schedule(c, "@every 17m", func() {
|
||||
trashController.DropFileMetadataCron()
|
||||
})
|
||||
|
||||
@@ -926,7 +926,7 @@ func setupAndStartCrons(userAuthRepo *repo.UserAuthRepository, publicCollectionR
|
||||
trashController.ProcessEmptyTrashRequests()
|
||||
})
|
||||
|
||||
schedule(c, "@every 30m", func() {
|
||||
schedule(c, "@every 45m", func() {
|
||||
// delete unclaimed codes older than 60 minutes
|
||||
_ = castDb.DeleteUnclaimedCodes(context.Background(), timeUtil.MicrosecondsBeforeMinutes(60))
|
||||
dataCleanupCtrl.DeleteDataCron()
|
||||
|
||||
@@ -30,7 +30,7 @@ services:
|
||||
command: "TCP-LISTEN:3200,fork,reuseaddr TCP:minio:3200"
|
||||
|
||||
postgres:
|
||||
image: postgres:12
|
||||
image: postgres:15
|
||||
ports:
|
||||
- 5432:5432
|
||||
environment:
|
||||
|
||||
@@ -178,13 +178,10 @@ func (c *Controller) RevokeInvite(ctx context.Context, adminID int64, id uuid.UU
|
||||
}
|
||||
|
||||
func (c *Controller) CloseFamily(ctx context.Context, adminID int64) error {
|
||||
familyMembers, err := c.FamilyRepo.GetMembersWithStatus(adminID, repo.ActiveFamilyMemberStatus)
|
||||
logger := logrus.WithField("adminID", adminID).WithField("operation", "CloseFamily")
|
||||
err := c.removeMembers(ctx, adminID, logger)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
if len(familyMembers) != 1 {
|
||||
msg := fmt.Sprintf("can not close family with %d members", len(familyMembers))
|
||||
return stacktrace.Propagate(ente.NewBadRequestWithMessage(msg), "")
|
||||
return stacktrace.Propagate(err, "failed to remove members")
|
||||
}
|
||||
err = c.FamilyRepo.CloseFamily(ctx, adminID)
|
||||
if err != nil {
|
||||
|
||||
@@ -112,29 +112,37 @@ func (c *Controller) HandleAccountDeletion(ctx context.Context, userID int64, lo
|
||||
}
|
||||
} else {
|
||||
logger.Info("user is a family admin, revoking invites & removing members")
|
||||
members, err := c.FetchMembersForAdminID(ctx, userID)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
|
||||
for _, member := range members.Members {
|
||||
if member.IsAdmin {
|
||||
continue
|
||||
} else if member.Status == ente.ACCEPTED {
|
||||
logger.Info(fmt.Sprintf("removing memeber_id %d", member.MemberUserID))
|
||||
err = c.RemoveMember(ctx, userID, member.ID)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
} else if member.Status == ente.INVITED {
|
||||
logger.Info(fmt.Sprintf("revoking invite member_id %d", member.MemberUserID))
|
||||
err = c.RevokeInvite(ctx, userID, member.ID)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
} else {
|
||||
logger.WithField("member", member).Error("unxpected state during account deletion")
|
||||
}
|
||||
removeErr := c.removeMembers(ctx, userID, logger)
|
||||
if removeErr != nil {
|
||||
return removeErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) removeMembers(ctx context.Context, adminID int64, logger *logrus.Entry) error {
|
||||
members, err := c.FetchMembersForAdminID(ctx, adminID)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
|
||||
for _, member := range members.Members {
|
||||
if member.IsAdmin {
|
||||
continue
|
||||
} else if member.Status == ente.ACCEPTED {
|
||||
logger.Info(fmt.Sprintf("removing memeber_id %d", member.MemberUserID))
|
||||
err = c.RemoveMember(ctx, adminID, member.ID)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
} else if member.Status == ente.INVITED {
|
||||
logger.Info(fmt.Sprintf("revoking invite member_id %d", member.MemberUserID))
|
||||
err = c.RevokeInvite(ctx, adminID, member.ID)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "")
|
||||
}
|
||||
} else {
|
||||
logger.WithField("member", member).Error("unxpected state during account deletion")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -689,7 +689,7 @@ func (c *FileController) CleanupDeletedFiles() {
|
||||
defer func() {
|
||||
c.LockController.ReleaseLock(DeletedObjectQueueLock)
|
||||
}()
|
||||
items, err := c.QueueRepo.GetItemsReadyForDeletion(repo.DeleteObjectQueue, 2000)
|
||||
items, err := c.QueueRepo.GetItemsReadyForDeletion(repo.DeleteObjectQueue, 1000)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to fetch items from queue")
|
||||
return
|
||||
|
||||
@@ -43,7 +43,7 @@ func (c *Controller) delete(i int) {
|
||||
if err != nil {
|
||||
// Sleep in proportion to the (arbitrary) index to space out the
|
||||
// workers further.
|
||||
time.Sleep(time.Duration(i+1) * time.Minute)
|
||||
time.Sleep(time.Duration(i+5) * time.Minute)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ const Page: React.FC = () => {
|
||||
|
||||
const showPasskeyFetchFailedErrorDialog = useCallback(() => {
|
||||
setDialogBoxAttributesV2({
|
||||
title: t("ERROR"),
|
||||
title: t("error"),
|
||||
content: t("passkey_fetch_failed"),
|
||||
close: {},
|
||||
});
|
||||
@@ -392,7 +392,7 @@ const RenamePasskeyDialog: React.FC<RenamePasskeyDialogProps> = ({
|
||||
initialValue={passkey.friendlyName}
|
||||
callback={handleSubmit}
|
||||
placeholder={t("enter_passkey_name")}
|
||||
buttonText={t("RENAME")}
|
||||
buttonText={t("rename")}
|
||||
fieldType="text"
|
||||
secondaryButtonAction={onClose}
|
||||
submitButtonProps={{ sx: { mt: 1, mb: 0 } }}
|
||||
|
||||
@@ -130,7 +130,7 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
|
||||
|
||||
const somethingWentWrong = () =>
|
||||
setDialogBoxAttributesV2({
|
||||
title: t("ERROR"),
|
||||
title: t("error"),
|
||||
close: { variant: "critical" },
|
||||
content: t("UNKNOWN_ERROR"),
|
||||
});
|
||||
|
||||
@@ -407,7 +407,7 @@ const Footer: React.FC = () => {
|
||||
href="https://github.com/ente-io/ente/tree/main/auth#-download"
|
||||
download
|
||||
>
|
||||
<Button color="accent">{t("DOWNLOAD")}</Button>
|
||||
<Button color="accent">{t("download")}</Button>
|
||||
</a>
|
||||
</Footer_>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useRouter } from "next/router";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { readCastData, storeCastData } from "services/cast-data";
|
||||
import { getCastData, register } from "services/pair";
|
||||
import { advertiseOnChromecast } from "../services/chromecast";
|
||||
import { advertiseOnChromecast } from "../services/chromecast-receiver";
|
||||
|
||||
export default function Index() {
|
||||
const [publicKeyB64, setPublicKeyB64] = useState<string | undefined>();
|
||||
|
||||
@@ -5,7 +5,7 @@ import { FilledCircleCheck } from "components/FilledCircleCheck";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { readCastData } from "services/cast-data";
|
||||
import { isChromecast } from "services/chromecast";
|
||||
import { isChromecast } from "services/chromecast-receiver";
|
||||
import { imageURLGenerator } from "services/render";
|
||||
|
||||
export default function Slideshow() {
|
||||
|
||||
@@ -28,7 +28,7 @@ import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import type { AxiosResponse } from "axios";
|
||||
import type { CastData } from "services/cast-data";
|
||||
import { detectMediaMIMEType } from "services/detect-type";
|
||||
import { isChromecast } from "./chromecast";
|
||||
import { isChromecast } from "./chromecast-receiver";
|
||||
|
||||
/**
|
||||
* An async generator function that loops through all the files in the
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
"exifreader": "^4",
|
||||
"fast-srp-hap": "^2.0.4",
|
||||
"ffmpeg-wasm": "file:./thirdparty/ffmpeg-wasm",
|
||||
"hdbscan": "0.0.1-alpha.5",
|
||||
"leaflet": "^1.9.4",
|
||||
"leaflet-defaulticon-compatibility": "^0.1.1",
|
||||
"localforage": "^1.9.0",
|
||||
@@ -32,7 +31,7 @@
|
||||
"react-select": "^5.8.0",
|
||||
"react-top-loading-bar": "^2.0.1",
|
||||
"react-virtualized-auto-sizer": "^1.0",
|
||||
"react-window": "^1.8.6",
|
||||
"react-window": "^1.8.10",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"similarity-transformation": "^0.0.1",
|
||||
"transformation-matrix": "^2.16",
|
||||
@@ -47,8 +46,7 @@
|
||||
"@types/leaflet": "^1.9",
|
||||
"@types/photoswipe": "^4.1.1",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0",
|
||||
"@types/react-window": "^1.8.2",
|
||||
"@types/react-window-infinite-loader": "^1.0.3",
|
||||
"@types/react-window": "^1.8.8",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"@types/zxcvbn": "^4.4.1"
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function AuthenticateUserModal({
|
||||
|
||||
const somethingWentWrong = () =>
|
||||
setDialogMessage({
|
||||
title: t("ERROR"),
|
||||
title: t("error"),
|
||||
close: { variant: "critical" },
|
||||
content: t("UNKNOWN_ERROR"),
|
||||
});
|
||||
|
||||
218
web/apps/photos/src/components/Collections/AlbumCastDialog.tsx
Normal file
@@ -0,0 +1,218 @@
|
||||
import { boxSeal } from "@/base/crypto/libsodium";
|
||||
import log from "@/base/log";
|
||||
import type { Collection } from "@/media/collection";
|
||||
import { loadCast } from "@/new/photos/utils/chromecast-sender";
|
||||
import DialogBoxV2 from "@ente/shared/components/DialogBoxV2";
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import SingleInputForm, {
|
||||
type SingleInputFormProps,
|
||||
} from "@ente/shared/components/SingleInputForm";
|
||||
import castGateway from "@ente/shared/network/cast";
|
||||
import { Button, Link, Stack, Typography } from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
interface AlbumCastDialogProps {
|
||||
/** If `true`, the dialog is shown. */
|
||||
open: boolean;
|
||||
/** Callback fired when the dialog wants to be closed. */
|
||||
onClose: () => void;
|
||||
/** The collection that we want to cast. */
|
||||
collection: Collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* A dialog that shows various options that the user has for casting an album.
|
||||
*/
|
||||
export const AlbumCastDialog: React.FC<AlbumCastDialogProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
collection,
|
||||
}) => {
|
||||
const [view, setView] = useState<
|
||||
"choose" | "auto" | "pin" | "auto-cast-error"
|
||||
>("choose");
|
||||
|
||||
const [browserCanCast, setBrowserCanCast] = useState(false);
|
||||
|
||||
// Make API call to clear all previous sessions on component mount.
|
||||
useEffect(() => {
|
||||
castGateway.revokeAllTokens();
|
||||
|
||||
setBrowserCanCast(typeof window["chrome"] !== "undefined");
|
||||
}, []);
|
||||
|
||||
const onSubmit: SingleInputFormProps["callback"] = async (
|
||||
value,
|
||||
setFieldError,
|
||||
) => {
|
||||
try {
|
||||
await doCast(value.trim());
|
||||
onClose();
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.message == "tv-not-found") {
|
||||
setFieldError(t("tv_not_found"));
|
||||
} else {
|
||||
setFieldError(t("UNKNOWN_ERROR"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const doCast = async (pin: string) => {
|
||||
// Does the TV exist? have they advertised their existence?
|
||||
const tvPublicKeyB64 = await castGateway.getPublicKey(pin);
|
||||
if (!tvPublicKeyB64) {
|
||||
throw new Error("tv-not-found");
|
||||
}
|
||||
|
||||
// Generate random id.
|
||||
const castToken = uuidv4();
|
||||
|
||||
// Ok, they exist. let's give them the good stuff.
|
||||
const payload = JSON.stringify({
|
||||
castToken: castToken,
|
||||
collectionID: collection.id,
|
||||
collectionKey: collection.key,
|
||||
});
|
||||
const encryptedPayload = await boxSeal(btoa(payload), tvPublicKeyB64);
|
||||
|
||||
// Hey TV, we acknowlege you!
|
||||
await castGateway.publishCastPayload(
|
||||
pin,
|
||||
encryptedPayload,
|
||||
collection.id,
|
||||
castToken,
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (view === "auto") {
|
||||
loadCast().then(async (cast) => {
|
||||
const instance = cast.framework.CastContext.getInstance();
|
||||
try {
|
||||
await instance.requestSession();
|
||||
} catch (e) {
|
||||
setView("auto-cast-error");
|
||||
log.error("Error requesting session", e);
|
||||
return;
|
||||
}
|
||||
const session = instance.getCurrentSession();
|
||||
session.addMessageListener(
|
||||
"urn:x-cast:pair-request",
|
||||
(_, message) => {
|
||||
const data = message;
|
||||
const obj = JSON.parse(data);
|
||||
const code = obj.code;
|
||||
|
||||
if (code) {
|
||||
doCast(code)
|
||||
.then(() => {
|
||||
setView("choose");
|
||||
onClose();
|
||||
})
|
||||
.catch((e) => {
|
||||
log.error("Error casting to TV", e);
|
||||
setView("auto-cast-error");
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const collectionID = collection.id;
|
||||
session
|
||||
.sendMessage("urn:x-cast:pair-request", { collectionID })
|
||||
.then(() => {
|
||||
log.debug(() => "urn:x-cast:pair-request sent");
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [view]);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) castGateway.revokeAllTokens();
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<DialogBoxV2
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
attributes={{ title: t("cast_album_to_tv") }}
|
||||
sx={{ zIndex: 1600 }}
|
||||
>
|
||||
{view == "choose" && (
|
||||
<Stack sx={{ py: 1, gap: 4 }}>
|
||||
{browserCanCast && (
|
||||
<Stack sx={{ gap: 2 }}>
|
||||
<Typography color={"text.muted"}>
|
||||
{t("cast_auto_pair_description")}
|
||||
</Typography>
|
||||
|
||||
<Button onClick={() => setView("auto")}>
|
||||
{t("cast_auto_pair")}
|
||||
</Button>
|
||||
</Stack>
|
||||
)}
|
||||
<Stack sx={{ gap: 2 }}>
|
||||
<Typography color="text.muted">
|
||||
{t("pair_with_pin_description")}
|
||||
</Typography>
|
||||
<Button onClick={() => setView("pin")}>
|
||||
{t("pair_with_pin")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)}
|
||||
{view == "auto" && (
|
||||
<Stack sx={{ pt: 1, gap: 3, textAlign: "center" }}>
|
||||
<div>
|
||||
<EnteSpinner />
|
||||
</div>
|
||||
<Typography>{t("choose_device_from_browser")}</Typography>
|
||||
<Button color="secondary" onClick={() => setView("choose")}>
|
||||
{t("GO_BACK")}
|
||||
</Button>
|
||||
</Stack>
|
||||
)}
|
||||
{view == "auto-cast-error" && (
|
||||
<Stack sx={{ pt: 1, gap: 3, textAlign: "center" }}>
|
||||
<Typography>{t("cast_auto_pair_failed")}</Typography>
|
||||
<Button color="secondary" onClick={() => setView("choose")}>
|
||||
{t("GO_BACK")}
|
||||
</Button>
|
||||
</Stack>
|
||||
)}
|
||||
{view == "pin" && (
|
||||
<>
|
||||
<Typography>
|
||||
<Trans
|
||||
i18nKey="visit_cast_url"
|
||||
components={{
|
||||
a: (
|
||||
<Link
|
||||
target="_blank"
|
||||
href="https://cast.ente.io"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
values={{ url: "cast.ente.io" }}
|
||||
/>
|
||||
</Typography>
|
||||
<Typography>{t("enter_cast_pin_code")}</Typography>
|
||||
<SingleInputForm
|
||||
callback={onSubmit}
|
||||
fieldType="text"
|
||||
realLabel={"Code"}
|
||||
realPlaceholder={"123456"}
|
||||
buttonText={t("pair_device_to_tv")}
|
||||
submitButtonProps={{ sx: { mt: 1, mb: 2 } }}
|
||||
/>
|
||||
<Button variant="text" onClick={() => setView("choose")}>
|
||||
{t("GO_BACK")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</DialogBoxV2>
|
||||
);
|
||||
};
|
||||
@@ -1,8 +1,9 @@
|
||||
import { AllCollectionTile } from "@/new/photos/components/ItemCards";
|
||||
import type { CollectionSummary } from "@/new/photos/types/collection";
|
||||
import { Typography } from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
import { CollectionSummary } from "types/collection";
|
||||
import CollectionCard from "../CollectionCard";
|
||||
import { AllCollectionTile, AllCollectionTileText } from "../styledComponents";
|
||||
import { AllCollectionTileText } from "../styledComponents";
|
||||
|
||||
interface Iprops {
|
||||
collectionSummary: CollectionSummary;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { CollectionSummary } from "@/new/photos/types/collection";
|
||||
import { FlexWrapper } from "@ente/shared/components/Container";
|
||||
import useWindowSize from "@ente/shared/hooks/useWindowSize";
|
||||
import { DialogContent } from "@mui/material";
|
||||
@@ -8,7 +9,6 @@ import {
|
||||
ListChildComponentProps,
|
||||
areEqual,
|
||||
} from "react-window";
|
||||
import { CollectionSummary } from "types/collection";
|
||||
import AllCollectionCard from "./collectionCard";
|
||||
import { AllCollectionMobileBreakpoint } from "./dialog";
|
||||
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import {
|
||||
FlexWrapper,
|
||||
FluidContainer,
|
||||
IconButtonWithBG,
|
||||
} from "@ente/shared/components/Container";
|
||||
import Close from "@mui/icons-material/Close";
|
||||
import { Box, DialogTitle, Stack, Typography } from "@mui/material";
|
||||
import CollectionListSortBy from "components/Collections/CollectionListSortBy";
|
||||
import { t } from "i18next";
|
||||
|
||||
export default function AllCollectionsHeader({
|
||||
onClose,
|
||||
collectionCount,
|
||||
collectionSortBy,
|
||||
setCollectionSortBy,
|
||||
isInHiddenSection,
|
||||
}) {
|
||||
return (
|
||||
<DialogTitle>
|
||||
<FlexWrapper>
|
||||
<FluidContainer mr={1.5}>
|
||||
<Box>
|
||||
<Typography variant="h3">
|
||||
{isInHiddenSection
|
||||
? t("ALL_HIDDEN_ALBUMS")
|
||||
: t("ALL_ALBUMS")}
|
||||
</Typography>
|
||||
<Typography variant="small" color={"text.muted"}>
|
||||
{t("albums_count", { count: collectionCount })}
|
||||
</Typography>
|
||||
</Box>
|
||||
</FluidContainer>
|
||||
<Stack direction="row" spacing={1.5}>
|
||||
<CollectionListSortBy
|
||||
activeSortBy={collectionSortBy}
|
||||
setSortBy={setCollectionSortBy}
|
||||
nestedInDialog
|
||||
/>
|
||||
<IconButtonWithBG onClick={onClose}>
|
||||
<Close />
|
||||
</IconButtonWithBG>
|
||||
</Stack>
|
||||
</FlexWrapper>
|
||||
</DialogTitle>
|
||||
);
|
||||
}
|
||||
@@ -1,39 +1,50 @@
|
||||
import { Divider, useMediaQuery } from "@mui/material";
|
||||
import { CollectionsSortOptions } from "@/new/photos/components/CollectionsSortOptions";
|
||||
import { FilledIconButton } from "@/new/photos/components/mui-custom";
|
||||
import type { CollectionSummary } from "@/new/photos/types/collection";
|
||||
import { CollectionsSortBy } from "@/new/photos/types/collection";
|
||||
import { FlexWrapper, FluidContainer } from "@ente/shared/components/Container";
|
||||
import Close from "@mui/icons-material/Close";
|
||||
import {
|
||||
Box,
|
||||
DialogTitle,
|
||||
Divider,
|
||||
Stack,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
} from "@mui/material";
|
||||
import {
|
||||
AllCollectionDialog,
|
||||
Transition,
|
||||
} from "components/Collections/AllCollections/dialog";
|
||||
import { CollectionSummary } from "types/collection";
|
||||
import { COLLECTION_LIST_SORT_BY } from "utils/collection";
|
||||
import { t } from "i18next";
|
||||
import AllCollectionContent from "./content";
|
||||
import AllCollectionsHeader from "./header";
|
||||
|
||||
interface Iprops {
|
||||
interface AllCollectionsProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
collectionSummaries: CollectionSummary[];
|
||||
setActiveCollectionID: (id?: number) => void;
|
||||
collectionListSortBy: COLLECTION_LIST_SORT_BY;
|
||||
setCollectionListSortBy: (v: COLLECTION_LIST_SORT_BY) => void;
|
||||
onSelectCollectionID: (id: number) => void;
|
||||
collectionsSortBy: CollectionsSortBy;
|
||||
onChangeCollectionsSortBy: (by: CollectionsSortBy) => void;
|
||||
isInHiddenSection: boolean;
|
||||
}
|
||||
|
||||
const LeftSlideTransition = Transition("up");
|
||||
|
||||
export default function AllCollections(props: Iprops) {
|
||||
export default function AllCollections(props: AllCollectionsProps) {
|
||||
const {
|
||||
collectionSummaries,
|
||||
open,
|
||||
onClose,
|
||||
setActiveCollectionID,
|
||||
collectionListSortBy,
|
||||
setCollectionListSortBy,
|
||||
onSelectCollectionID,
|
||||
collectionsSortBy,
|
||||
onChangeCollectionsSortBy,
|
||||
isInHiddenSection,
|
||||
} = props;
|
||||
const isMobile = useMediaQuery("(max-width: 428px)");
|
||||
|
||||
const onCollectionClick = (collectionID: number) => {
|
||||
setActiveCollectionID(collectionID);
|
||||
onSelectCollectionID(collectionID);
|
||||
onClose();
|
||||
};
|
||||
|
||||
@@ -47,11 +58,13 @@ export default function AllCollections(props: Iprops) {
|
||||
fullWidth={true}
|
||||
>
|
||||
<AllCollectionsHeader
|
||||
isInHiddenSection={isInHiddenSection}
|
||||
onClose={onClose}
|
||||
{...{
|
||||
isInHiddenSection,
|
||||
onClose,
|
||||
collectionsSortBy,
|
||||
onChangeCollectionsSortBy,
|
||||
}}
|
||||
collectionCount={props.collectionSummaries.length}
|
||||
collectionSortBy={collectionListSortBy}
|
||||
setCollectionSortBy={setCollectionListSortBy}
|
||||
/>
|
||||
<Divider />
|
||||
<AllCollectionContent
|
||||
@@ -61,3 +74,38 @@ export default function AllCollections(props: Iprops) {
|
||||
</AllCollectionDialog>
|
||||
);
|
||||
}
|
||||
|
||||
const AllCollectionsHeader = ({
|
||||
onClose,
|
||||
collectionCount,
|
||||
collectionsSortBy,
|
||||
onChangeCollectionsSortBy,
|
||||
isInHiddenSection,
|
||||
}) => (
|
||||
<DialogTitle>
|
||||
<FlexWrapper>
|
||||
<FluidContainer mr={1.5}>
|
||||
<Box>
|
||||
<Typography variant="h3">
|
||||
{isInHiddenSection
|
||||
? t("all_hidden_albums")
|
||||
: t("all_albums")}
|
||||
</Typography>
|
||||
<Typography variant="small" color={"text.muted"}>
|
||||
{t("albums_count", { count: collectionCount })}
|
||||
</Typography>
|
||||
</Box>
|
||||
</FluidContainer>
|
||||
<Stack direction="row" spacing={1.5}>
|
||||
<CollectionsSortOptions
|
||||
activeSortBy={collectionsSortBy}
|
||||
onChangeSortBy={onChangeCollectionsSortBy}
|
||||
nestedInDialog
|
||||
/>
|
||||
<FilledIconButton onClick={onClose}>
|
||||
<Close />
|
||||
</FilledIconButton>
|
||||
</Stack>
|
||||
</FlexWrapper>
|
||||
</DialogTitle>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ import downloadManager from "@/new/photos/services/download";
|
||||
import { EnteFile } from "@/new/photos/types/file";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
/** See also: {@link ItemCard}. */
|
||||
/** Deprecated in favor of {@link ItemCard}. */
|
||||
export default function CollectionCard(props: {
|
||||
children?: any;
|
||||
coverFile: EnteFile;
|
||||
|
||||
767
web/apps/photos/src/components/Collections/CollectionHeader.tsx
Normal file
@@ -0,0 +1,767 @@
|
||||
import { assertionFailed } from "@/base/assert";
|
||||
import log from "@/base/log";
|
||||
import type { Collection } from "@/media/collection";
|
||||
import { ItemVisibility } from "@/media/file-metadata";
|
||||
import {
|
||||
GalleryItemsHeaderAdapter,
|
||||
GalleryItemsSummary,
|
||||
} from "@/new/photos/components/Gallery/ListHeader";
|
||||
import { SpaceBetweenBox } from "@/new/photos/components/mui-custom";
|
||||
import type {
|
||||
CollectionSummary,
|
||||
CollectionSummaryType,
|
||||
} from "@/new/photos/types/collection";
|
||||
import { HorizontalFlex } from "@ente/shared/components/Container";
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import OverflowMenu, {
|
||||
StyledMenu,
|
||||
} from "@ente/shared/components/OverflowMenu/menu";
|
||||
import { OverflowMenuOption } from "@ente/shared/components/OverflowMenu/option";
|
||||
import ArchiveOutlined from "@mui/icons-material/ArchiveOutlined";
|
||||
import DeleteOutlinedIcon from "@mui/icons-material/DeleteOutlined";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
import Favorite from "@mui/icons-material/FavoriteRounded";
|
||||
import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined";
|
||||
import LinkIcon from "@mui/icons-material/Link";
|
||||
import LogoutIcon from "@mui/icons-material/Logout";
|
||||
import MoreHoriz from "@mui/icons-material/MoreHoriz";
|
||||
import PeopleIcon from "@mui/icons-material/People";
|
||||
import PushPinOutlined from "@mui/icons-material/PushPinOutlined";
|
||||
import SortIcon from "@mui/icons-material/Sort";
|
||||
import TvIcon from "@mui/icons-material/Tv";
|
||||
import Unarchive from "@mui/icons-material/Unarchive";
|
||||
import VisibilityOffOutlined from "@mui/icons-material/VisibilityOffOutlined";
|
||||
import VisibilityOutlined from "@mui/icons-material/VisibilityOutlined";
|
||||
import { Box, IconButton, Stack, Tooltip } from "@mui/material";
|
||||
import { SetCollectionNamerAttributes } from "components/Collections/CollectionNamer";
|
||||
import { UnPinIcon } from "components/icons/UnPinIcon";
|
||||
import { t } from "i18next";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { GalleryContext } from "pages/gallery";
|
||||
import React, { useCallback, useContext, useRef, useState } from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
import * as CollectionAPI from "services/collectionService";
|
||||
import * as TrashService from "services/trashService";
|
||||
import { SetFilesDownloadProgressAttributesCreator } from "types/gallery";
|
||||
import {
|
||||
ALL_SECTION,
|
||||
changeCollectionOrder,
|
||||
changeCollectionSortOrder,
|
||||
changeCollectionVisibility,
|
||||
downloadCollectionHelper,
|
||||
downloadDefaultHiddenCollectionHelper,
|
||||
HIDDEN_ITEMS_SECTION,
|
||||
isHiddenCollection,
|
||||
} from "utils/collection";
|
||||
import { isArchivedCollection, isPinnedCollection } from "utils/magicMetadata";
|
||||
|
||||
interface CollectionHeaderProps {
|
||||
collectionSummary: CollectionSummary;
|
||||
activeCollection: Collection;
|
||||
setActiveCollectionID: (collectionID: number) => void;
|
||||
isActiveCollectionDownloadInProgress: () => boolean;
|
||||
onCollectionShare: () => void;
|
||||
onCollectionCast: () => void;
|
||||
setCollectionNamerAttributes: SetCollectionNamerAttributes;
|
||||
setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator;
|
||||
}
|
||||
|
||||
/**
|
||||
* A header shown at the top of the list of photos in the gallery, when the
|
||||
* gallery is showing a collection.
|
||||
*/
|
||||
export const CollectionHeader: React.FC<CollectionHeaderProps> = ({
|
||||
collectionSummary,
|
||||
...rest
|
||||
}) => {
|
||||
if (!collectionSummary) {
|
||||
assertionFailed("Gallery/CollectionHeader without a collection");
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const { name, type, fileCount } = collectionSummary;
|
||||
|
||||
const EndIcon = ({ type }: { type: CollectionSummaryType }) => {
|
||||
switch (type) {
|
||||
case "favorites":
|
||||
return <Favorite />;
|
||||
case "archived":
|
||||
return <ArchiveOutlined />;
|
||||
case "incomingShareViewer":
|
||||
case "incomingShareCollaborator":
|
||||
return <PeopleIcon />;
|
||||
case "outgoingShare":
|
||||
return <PeopleIcon />;
|
||||
case "sharedOnlyViaLink":
|
||||
return <LinkIcon />;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<GalleryItemsHeaderAdapter>
|
||||
<SpaceBetweenBox>
|
||||
<GalleryItemsSummary
|
||||
name={name}
|
||||
fileCount={fileCount}
|
||||
endIcon={<EndIcon type={type} />}
|
||||
/>
|
||||
{shouldShowOptions(type) && (
|
||||
<CollectionOptions collectionSummaryType={type} {...rest} />
|
||||
)}
|
||||
</SpaceBetweenBox>
|
||||
</GalleryItemsHeaderAdapter>
|
||||
);
|
||||
};
|
||||
|
||||
const shouldShowOptions = (type: CollectionSummaryType) =>
|
||||
type != "all" && type != "archive";
|
||||
|
||||
type CollectionOptionsProps = Omit<
|
||||
CollectionHeaderProps,
|
||||
"collectionSummary"
|
||||
> & {
|
||||
collectionSummaryType: CollectionSummaryType;
|
||||
};
|
||||
|
||||
const CollectionOptions: React.FC<CollectionOptionsProps> = ({
|
||||
activeCollection,
|
||||
collectionSummaryType,
|
||||
setActiveCollectionID,
|
||||
onCollectionShare,
|
||||
onCollectionCast,
|
||||
setCollectionNamerAttributes,
|
||||
setFilesDownloadProgressAttributesCreator,
|
||||
isActiveCollectionDownloadInProgress,
|
||||
}) => {
|
||||
const { startLoading, finishLoading, setDialogMessage } =
|
||||
useContext(AppContext);
|
||||
const { syncWithRemote } = useContext(GalleryContext);
|
||||
const overFlowMenuIconRef = useRef<SVGSVGElement>(null);
|
||||
const [openSortOrderMenu, setOpenSortOrderMenu] = useState(false);
|
||||
|
||||
const handleError = useCallback(
|
||||
(e: unknown) => {
|
||||
log.error("Collection action failed", e);
|
||||
setDialogMessage({
|
||||
title: t("error"),
|
||||
content: t("UNKNOWN_ERROR"),
|
||||
close: { variant: "critical" },
|
||||
});
|
||||
},
|
||||
[setDialogMessage],
|
||||
);
|
||||
|
||||
/**
|
||||
* Return a new function by wrapping an async function in an error handler,
|
||||
* showing the global loading bar when the function runs, and syncing with
|
||||
* remote on completion.
|
||||
*/
|
||||
const wrap = useCallback(
|
||||
(f: () => Promise<void>) => {
|
||||
const wrapped = async () => {
|
||||
startLoading();
|
||||
try {
|
||||
await f();
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
} finally {
|
||||
void syncWithRemote(false, true);
|
||||
finishLoading();
|
||||
}
|
||||
};
|
||||
return (): void => void wrapped();
|
||||
},
|
||||
[handleError, syncWithRemote, startLoading, finishLoading],
|
||||
);
|
||||
|
||||
const showRenameCollectionModal = () => {
|
||||
setCollectionNamerAttributes({
|
||||
title: t("rename_album"),
|
||||
buttonText: t("rename"),
|
||||
autoFilledName: activeCollection.name,
|
||||
callback: renameCollection,
|
||||
});
|
||||
};
|
||||
|
||||
const _renameCollection = async (newName: string) => {
|
||||
if (activeCollection.name !== newName) {
|
||||
await CollectionAPI.renameCollection(activeCollection, newName);
|
||||
}
|
||||
};
|
||||
|
||||
const renameCollection = (newName: string) =>
|
||||
wrap(() => _renameCollection(newName))();
|
||||
|
||||
const confirmDeleteCollection = () => {
|
||||
setDialogMessage({
|
||||
title: t("delete_album_title"),
|
||||
content: (
|
||||
<Trans
|
||||
i18nKey={"delete_album_message"}
|
||||
components={{
|
||||
a: <Box component={"span"} color="text.base" />,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
proceed: {
|
||||
text: t("delete_photos"),
|
||||
action: deleteCollectionAlongWithFiles,
|
||||
variant: "critical",
|
||||
},
|
||||
secondary: {
|
||||
text: t("keep_photos"),
|
||||
action: deleteCollectionButKeepFiles,
|
||||
variant: "primary",
|
||||
},
|
||||
close: {
|
||||
text: t("cancel"),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const deleteCollectionAlongWithFiles = wrap(async () => {
|
||||
await CollectionAPI.deleteCollection(activeCollection.id, false);
|
||||
setActiveCollectionID(ALL_SECTION);
|
||||
});
|
||||
|
||||
const deleteCollectionButKeepFiles = wrap(async () => {
|
||||
await CollectionAPI.deleteCollection(activeCollection.id, true);
|
||||
setActiveCollectionID(ALL_SECTION);
|
||||
});
|
||||
|
||||
const confirmEmptyTrash = () =>
|
||||
setDialogMessage({
|
||||
title: t("empty_trash_title"),
|
||||
content: t("empty_trash_message"),
|
||||
proceed: {
|
||||
action: emptyTrash,
|
||||
text: t("empty_trash"),
|
||||
variant: "critical",
|
||||
},
|
||||
close: { text: t("cancel") },
|
||||
});
|
||||
|
||||
const emptyTrash = wrap(async () => {
|
||||
await TrashService.emptyTrash();
|
||||
await TrashService.clearLocalTrash();
|
||||
setActiveCollectionID(ALL_SECTION);
|
||||
});
|
||||
|
||||
const _downloadCollection = () => {
|
||||
if (isActiveCollectionDownloadInProgress()) return;
|
||||
|
||||
if (collectionSummaryType == "hiddenItems") {
|
||||
return downloadDefaultHiddenCollectionHelper(
|
||||
setFilesDownloadProgressAttributesCreator(
|
||||
activeCollection.name,
|
||||
HIDDEN_ITEMS_SECTION,
|
||||
true,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return downloadCollectionHelper(
|
||||
activeCollection.id,
|
||||
setFilesDownloadProgressAttributesCreator(
|
||||
activeCollection.name,
|
||||
activeCollection.id,
|
||||
isHiddenCollection(activeCollection),
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const downloadCollection = () =>
|
||||
void _downloadCollection().catch(handleError);
|
||||
|
||||
const archiveAlbum = wrap(() =>
|
||||
changeCollectionVisibility(activeCollection, ItemVisibility.archived),
|
||||
);
|
||||
|
||||
const unarchiveAlbum = wrap(() =>
|
||||
changeCollectionVisibility(activeCollection, ItemVisibility.visible),
|
||||
);
|
||||
|
||||
const confirmLeaveSharedAlbum = () => {
|
||||
setDialogMessage({
|
||||
title: t("leave_shared_album_title"),
|
||||
content: t("leave_shared_album_message"),
|
||||
proceed: {
|
||||
text: t("leave_shared_album"),
|
||||
action: leaveSharedAlbum,
|
||||
variant: "critical",
|
||||
},
|
||||
close: {
|
||||
text: t("cancel"),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const leaveSharedAlbum = wrap(async () => {
|
||||
await CollectionAPI.leaveSharedAlbum(activeCollection.id);
|
||||
setActiveCollectionID(ALL_SECTION);
|
||||
});
|
||||
|
||||
const pinAlbum = wrap(() => changeCollectionOrder(activeCollection, 1));
|
||||
|
||||
const unpinAlbum = wrap(() => changeCollectionOrder(activeCollection, 0));
|
||||
|
||||
const hideAlbum = wrap(async () => {
|
||||
await changeCollectionVisibility(
|
||||
activeCollection,
|
||||
ItemVisibility.hidden,
|
||||
);
|
||||
setActiveCollectionID(ALL_SECTION);
|
||||
});
|
||||
|
||||
const unhideAlbum = wrap(async () => {
|
||||
await changeCollectionVisibility(
|
||||
activeCollection,
|
||||
ItemVisibility.visible,
|
||||
);
|
||||
setActiveCollectionID(HIDDEN_ITEMS_SECTION);
|
||||
});
|
||||
|
||||
const showSortOrderMenu = () => setOpenSortOrderMenu(true);
|
||||
|
||||
const closeSortOrderMenu = () => setOpenSortOrderMenu(false);
|
||||
|
||||
const changeSortOrderAsc = wrap(() =>
|
||||
changeCollectionSortOrder(activeCollection, true),
|
||||
);
|
||||
|
||||
const changeSortOrderDesc = wrap(() =>
|
||||
changeCollectionSortOrder(activeCollection, false),
|
||||
);
|
||||
|
||||
return (
|
||||
<HorizontalFlex sx={{ display: "inline-flex", gap: "16px" }}>
|
||||
<QuickOptions
|
||||
collectionSummaryType={collectionSummaryType}
|
||||
isDownloadInProgress={isActiveCollectionDownloadInProgress}
|
||||
onEmptyTrashClick={confirmEmptyTrash}
|
||||
onDownloadClick={downloadCollection}
|
||||
onShareClick={onCollectionShare}
|
||||
/>
|
||||
|
||||
<OverflowMenu
|
||||
ariaControls={"collection-options"}
|
||||
triggerButtonIcon={<MoreHoriz ref={overFlowMenuIconRef} />}
|
||||
>
|
||||
{collectionSummaryType == "trash" ? (
|
||||
<EmptyTrashOption onClick={confirmEmptyTrash} />
|
||||
) : collectionSummaryType == "favorites" ? (
|
||||
<DownloadOption
|
||||
isDownloadInProgress={
|
||||
isActiveCollectionDownloadInProgress
|
||||
}
|
||||
onClick={downloadCollection}
|
||||
>
|
||||
{t("download_favorites")}
|
||||
</DownloadOption>
|
||||
) : collectionSummaryType == "uncategorized" ? (
|
||||
<DownloadOption onClick={downloadCollection}>
|
||||
{t("download_uncategorized")}
|
||||
</DownloadOption>
|
||||
) : collectionSummaryType == "hiddenItems" ? (
|
||||
<DownloadOption onClick={downloadCollection}>
|
||||
{t("download_hidden_items")}
|
||||
</DownloadOption>
|
||||
) : collectionSummaryType == "incomingShareViewer" ||
|
||||
collectionSummaryType == "incomingShareCollaborator" ? (
|
||||
<SharedCollectionOptions
|
||||
isArchived={isArchivedCollection(activeCollection)}
|
||||
onArchiveClick={archiveAlbum}
|
||||
onUnarchiveClick={unarchiveAlbum}
|
||||
onLeaveSharedAlbumClick={confirmLeaveSharedAlbum}
|
||||
onCastClick={onCollectionCast}
|
||||
/>
|
||||
) : (
|
||||
<AlbumCollectionOptions
|
||||
isArchived={isArchivedCollection(activeCollection)}
|
||||
isHidden={isHiddenCollection(activeCollection)}
|
||||
isPinned={isPinnedCollection(activeCollection)}
|
||||
onRenameClick={showRenameCollectionModal}
|
||||
onSortClick={showSortOrderMenu}
|
||||
onArchiveClick={archiveAlbum}
|
||||
onUnarchiveClick={unarchiveAlbum}
|
||||
onPinClick={pinAlbum}
|
||||
onUnpinClick={unpinAlbum}
|
||||
onHideClick={hideAlbum}
|
||||
onUnhideClick={unhideAlbum}
|
||||
onDeleteClick={confirmDeleteCollection}
|
||||
onShareClick={onCollectionShare}
|
||||
onCastClick={onCollectionCast}
|
||||
/>
|
||||
)}
|
||||
</OverflowMenu>
|
||||
<CollectionSortOrderMenu
|
||||
open={openSortOrderMenu}
|
||||
onClose={closeSortOrderMenu}
|
||||
overFlowMenuIconRef={overFlowMenuIconRef}
|
||||
onAscClick={changeSortOrderAsc}
|
||||
onDescClick={changeSortOrderDesc}
|
||||
/>
|
||||
</HorizontalFlex>
|
||||
);
|
||||
};
|
||||
|
||||
/** Props for a generic option. */
|
||||
interface OptionProps {
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
interface QuickOptionsProps {
|
||||
collectionSummaryType: CollectionSummaryType;
|
||||
isDownloadInProgress: () => boolean;
|
||||
onEmptyTrashClick: () => void;
|
||||
onDownloadClick: () => void;
|
||||
onShareClick: () => void;
|
||||
}
|
||||
|
||||
const QuickOptions: React.FC<QuickOptionsProps> = ({
|
||||
onEmptyTrashClick,
|
||||
onDownloadClick,
|
||||
onShareClick,
|
||||
collectionSummaryType: type,
|
||||
isDownloadInProgress,
|
||||
}) => (
|
||||
<Stack direction="row" sx={{ alignItems: "center", gap: "16px" }}>
|
||||
{showEmptyTrashQuickOption(type) && (
|
||||
<EmptyTrashQuickOption onClick={onEmptyTrashClick} />
|
||||
)}
|
||||
{showDownloadQuickOption(type) &&
|
||||
(isDownloadInProgress() ? (
|
||||
<EnteSpinner size="20px" sx={{ m: "12px" }} />
|
||||
) : (
|
||||
<DownloadQuickOption
|
||||
onClick={onDownloadClick}
|
||||
collectionSummaryType={type}
|
||||
/>
|
||||
))}
|
||||
{showShareQuickOption(type) && (
|
||||
<ShareQuickOption
|
||||
onClick={onShareClick}
|
||||
collectionSummaryType={type}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
const showEmptyTrashQuickOption = (type: CollectionSummaryType) =>
|
||||
type == "trash";
|
||||
|
||||
const EmptyTrashQuickOption: React.FC<OptionProps> = ({ onClick }) => (
|
||||
<Tooltip title={t("empty_trash")}>
|
||||
<IconButton onClick={onClick}>
|
||||
<DeleteOutlinedIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const showDownloadQuickOption = (type: CollectionSummaryType) =>
|
||||
type == "folder" ||
|
||||
type == "favorites" ||
|
||||
type == "album" ||
|
||||
type == "uncategorized" ||
|
||||
type == "hiddenItems" ||
|
||||
type == "incomingShareViewer" ||
|
||||
type == "incomingShareCollaborator" ||
|
||||
type == "outgoingShare" ||
|
||||
type == "sharedOnlyViaLink" ||
|
||||
type == "archived" ||
|
||||
type == "pinned";
|
||||
|
||||
type DownloadQuickOptionProps = OptionProps & {
|
||||
collectionSummaryType: CollectionSummaryType;
|
||||
};
|
||||
|
||||
const DownloadQuickOption: React.FC<DownloadQuickOptionProps> = ({
|
||||
onClick,
|
||||
collectionSummaryType,
|
||||
}) => (
|
||||
<Tooltip
|
||||
title={
|
||||
collectionSummaryType == "favorites"
|
||||
? t("download_favorites")
|
||||
: collectionSummaryType == "uncategorized"
|
||||
? t("download_uncategorized")
|
||||
: collectionSummaryType == "hiddenItems"
|
||||
? t("download_hidden_items")
|
||||
: t("download_album")
|
||||
}
|
||||
>
|
||||
<IconButton onClick={onClick}>
|
||||
<FileDownloadOutlinedIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const showShareQuickOption = (type: CollectionSummaryType) =>
|
||||
type == "folder" ||
|
||||
type == "album" ||
|
||||
type == "outgoingShare" ||
|
||||
type == "sharedOnlyViaLink" ||
|
||||
type == "archived" ||
|
||||
type == "incomingShareViewer" ||
|
||||
type == "incomingShareCollaborator" ||
|
||||
type == "pinned";
|
||||
|
||||
interface ShareQuickOptionProps {
|
||||
onClick: () => void;
|
||||
collectionSummaryType: CollectionSummaryType;
|
||||
}
|
||||
|
||||
const ShareQuickOption: React.FC<ShareQuickOptionProps> = ({
|
||||
onClick,
|
||||
collectionSummaryType,
|
||||
}) => (
|
||||
<Tooltip
|
||||
title={
|
||||
collectionSummaryType == "incomingShareViewer" ||
|
||||
collectionSummaryType == "incomingShareCollaborator"
|
||||
? t("sharing_details")
|
||||
: collectionSummaryType == "outgoingShare" ||
|
||||
collectionSummaryType == "sharedOnlyViaLink"
|
||||
? t("modify_sharing")
|
||||
: t("share_album")
|
||||
}
|
||||
>
|
||||
<IconButton onClick={onClick}>
|
||||
<PeopleIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const EmptyTrashOption: React.FC<OptionProps> = ({ onClick }) => (
|
||||
<OverflowMenuOption
|
||||
color="critical"
|
||||
startIcon={<DeleteOutlinedIcon />}
|
||||
onClick={onClick}
|
||||
>
|
||||
{t("empty_trash")}
|
||||
</OverflowMenuOption>
|
||||
);
|
||||
|
||||
type DownloadOptionProps = OptionProps & {
|
||||
isDownloadInProgress?: () => boolean;
|
||||
};
|
||||
|
||||
const DownloadOption: React.FC<
|
||||
React.PropsWithChildren<DownloadOptionProps>
|
||||
> = ({ isDownloadInProgress, onClick, children }) => (
|
||||
<OverflowMenuOption
|
||||
startIcon={
|
||||
isDownloadInProgress && isDownloadInProgress() ? (
|
||||
<EnteSpinner size="20px" sx={{ cursor: "not-allowed" }} />
|
||||
) : (
|
||||
<FileDownloadOutlinedIcon />
|
||||
)
|
||||
}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</OverflowMenuOption>
|
||||
);
|
||||
|
||||
interface SharedCollectionOptionProps {
|
||||
isArchived: boolean;
|
||||
onArchiveClick: () => void;
|
||||
onUnarchiveClick: () => void;
|
||||
onLeaveSharedAlbumClick: () => void;
|
||||
onCastClick: () => void;
|
||||
}
|
||||
|
||||
const SharedCollectionOptions: React.FC<SharedCollectionOptionProps> = ({
|
||||
isArchived,
|
||||
onArchiveClick,
|
||||
onUnarchiveClick,
|
||||
onLeaveSharedAlbumClick,
|
||||
onCastClick,
|
||||
}) => (
|
||||
<>
|
||||
{isArchived ? (
|
||||
<OverflowMenuOption
|
||||
onClick={onUnarchiveClick}
|
||||
startIcon={<Unarchive />}
|
||||
>
|
||||
{t("unarchive_album")}
|
||||
</OverflowMenuOption>
|
||||
) : (
|
||||
<OverflowMenuOption
|
||||
onClick={onArchiveClick}
|
||||
startIcon={<ArchiveOutlined />}
|
||||
>
|
||||
{t("archive_album")}
|
||||
</OverflowMenuOption>
|
||||
)}
|
||||
<OverflowMenuOption
|
||||
startIcon={<LogoutIcon />}
|
||||
onClick={onLeaveSharedAlbumClick}
|
||||
>
|
||||
{t("leave_album")}
|
||||
</OverflowMenuOption>
|
||||
<OverflowMenuOption startIcon={<TvIcon />} onClick={onCastClick}>
|
||||
{t("cast_album_to_tv")}
|
||||
</OverflowMenuOption>
|
||||
</>
|
||||
);
|
||||
|
||||
interface AlbumCollectionOptionsProps {
|
||||
isArchived: boolean;
|
||||
isPinned: boolean;
|
||||
isHidden: boolean;
|
||||
onRenameClick: () => void;
|
||||
onSortClick: () => void;
|
||||
onArchiveClick: () => void;
|
||||
onUnarchiveClick: () => void;
|
||||
onPinClick: () => void;
|
||||
onUnpinClick: () => void;
|
||||
onHideClick: () => void;
|
||||
onUnhideClick: () => void;
|
||||
onDeleteClick: () => void;
|
||||
onShareClick: () => void;
|
||||
onCastClick: () => void;
|
||||
}
|
||||
|
||||
const AlbumCollectionOptions: React.FC<AlbumCollectionOptionsProps> = ({
|
||||
isArchived,
|
||||
isPinned,
|
||||
isHidden,
|
||||
onRenameClick,
|
||||
onSortClick,
|
||||
onArchiveClick,
|
||||
onUnarchiveClick,
|
||||
onPinClick,
|
||||
onUnpinClick,
|
||||
onHideClick,
|
||||
onUnhideClick,
|
||||
onDeleteClick,
|
||||
onShareClick,
|
||||
onCastClick,
|
||||
}) => (
|
||||
<>
|
||||
<OverflowMenuOption onClick={onRenameClick} startIcon={<EditIcon />}>
|
||||
{t("rename_album")}
|
||||
</OverflowMenuOption>
|
||||
<OverflowMenuOption onClick={onSortClick} startIcon={<SortIcon />}>
|
||||
{t("sort_by")}
|
||||
</OverflowMenuOption>
|
||||
{isPinned ? (
|
||||
<OverflowMenuOption
|
||||
onClick={onUnpinClick}
|
||||
startIcon={<UnPinIcon />}
|
||||
>
|
||||
{t("unpin_album")}
|
||||
</OverflowMenuOption>
|
||||
) : (
|
||||
<OverflowMenuOption
|
||||
onClick={onPinClick}
|
||||
startIcon={<PushPinOutlined />}
|
||||
>
|
||||
{t("pin_album")}
|
||||
</OverflowMenuOption>
|
||||
)}
|
||||
{!isHidden && (
|
||||
<>
|
||||
{isArchived ? (
|
||||
<OverflowMenuOption
|
||||
onClick={onUnarchiveClick}
|
||||
startIcon={<Unarchive />}
|
||||
>
|
||||
{t("unarchive_album")}
|
||||
</OverflowMenuOption>
|
||||
) : (
|
||||
<OverflowMenuOption
|
||||
onClick={onArchiveClick}
|
||||
startIcon={<ArchiveOutlined />}
|
||||
>
|
||||
{t("archive_album")}
|
||||
</OverflowMenuOption>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{isHidden ? (
|
||||
<OverflowMenuOption
|
||||
onClick={onUnhideClick}
|
||||
startIcon={<VisibilityOutlined />}
|
||||
>
|
||||
{t("unhide_collection")}
|
||||
</OverflowMenuOption>
|
||||
) : (
|
||||
<OverflowMenuOption
|
||||
onClick={onHideClick}
|
||||
startIcon={<VisibilityOffOutlined />}
|
||||
>
|
||||
{t("hide_collection")}
|
||||
</OverflowMenuOption>
|
||||
)}
|
||||
<OverflowMenuOption
|
||||
startIcon={<DeleteOutlinedIcon />}
|
||||
onClick={onDeleteClick}
|
||||
>
|
||||
{t("delete_album")}
|
||||
</OverflowMenuOption>
|
||||
<OverflowMenuOption onClick={onShareClick} startIcon={<PeopleIcon />}>
|
||||
{t("share_album")}
|
||||
</OverflowMenuOption>
|
||||
<OverflowMenuOption startIcon={<TvIcon />} onClick={onCastClick}>
|
||||
{t("cast_album_to_tv")}
|
||||
</OverflowMenuOption>
|
||||
</>
|
||||
);
|
||||
|
||||
interface CollectionSortOrderMenuProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
overFlowMenuIconRef: React.MutableRefObject<SVGSVGElement>;
|
||||
onAscClick: () => void;
|
||||
onDescClick: () => void;
|
||||
}
|
||||
|
||||
const CollectionSortOrderMenu: React.FC<CollectionSortOrderMenuProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
overFlowMenuIconRef,
|
||||
onAscClick,
|
||||
onDescClick,
|
||||
}) => {
|
||||
const handleAscClick = () => {
|
||||
onClose();
|
||||
onAscClick();
|
||||
};
|
||||
|
||||
const handleDescClick = () => {
|
||||
onClose();
|
||||
onDescClick();
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledMenu
|
||||
id={"collection-files-sort"}
|
||||
anchorEl={overFlowMenuIconRef.current}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
MenuListProps={{
|
||||
disablePadding: true,
|
||||
"aria-labelledby": "collection-files-sort",
|
||||
}}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "right",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
}}
|
||||
>
|
||||
<OverflowMenuOption onClick={handleDescClick}>
|
||||
{t("newest_first")}
|
||||
</OverflowMenuOption>
|
||||
<OverflowMenuOption onClick={handleAscClick}>
|
||||
{t("oldest_first")}
|
||||
</OverflowMenuOption>
|
||||
</StyledMenu>
|
||||
);
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
import { FlexWrapper } from "@ente/shared/components/Container";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
import React from "react";
|
||||
interface Iprops {
|
||||
name: string;
|
||||
fileCount: number;
|
||||
endIcon?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function CollectionInfo({ name, fileCount, endIcon }: Iprops) {
|
||||
return (
|
||||
<div>
|
||||
<Typography variant="h3">{name}</Typography>
|
||||
|
||||
<FlexWrapper>
|
||||
<Typography variant="small" color="text.muted">
|
||||
{t("photos_count", { count: fileCount })}
|
||||
</Typography>
|
||||
{endIcon && (
|
||||
<Box
|
||||
sx={{
|
||||
svg: {
|
||||
fontSize: "17px",
|
||||
color: "text.muted",
|
||||
},
|
||||
}}
|
||||
ml={1.5}
|
||||
>
|
||||
{endIcon}
|
||||
</Box>
|
||||
)}
|
||||
</FlexWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import type { Collection } from "@/media/collection";
|
||||
import { SpaceBetweenFlex } from "@ente/shared/components/Container";
|
||||
import ArchiveOutlined from "@mui/icons-material/ArchiveOutlined";
|
||||
import Favorite from "@mui/icons-material/FavoriteRounded";
|
||||
import LinkIcon from "@mui/icons-material/Link";
|
||||
import PeopleIcon from "@mui/icons-material/People";
|
||||
import { SetCollectionNamerAttributes } from "components/Collections/CollectionNamer";
|
||||
import CollectionOptions from "components/Collections/CollectionOptions";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
import { CollectionSummary, CollectionSummaryType } from "types/collection";
|
||||
import { SetFilesDownloadProgressAttributesCreator } from "types/gallery";
|
||||
import { shouldShowOptions } from "utils/collection";
|
||||
import { CollectionInfo } from "./CollectionInfo";
|
||||
import { CollectionInfoBarWrapper } from "./styledComponents";
|
||||
|
||||
interface Iprops {
|
||||
activeCollection: Collection;
|
||||
collectionSummary: CollectionSummary;
|
||||
setCollectionNamerAttributes: SetCollectionNamerAttributes;
|
||||
showCollectionShareModal: () => void;
|
||||
setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator;
|
||||
isActiveCollectionDownloadInProgress: () => boolean;
|
||||
setActiveCollectionID: (collectionID: number) => void;
|
||||
setShowAlbumCastDialog: Dispatch<SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
export default function CollectionInfoWithOptions({
|
||||
collectionSummary,
|
||||
...props
|
||||
}: Iprops) {
|
||||
if (!collectionSummary) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const { name, type, fileCount } = collectionSummary;
|
||||
|
||||
const EndIcon = ({ type }: { type: CollectionSummaryType }) => {
|
||||
switch (type) {
|
||||
case CollectionSummaryType.favorites:
|
||||
return <Favorite />;
|
||||
case CollectionSummaryType.archived:
|
||||
return <ArchiveOutlined />;
|
||||
case CollectionSummaryType.incomingShareViewer:
|
||||
case CollectionSummaryType.incomingShareCollaborator:
|
||||
return <PeopleIcon />;
|
||||
case CollectionSummaryType.outgoingShare:
|
||||
return <PeopleIcon />;
|
||||
case CollectionSummaryType.sharedOnlyViaLink:
|
||||
return <LinkIcon />;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<CollectionInfoBarWrapper>
|
||||
<SpaceBetweenFlex>
|
||||
<CollectionInfo
|
||||
name={name}
|
||||
fileCount={fileCount}
|
||||
endIcon={<EndIcon type={type} />}
|
||||
/>
|
||||
{shouldShowOptions(type) && (
|
||||
<CollectionOptions
|
||||
{...props}
|
||||
collectionSummaryType={type}
|
||||
/>
|
||||
)}
|
||||
</SpaceBetweenFlex>
|
||||
</CollectionInfoBarWrapper>
|
||||
);
|
||||
}
|
||||